From 41fcf65c05f34a4a6600f80c895dca9d17a9cdde Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Wed, 18 Jul 2018 14:48:54 +0100 Subject: [PATCH] Run E2E tests for server execution as well as WebAssembly. Fixes several server-execution bugs uncovered by the E2E tests. --- .../src/Boot.Server.ts | 2 +- .../RenderBatch/OutOfProcessRenderBatch.ts | 12 ++- .../Circuits/RenderBatchWriter.cs | 16 ++- src/Microsoft.JSInterop/DotNetDispatcher.cs | 31 +++++- .../Infrastructure/BasicTestAppTestBase.cs | 26 ++--- .../Infrastructure/BrowserFixture.cs | 1 + .../ToggleExecutionModeServerFixture.cs | 49 ++++++++++ .../Infrastructure/ServerTestBase.cs | 5 +- .../Infrastructure/WaitAssert.cs | 70 +++++++++++++ .../ServerExecutionTestExtensions.cs | 16 +++ .../ServerSideAppTest.cs} | 32 +++--- .../ServerExecutionTests/TestSubclasses.cs | 59 +++++++++++ .../Tests/BinaryHttpClientTest.cs | 4 +- .../Tests/BindTest.cs | 45 ++++----- .../Tests/ComponentRenderingTest.cs | 97 ++++++++++++------- .../Tests/EventBubblingTest.cs | 30 +++--- .../Tests/EventTest.cs | 20 ++-- .../Tests/HttpClientTest.cs | 4 +- .../Tests/InteropTest.cs | 81 +++++++++------- .../Tests/RoutingTest.cs | 87 ++++++++--------- .../Tests/StandaloneAppTest.cs | 3 - .../BasicTestApp/CounterComponent.cshtml | 7 +- test/testapps/BasicTestApp/Index.cshtml | 64 ++++++++++++ .../BasicTestApp/InteropComponent.cshtml | 52 +++++++--- test/testapps/BasicTestApp/Program.cs | 25 ++--- test/testapps/BasicTestApp/Startup.cs | 33 +++++++ test/testapps/BasicTestApp/wwwroot/index.html | 67 +++---------- .../BasicTestApp/wwwroot/js/jsinteroptests.js | 91 +++++++++-------- test/testapps/TestServer/Startup.cs | 9 +- test/testapps/TestServer/TestServer.csproj | 5 + 30 files changed, 697 insertions(+), 346 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/WaitAssert.cs create mode 100644 test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs rename test/Microsoft.AspNetCore.Blazor.E2ETest/{Tests/ServerSideBlazorTest.cs => ServerExecutionTests/ServerSideAppTest.cs} (77%) create mode 100644 test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/TestSubclasses.cs create mode 100644 test/testapps/BasicTestApp/Index.cshtml create mode 100644 test/testapps/BasicTestApp/Startup.cs diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.Server.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.Server.ts index 65cfd6226c..0efbe4e161 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.Server.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Boot.Server.ts @@ -17,7 +17,7 @@ function boot() { }); connection = new signalR.HubConnectionBuilder() - .withUrl('/_blazor') + .withUrl('_blazor') .withHubProtocol(new MessagePackHubProtocol()) .configureLogging(signalR.LogLevel.Information) .build(); diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts index 9760fc5f5c..6915da01c4 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/RenderBatch/OutOfProcessRenderBatch.ts @@ -118,7 +118,8 @@ class OutOfProcessRenderTreeFrameReader implements RenderTreeFrameReader { } elementReferenceCaptureId(frame: RenderTreeFrame) { - return readInt32LE(this.batchDataUint8, frame as any + 4); // 2nd int + const stringIndex = readInt32LE(this.batchDataUint8, frame as any + 4); // 2nd int + return this.stringReader.readString(stringIndex); } componentId(frame: RenderTreeFrame) { @@ -159,14 +160,15 @@ class OutOfProcessStringReader { } readString(index: number): string | null { - const stringTableEntry = readInt32LE(this.batchDataUint8, this.stringTableStartIndex + index * stringTableEntryLength); - if (stringTableEntry === -1) { // Special value encodes 'null' + if (index === -1) { // Special value encodes 'null' return null; } else { + const stringTableEntryPos = readInt32LE(this.batchDataUint8, this.stringTableStartIndex + index * stringTableEntryLength); + // By default, .NET's BinaryWriter gives LEB128-length-prefixed UTF-8 data. // This is convenient enough to decode in JavaScript. - const numUtf8Bytes = readLEB128(this.batchDataUint8, stringTableEntry); - const charsStart = stringTableEntry + numLEB128Bytes(numUtf8Bytes); + const numUtf8Bytes = readLEB128(this.batchDataUint8, stringTableEntryPos); + const charsStart = stringTableEntryPos + numLEB128Bytes(numUtf8Bytes); const utf8Data = new DataView( this.batchDataUint8.buffer, this.batchDataUint8.byteOffset + charsStart, diff --git a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs index 1e892d7b1d..6538e81d26 100644 --- a/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs +++ b/src/Microsoft.AspNetCore.Blazor.Server/Circuits/RenderBatchWriter.cs @@ -138,7 +138,21 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits { case RenderTreeFrameType.Attribute: WriteString(frame.AttributeName); - WriteString(frame.AttributeValue as string); + if (frame.AttributeValue is bool boolValue) + { + // Encoding the bool as either "" or null is pretty odd, but avoids + // having to pack any "what type of thing is this" info into the same + // 4 bytes as the string table index. If, later, we need a way of + // distinguishing whether an attribute value is really a bool or a string + // or something else, we'll need a different encoding mechanism. Since there + // would never be more than (say) 2^28 (268 million) distinct string table + // entries, we could use the first 4 bits to encode the value type. + WriteString(boolValue ? string.Empty : null); + } + else + { + WriteString(frame.AttributeValue as string); + } _binaryWriter.Write(frame.AttributeEventHandlerId); break; case RenderTreeFrameType.Component: diff --git a/src/Microsoft.JSInterop/DotNetDispatcher.cs b/src/Microsoft.JSInterop/DotNetDispatcher.cs index d445c9519f..5612d9edcc 100644 --- a/src/Microsoft.JSInterop/DotNetDispatcher.cs +++ b/src/Microsoft.JSInterop/DotNetDispatcher.cs @@ -72,14 +72,25 @@ namespace Microsoft.JSInterop ? null : jsRuntimeBaseInstance.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId); - var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); + object syncResult = null; + Exception syncException = null; + + try + { + syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson); + } + catch (Exception ex) + { + syncException = ex; + } // If there was no callId, the caller does not want to be notified about the result if (callId != null) { // Invoke and coerce the result to a Task so the caller can use the same async API // for both synchronous and asynchronous methods - var task = syncResult is Task syncResultTask ? syncResultTask : Task.FromResult(syncResult); + var task = CoerceToTask(syncResult, syncException); + task.ContinueWith(completedTask => { try @@ -96,6 +107,22 @@ namespace Microsoft.JSInterop } } + private static Task CoerceToTask(object syncResult, Exception syncException) + { + if (syncException != null) + { + return Task.FromException(syncException); + } + else if (syncResult is Task syncResultTask) + { + return syncResultTask; + } + else + { + return Task.FromResult(syncResult); + } + } + private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson) { if (targetInstance != null) diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs index 5bd5e73875..a4d093799c 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using BasicTestApp; @@ -11,13 +11,14 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure { - public class BasicTestAppTestBase : ServerTestBase> + public class BasicTestAppTestBase : ServerTestBase> { - public const string ServerPathBase = "/subdir"; + public string ServerPathBase + => "/subdir" + (_serverFixture.UsingAspNetHost ? "#server" : ""); public BasicTestAppTestBase( BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { @@ -27,19 +28,18 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure protected IWebElement MountTestComponent() where TComponent : IComponent { var componentTypeName = typeof(TComponent).FullName; - WaitUntilDotNetRunningInBrowser(); - ((IJavaScriptExecutor)Browser).ExecuteScript( - $"mountTestComponent('{componentTypeName}')"); + var testSelector = WaitUntilTestSelectorReady(); + testSelector.SelectByValue("none"); + testSelector.SelectByValue(componentTypeName); return Browser.FindElement(By.TagName("app")); } - protected void WaitUntilDotNetRunningInBrowser() + protected SelectElement WaitUntilTestSelectorReady() { - new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(driver => - { - return ((IJavaScriptExecutor)driver) - .ExecuteScript("return window.isTestReady;"); - }); + var elemToFind = By.CssSelector("#test-selector > select"); + new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until( + driver => driver.FindElement(elemToFind) != null); + return new SelectElement(Browser.FindElement(elemToFind)); } } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs index 1d41aae7bf..a4dfff7e8b 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure try { var driver = new RemoteWebDriver(opts); + driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1); Browser = driver; Logs = new RemoteLogs(driver); } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs new file mode 100644 index 0000000000..7afc3bc21a --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures +{ + public class ToggleExecutionModeServerFixture + : ServerFixture + { + public string PathBase { get; set; } + public bool UsingAspNetHost { get; private set; } + + private AspNetSiteServerFixture.BuildWebHost _buildWebHostMethod; + private IDisposable _serverToDispose; + + public void UseAspNetHost(AspNetSiteServerFixture.BuildWebHost buildWebHostMethod) + { + _buildWebHostMethod = buildWebHostMethod + ?? throw new ArgumentNullException(nameof(buildWebHostMethod)); + UsingAspNetHost = true; + } + + protected override string StartAndGetRootUri() + { + if (_buildWebHostMethod == null) + { + // Use Blazor's dev host server + var underlying = new DevHostServerFixture(); + underlying.PathBase = PathBase; + _serverToDispose = underlying; + return underlying.RootUri.AbsoluteUri; + } + else + { + // Use specified ASP.NET host server + var underlying = new AspNetSiteServerFixture(); + underlying.BuildWebHostMethod = _buildWebHostMethod; + _serverToDispose = underlying; + return underlying.RootUri.AbsoluteUri; + } + } + + public override void Dispose() + { + _serverToDispose.Dispose(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs index deb029d89a..9ce9a38046 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure : BrowserTestBase, IClassFixture where TServerFixture: ServerFixture { - private readonly TServerFixture _serverFixture; + protected readonly TServerFixture _serverFixture; public ServerTestBase(BrowserFixture browserFixture, TServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, output) @@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure } } + Browser.Navigate().GoToUrl("about:blank"); Browser.Navigate().GoToUrl(absoluteUrl); } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/WaitAssert.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/WaitAssert.cs new file mode 100644 index 0000000000..38c5de0bcf --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/WaitAssert.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; +using Xunit; + +namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure +{ + // XUnit assertions, but hooked into Selenium's polling mechanism + + public class WaitAssert + { + private readonly static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(1); + + public static void Equal(T expected, Func actual) + => WaitAssertCore(() => Assert.Equal(expected, actual())); + + public static void True(Func actual) + => WaitAssertCore(() => Assert.True(actual())); + + public static void False(Func actual) + => WaitAssertCore(() => Assert.False(actual())); + + public static void Contains(string expectedSubstring, Func actualString) + => WaitAssertCore(() => Assert.Contains(expectedSubstring, actualString())); + + public static void Collection(Func> actualValues, params Action[] elementInspectors) + => WaitAssertCore(() => Assert.Collection(actualValues(), elementInspectors)); + + public static void Empty(Func actualValues) + => WaitAssertCore(() => Assert.Empty(actualValues())); + + public static void Single(Func actualValues) + => WaitAssertCore(() => Assert.Single(actualValues())); + + private static void WaitAssertCore(Action assertion, TimeSpan timeout = default) + { + if (timeout == default) + { + timeout = DefaultTimeout; + } + + try + { + new WebDriverWait(BrowserTestBase.Browser, timeout).Until(_ => + { + try + { + assertion(); + return true; + } + catch + { + return false; + } + }); + } + catch (WebDriverTimeoutException) + { + // Instead of reporting it as a timeout, report the Xunit exception + assertion(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs new file mode 100644 index 0000000000..db97edf45d --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/ServerExecutionTestExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; + +namespace Microsoft.AspNetCore.Blazor.E2ETest.ServerExecutionTests +{ + internal static class ServerExecutionTestExtensions + { + public static ToggleExecutionModeServerFixture WithServerExecution(this ToggleExecutionModeServerFixture serverFixture) + { + serverFixture.UseAspNetHost(TestServer.Program.BuildWebHost); + return serverFixture; + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ServerSideBlazorTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/ServerSideAppTest.cs similarity index 77% rename from test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ServerSideBlazorTest.cs rename to test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/ServerSideAppTest.cs index 45c38335c7..f60ae91c62 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ServerSideBlazorTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/ServerSideAppTest.cs @@ -10,23 +10,20 @@ using System.Linq; using Xunit; using Xunit.Abstractions; -namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests +namespace Microsoft.AspNetCore.Blazor.E2ETest.ServerExecutionTests { - public class ServerSideBlazorTest : ServerTestBase, IDisposable + public class ServerSideAppTest : ServerTestBase { - private readonly AspNetSiteServerFixture _serverFixture; - - public ServerSideBlazorTest( + public ServerSideAppTest( BrowserFixture browserFixture, AspNetSiteServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { - _serverFixture = serverFixture; _serverFixture.Environment = AspNetEnvironment.Development; _serverFixture.BuildWebHostMethod = ServerSideBlazor.Server.Program.BuildWebHost; - Navigate("/", noReload: true); + Navigate("/", noReload: false); WaitUntilLoaded(); } @@ -58,13 +55,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Browser.FindElement(By.LinkText("Counter")).Click(); // Verify we're now on the counter page, with that nav link (only) highlighted - Assert.Equal("Counter", Browser.FindElement(mainHeaderSelector).Text); + WaitAssert.Equal("Counter", () => Browser.FindElement(mainHeaderSelector).Text); Assert.Collection(Browser.FindElements(activeNavLinksSelector), item => Assert.Equal("Counter", item.Text)); // Verify we can navigate back to home too Browser.FindElement(By.LinkText("Home")).Click(); - Assert.Equal("Hello, world!", Browser.FindElement(mainHeaderSelector).Text); + WaitAssert.Equal("Hello, world!", () => Browser.FindElement(mainHeaderSelector).Text); Assert.Collection(Browser.FindElements(activeNavLinksSelector), item => Assert.Equal("Home", item.Text)); } @@ -74,7 +71,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests { // Navigate to "Counter" Browser.FindElement(By.LinkText("Counter")).Click(); - Assert.Equal("Counter", Browser.FindElement(By.TagName("h1")).Text); + WaitAssert.Equal("Counter", () => Browser.FindElement(By.TagName("h1")).Text); // Observe the initial value is zero var countDisplayElement = Browser.FindElement(By.CssSelector("h1 + p")); @@ -83,17 +80,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Click the button; see it counts var button = Browser.FindElement(By.CssSelector(".main button")); button.Click(); + WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text); button.Click(); + WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text); button.Click(); - Assert.Equal("Current count: 3", countDisplayElement.Text); + WaitAssert.Equal("Current count: 3", () => countDisplayElement.Text); } - [Fact(Skip = "This is failing and I don't know why")] + [Fact] public void HasFetchDataPage() { // Navigate to "Fetch Data" Browser.FindElement(By.LinkText("Fetch data")).Click(); - Assert.Equal("Weather forecast", Browser.FindElement(By.TagName("h1")).Text); + WaitAssert.Equal("Weather forecast", () => Browser.FindElement(By.TagName("h1")).Text); // Wait until loaded var tableSelector = By.CssSelector("table.table"); @@ -115,12 +114,5 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until( driver => driver.FindElement(By.TagName("app")).Text != "Loading..."); } - - public void Dispose() - { - // Make the tests run faster by navigating back to the home page when we are done - // If we don't, then the next test will reload the whole page before it starts - Browser.FindElement(By.LinkText("Home")).Click(); - } } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/TestSubclasses.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/TestSubclasses.cs new file mode 100644 index 0000000000..0cd17d7704 --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/ServerExecutionTests/TestSubclasses.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using BasicTestApp; +using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Blazor.E2ETest.Tests; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Blazor.E2ETest.ServerExecutionTests +{ + public class ServerComponentRenderingTest : ComponentRenderingTest + { + public ServerComponentRenderingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) + : base(browserFixture, serverFixture.WithServerExecution(), output) + { + } + } + + public class ServerBindTest : BindTest + { + public ServerBindTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) + : base(browserFixture, serverFixture.WithServerExecution(), output) + { + } + } + + public class ServerEventBubblingTest : EventBubblingTest + { + public ServerEventBubblingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) + : base(browserFixture, serverFixture.WithServerExecution(), output) + { + } + } + + public class ServerEventTest : EventTest + { + public ServerEventTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) + : base(browserFixture, serverFixture.WithServerExecution(), output) + { + } + } + + public class ServerInteropTest : InteropTest + { + public ServerInteropTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) + : base(browserFixture, serverFixture.WithServerExecution(), output) + { + } + } + + public class ServerRoutingTest : RoutingTest + { + public ServerRoutingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) + : base(browserFixture, serverFixture.WithServerExecution(), output) + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BinaryHttpClientTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BinaryHttpClientTest.cs index e5c302adf6..9e630ec9aa 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BinaryHttpClientTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BinaryHttpClientTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using BasicTestApp.HttpClientTest; @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests public BinaryHttpClientTest( BrowserFixture browserFixture, - DevHostServerFixture devHostServerFixture, + ToggleExecutionModeServerFixture devHostServerFixture, AspNetSiteServerFixture apiServerFixture, ITestOutputHelper output) : base(browserFixture, devHostServerFixture, output) diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs index e153c498b3..fadf0b37aa 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using BasicTestApp; @@ -14,12 +14,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests public class BindTest : BasicTestAppTestBase { public BindTest( - BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { - Navigate(ServerPathBase, noReload: true); + // On WebAssembly, page reloads are expensive so skip if possible + Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost); MountTestComponent(); } @@ -39,12 +40,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Assert.Equal(string.Empty, boundValue.Text); // Doesn't update until change event Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); target.SendKeys("\t"); - Assert.Equal("Changed value", boundValue.Text); + WaitAssert.Equal("Changed value", () => boundValue.Text); Assert.Equal("Changed value", mirrorValue.GetAttribute("value")); // Remove the value altogether setNullButton.Click(); - Assert.Equal(string.Empty, target.GetAttribute("value")); + WaitAssert.Equal(string.Empty, () => target.GetAttribute("value")); Assert.Equal(string.Empty, boundValue.Text); Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); } @@ -63,12 +64,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated and that textboxes linked to the same data are updated target.Clear(); target.SendKeys("Changed value\t"); - Assert.Equal("Changed value", boundValue.Text); + WaitAssert.Equal("Changed value", () => boundValue.Text); Assert.Equal("Changed value", mirrorValue.GetAttribute("value")); // Remove the value altogether setNullButton.Click(); - Assert.Equal(string.Empty, target.GetAttribute("value")); + WaitAssert.Equal(string.Empty, () => target.GetAttribute("value")); Assert.Equal(string.Empty, boundValue.Text); Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); } @@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests target.SendKeys("Changed value"); Assert.Equal(string.Empty, boundValue.Text); // Don't update as there's no change event fired yet. target.SendKeys("\t"); - Assert.Equal("Changed value", boundValue.Text); + WaitAssert.Equal("Changed value", () => boundValue.Text); } [Fact] @@ -99,7 +100,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated target.Clear(); target.SendKeys("Changed value\t"); - Assert.Equal("Changed value", boundValue.Text); + WaitAssert.Equal("Changed value", () => boundValue.Text); } [Fact] @@ -113,12 +114,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated target.Click(); - Assert.True(target.Selected); + WaitAssert.True(() => target.Selected); Assert.Equal("True", boundValue.Text); // Modify data; verify checkbox is updated invertButton.Click(); - Assert.False(target.Selected); + WaitAssert.False(() => target.Selected); Assert.Equal("False", boundValue.Text); } @@ -133,12 +134,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated target.Click(); - Assert.False(target.Selected); + WaitAssert.False(() => target.Selected); Assert.Equal("False", boundValue.Text); // Modify data; verify checkbox is updated invertButton.Click(); - Assert.True(target.Selected); + WaitAssert.True(() => target.Selected); Assert.Equal("True", boundValue.Text); } @@ -152,13 +153,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated target.SelectByText("Third choice"); - Assert.Equal("Third", boundValue.Text); + WaitAssert.Equal("Third", () => boundValue.Text); // Also verify we can add and select new options atomically // Don't move this into a separate test, because then the previous assertions // would be dependent on test execution order (or would require a full page reload) Browser.FindElement(By.Id("select-box-add-option")).Click(); - Assert.Equal("Fourth", boundValue.Text); + WaitAssert.Equal("Fourth", () => boundValue.Text); Assert.Equal("Fourth choice", target.SelectedOption.Text); } @@ -175,7 +176,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated and that textboxes linked to the same data are updated target.Clear(); target.SendKeys("42\t"); - Assert.Equal("42", boundValue.Text); + WaitAssert.Equal("42", () => boundValue.Text); Assert.Equal("42", mirrorValue.GetAttribute("value")); } @@ -192,7 +193,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated and that textboxes linked to the same data are updated target.Clear(); target.SendKeys("-3000000000\t"); - Assert.Equal("-3000000000", boundValue.Text); + WaitAssert.Equal("-3000000000", () => boundValue.Text); Assert.Equal("-3000000000", mirrorValue.GetAttribute("value")); } @@ -209,7 +210,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated and that textboxes linked to the same data are updated target.Clear(); target.SendKeys("-3.141\t"); - Assert.Equal("-3.141", boundValue.Text); + WaitAssert.Equal("-3.141", () => boundValue.Text); Assert.Equal("-3.141", mirrorValue.GetAttribute("value")); } @@ -226,14 +227,14 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Modify target; verify value is updated and that textboxes linked to the same data are updated target.Clear(); target.SendKeys("-3.14159265359\t"); - Assert.Equal("-3.14159265359", boundValue.Text); + WaitAssert.Equal("-3.14159265359", () => boundValue.Text); Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value")); // Modify target; verify value is updated and that textboxes linked to the same data are updated // Double shouldn't preserve trailing zeros target.Clear(); target.SendKeys("0.010\t"); - Assert.Equal("0.01", boundValue.Text); + WaitAssert.Equal("0.01", () => boundValue.Text); Assert.Equal("0.01", mirrorValue.GetAttribute("value")); } @@ -251,7 +252,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Decimal should preserve trailing zeros target.Clear(); target.SendKeys("0.010\t"); - Assert.Equal("0.010", boundValue.Text); + WaitAssert.Equal("0.010", () => boundValue.Text); Assert.Equal("0.010", mirrorValue.GetAttribute("value")); } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs index ef77b7cfee..845f83ce5a 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs @@ -11,6 +11,7 @@ using BasicTestApp.HierarchicalImportsTest.Subdir; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; using OpenQA.Selenium; +using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; @@ -20,11 +21,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests { public ComponentRenderingTest( BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { - Navigate(ServerPathBase, noReload: true); + Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost); } [Fact] @@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Clicking button increments count appElement.FindElement(By.TagName("button")).Click(); - Assert.Equal("Current count: 1", countDisplayElement.Text); + WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text); } [Fact] @@ -84,11 +85,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Clicking 'tick' changes the state, and starts a task appElement.FindElement(By.Id("tick")).Click(); - Assert.Equal("Started", stateElement.Text); + WaitAssert.Equal("Started", () => stateElement.Text); // Clicking 'tock' completes the task, which updates the state appElement.FindElement(By.Id("tock")).Click(); - Assert.Equal("Stopped", stateElement.Text); + WaitAssert.Equal("Stopped", () => stateElement.Text); } [Fact] @@ -103,12 +104,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Typing adds element inputElement.SendKeys("a"); - Assert.Collection(liElements(), + WaitAssert.Collection(liElements, li => Assert.Equal("a", li.Text)); // Typing again adds another element inputElement.SendKeys("b"); - Assert.Collection(liElements(), + WaitAssert.Collection(liElements, li => Assert.Equal("a", li.Text), li => Assert.Equal("b", li.Text)); @@ -127,17 +128,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Initial count is zero; clicking button increments count Assert.Equal("Current count: 0", countDisplayElement.Text); incrementButton.Click(); - Assert.Equal("Current count: 1", countDisplayElement.Text); + WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text); // We can remove an event handler toggleClickHandlerCheckbox.Click(); + WaitAssert.Empty(() => appElement.FindElements(By.Id("listening-message"))); incrementButton.Click(); - Assert.Equal("Current count: 1", countDisplayElement.Text); + WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text); // We can add an event handler toggleClickHandlerCheckbox.Click(); + appElement.FindElement(By.Id("listening-message")); incrementButton.Click(); - Assert.Equal("Current count: 2", countDisplayElement.Text); + WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text); } [Fact] @@ -164,7 +167,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Clicking increments count in child component appElement.FindElement(By.TagName("button")).Click(); - Assert.Equal("Current count: 1", counterDisplay.Text); + WaitAssert.Equal("Current count: 1", () => counterDisplay.Text); } [Fact] @@ -179,7 +182,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Clicking increments count in child element appElement.FindElement(By.TagName("button")).Click(); - Assert.Equal("1", messageElementInChild.Text); + WaitAssert.Equal("1", () => messageElementInChild.Text); } [Fact] @@ -192,12 +195,22 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Func> childComponentWrappers = () => appElement.FindElements(By.TagName("p")); Assert.Empty(childComponentWrappers()); - // Click to add some child components + // Click to add/remove some child components addButton.Click(); + WaitAssert.Collection(childComponentWrappers, + elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text)); + addButton.Click(); + WaitAssert.Collection(childComponentWrappers, + elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text), + elem => Assert.Equal("Child 2", elem.FindElement(By.ClassName("message")).Text)); + removeButton.Click(); + WaitAssert.Collection(childComponentWrappers, + elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text)); + addButton.Click(); - Assert.Collection(childComponentWrappers(), + WaitAssert.Collection(childComponentWrappers, elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text), elem => Assert.Equal("Child 3", elem.FindElement(By.ClassName("message")).Text)); } @@ -215,7 +228,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // When property changes, child is renotified before rerender incrementButton.Click(); - Assert.Equal("You supplied: 101", suppliedValueElement.Text); + WaitAssert.Equal("You supplied: 101", () => suppliedValueElement.Text); Assert.Equal("I computed: 202", computedValueElement.Text); } @@ -225,8 +238,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Initially, the region isn't shown var appElement = MountTestComponent(); var originalButton = appElement.FindElement(By.TagName("button")); - var fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]")); - Assert.Empty(fragmentElements); + Func> fragmentElements = () => appElement.FindElements(By.CssSelector("p[name=fragment-element]")); + Assert.Empty(fragmentElements()); // The JS-side DOM builder handles regions correctly, placing elements // after the region after the corresponding elements @@ -234,13 +247,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // When we click the button, the region is shown originalButton.Click(); - fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]")); - Assert.Single(fragmentElements); + WaitAssert.Single(fragmentElements); // The button itself was preserved, so we can click it again and see the effect originalButton.Click(); - fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]")); - Assert.Empty(fragmentElements); + WaitAssert.Empty(fragmentElements); } [Fact] @@ -263,11 +274,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // .NET code access to browser APIs var showPromptButton = appElement.FindElements(By.TagName("button")).First(); showPromptButton.Click(); - var modal = Browser.SwitchTo().Alert(); + + var modal = new WebDriverWait(Browser, TimeSpan.FromSeconds(3)) + .Until(SwitchToAlert); modal.SendKeys("Some value from test"); modal.Accept(); var promptResult = appElement.FindElement(By.TagName("strong")); - Assert.Equal("Some value from test", promptResult.Text); + WaitAssert.Equal("Some value from test", () => promptResult.Text); // NuGet packages can also embed entire Blazor components (themselves // authored as Razor files), including static content. The CSS value @@ -280,7 +293,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests var externalComponentButton = specialStyleDiv.FindElement(By.TagName("button")); Assert.Equal("Click me", externalComponentButton.Text); externalComponentButton.Click(); - Assert.Equal("It works", externalComponentButton.Text); + WaitAssert.Equal("It works", () => externalComponentButton.Text); } [Fact] @@ -324,9 +337,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Assert.Equal(string.Empty, inputElement.GetAttribute("value")); buttonElement.Click(); - Assert.Equal("Clicks: 1", inputElement.GetAttribute("value")); + WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value")); buttonElement.Click(); - Assert.Equal("Clicks: 2", inputElement.GetAttribute("value")); + WaitAssert.Equal("Clicks: 2", () => inputElement.GetAttribute("value")); } [Fact] @@ -342,17 +355,16 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Remove the captured element checkbox.Click(); - Assert.Empty(appElement.FindElements(By.Id("capturedElement"))); + WaitAssert.Empty(() => appElement.FindElements(By.Id("capturedElement"))); // Re-add it; observe it starts empty again checkbox.Click(); var inputElement = appElement.FindElement(By.Id("capturedElement")); - Assert.NotNull(inputElement); Assert.Equal(string.Empty, inputElement.GetAttribute("value")); // See that the capture variable was automatically updated to reference the new instance buttonElement.Click(); - Assert.Equal("Clicks: 1", inputElement.GetAttribute("value")); + WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value")); } [Fact] @@ -363,26 +375,27 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests var currentCountTextSelector = By.CssSelector("#child-component p:first-of-type"); var resetButton = appElement.FindElement(By.Id("reset-child")); var toggleChildCheckbox = appElement.FindElement(By.Id("toggle-child")); + Func currentCountText = () => appElement.FindElement(currentCountTextSelector).Text; // Verify the reference was captured initially appElement.FindElement(incrementButtonSelector).Click(); - Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text); + WaitAssert.Equal("Current count: 1", currentCountText); resetButton.Click(); - Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text); + WaitAssert.Equal("Current count: 0", currentCountText); appElement.FindElement(incrementButtonSelector).Click(); - Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text); + WaitAssert.Equal("Current count: 1", currentCountText); // Remove and re-add a new instance of the child, checking the text was reset toggleChildCheckbox.Click(); - Assert.Empty(appElement.FindElements(incrementButtonSelector)); + WaitAssert.Empty(() => appElement.FindElements(incrementButtonSelector)); toggleChildCheckbox.Click(); - Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text); + WaitAssert.Equal("Current count: 0", currentCountText); // Verify we have a new working reference appElement.FindElement(incrementButtonSelector).Click(); - Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text); + WaitAssert.Equal("Current count: 1", currentCountText); resetButton.Click(); - Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text); + WaitAssert.Equal("Current count: 0", currentCountText); } [Fact] @@ -392,5 +405,17 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests var inputElement = appElement.FindElement(By.TagName("input")); Assert.Equal("Value set after render", inputElement.GetAttribute("value")); } + + static IAlert SwitchToAlert(IWebDriver driver) + { + try + { + return driver.SwitchTo().Alert(); + } + catch (NoAlertPresentException) + { + return null; + } + } } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventBubblingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventBubblingTest.cs index 5302a738d3..8c80c48d22 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventBubblingTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventBubblingTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using BasicTestApp; @@ -20,12 +20,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // the one that doesn't match the 'bubbles' flag on the received event object. public EventBubblingTest( - BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { - Navigate(ServerPathBase, noReload: true); + Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost); MountTestComponent(); } @@ -35,9 +35,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Browser.FindElement(By.Id("button-with-onclick")).Click(); // Triggers event on target and ancestors with handler in upwards direction - Assert.Equal( + WaitAssert.Equal( new[] { "target onclick", "parent onclick" }, - GetLogLines()); + GetLogLines); } [Fact] @@ -46,9 +46,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Browser.FindElement(By.Id("button-without-onclick")).Click(); // Triggers event on ancestors with handler in upwards direction - Assert.Equal( + WaitAssert.Equal( new[] { "parent onclick" }, - GetLogLines()); + GetLogLines); } [Fact] @@ -57,9 +57,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests TriggerCustomBubblingEvent("element-with-onsneeze", "sneeze"); // Triggers event on target and ancestors with handler in upwards direction - Assert.Equal( + WaitAssert.Equal( new[] { "target onsneeze", "parent onsneeze" }, - GetLogLines()); + GetLogLines); } [Fact] @@ -68,9 +68,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests TriggerCustomBubblingEvent("element-without-onsneeze", "sneeze"); // Triggers event on ancestors with handler in upwards direction - Assert.Equal( + WaitAssert.Equal( new[] { "parent onsneeze" }, - GetLogLines()); + GetLogLines); } [Fact] @@ -79,7 +79,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Browser.FindElement(By.Id("input-with-onfocus")).Click(); // Triggers event only on target, not other ancestors with event handler - Assert.Equal(new[] { "target onfocus" }, GetLogLines()); + WaitAssert.Equal( + new[] { "target onfocus" }, + GetLogLines); } [Fact] @@ -88,7 +90,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests Browser.FindElement(By.Id("input-without-onfocus")).Click(); // Triggers no event - Assert.Empty(GetLogLines()); + WaitAssert.Empty(GetLogLines); } private string[] GetLogLines() diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs index 51e805dafb..a5db339ef5 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs @@ -1,13 +1,11 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using BasicTestApp; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; -using OpenQA.Selenium.Support.UI; using Xunit; using Xunit.Abstractions; @@ -16,8 +14,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests public class EventTest : BasicTestAppTestBase { public EventTest( - BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { @@ -38,13 +36,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // Focus the target, verify onfocusin is fired input.Click(); - Assert.Equal("onfocus,onfocusin,", output.Text); + WaitAssert.Equal("onfocus,onfocusin,", () => output.Text); // Focus something else, verify onfocusout is also fired var other = Browser.FindElement(By.Id("other")); other.Click(); - Assert.Equal("onfocus,onfocusin,onblur,onfocusout,", output.Text); + WaitAssert.Equal("onfocus,onfocusin,onblur,onfocusout,", () => output.Text); } [Fact] @@ -65,7 +63,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests .MoveToElement(other); actions.Perform(); - Assert.Equal("onmouseover,onmouseout,", output.Text); + WaitAssert.Equal("onmouseover,onmouseout,", () => output.Text); } [Fact] @@ -84,7 +82,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests .MoveToElement(input, 10, 10); actions.Perform(); - Assert.Contains("onmousemove,", output.Text); + WaitAssert.Contains("onmousemove,", () => output.Text); } [Fact] @@ -103,12 +101,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests var actions = new Actions(Browser).ClickAndHold(input); actions.Perform(); - Assert.Equal("onmousedown,", output.Text); + WaitAssert.Equal("onmousedown,", () => output.Text); actions = new Actions(Browser).Release(input); actions.Perform(); - Assert.Equal("onmousedown,onmouseup,", output.Text); + WaitAssert.Equal("onmousedown,onmouseup,", () => output.Text); } } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs index 604d717230..d114f75637 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using BasicTestApp.HttpClientTest; @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests public HttpClientTest( BrowserFixture browserFixture, - DevHostServerFixture devHostServerFixture, + ToggleExecutionModeServerFixture devHostServerFixture, AspNetSiteServerFixture apiServerFixture, ITestOutputHelper output) : base(browserFixture, devHostServerFixture, output) diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/InteropTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/InteropTest.cs index c9bc666b99..2327cf5f0b 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/InteropTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/InteropTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests { public InteropTest( BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { @@ -26,7 +26,42 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests public void CanInvokeDotNetMethods() { // Arrange - var expectedValues = new Dictionary + var expectedAsyncValues = new Dictionary + { + ["VoidParameterlessAsync"] = "[]", + ["VoidWithOneParameterAsync"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", + ["VoidWithTwoParametersAsync"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]", + ["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]", + ["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]", + ["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]", + ["VoidWithSixParametersAsync"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]", + ["VoidWithSevenParametersAsync"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]", + ["VoidWithEightParametersAsync"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]", + ["result1Async"] = @"[0.1,0.2]", + ["result2Async"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", + ["result3Async"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]", + ["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]", + ["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]", + ["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]", + ["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]", + ["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]", + ["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]", + ["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!", + ["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!", + ["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!", + ["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!", + ["resultReturnDotNetObjectByRefAsync"] = "1001", + ["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""", + ["instanceMethodStringValueUpperAsync"] = @"""MY STRING""", + ["instanceMethodIncomingByRefAsync"] = "123", + ["instanceMethodOutgoingByRefAsync"] = "1234", + ["stringValueUpperAsync"] = "MY STRING", + ["testDtoNonSerializedValueAsync"] = "99999", + ["testDtoAsync"] = "Same", + ["returnPrimitiveAsync"] = "123", + }; + + var expectedSyncValues = new Dictionary { ["VoidParameterless"] = "[]", ["VoidWithOneParameter"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", @@ -37,15 +72,6 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests ["VoidWithSixParameters"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]", ["VoidWithSevenParameters"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]", ["VoidWithEightParameters"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]", - ["VoidParameterlessAsync"] = "[]", - ["VoidWithOneParameterAsync"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", - ["VoidWithTwoParametersAsync"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]", - ["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]", - ["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]", - ["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]", - ["VoidWithSixParametersAsync"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]", - ["VoidWithSevenParametersAsync"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]", - ["VoidWithEightParametersAsync"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]", ["result1"] = @"[0.1,0.2]", ["result2"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", ["result3"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]", @@ -55,40 +81,29 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests ["result7"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]", ["result8"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]", ["result9"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]", - ["result1Async"] = @"[0.1,0.2]", - ["result2Async"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", - ["result3Async"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]", - ["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]", - ["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]", - ["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]", - ["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]", - ["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]", - ["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]", ["ThrowException"] = @"""System.InvalidOperationException: Threw an exception!", - ["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!", - ["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!", ["ExceptionFromSyncMethod"] = "Function threw an exception!", - ["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!", - ["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!", ["resultReturnDotNetObjectByRefSync"] = "1000", - ["resultReturnDotNetObjectByRefAsync"] = "1001", ["instanceMethodThisTypeName"] = @"""JavaScriptInterop""", ["instanceMethodStringValueUpper"] = @"""MY STRING""", ["instanceMethodIncomingByRef"] = "123", ["instanceMethodOutgoingByRef"] = "1234", - ["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""", - ["instanceMethodStringValueUpperAsync"] = @"""MY STRING""", - ["instanceMethodIncomingByRefAsync"] = "123", - ["instanceMethodOutgoingByRefAsync"] = "1234", ["stringValueUpperSync"] = "MY STRING", ["testDtoNonSerializedValueSync"] = "99999", ["testDtoSync"] = "Same", - ["stringValueUpperAsync"] = "MY STRING", - ["testDtoNonSerializedValueAsync"] = "99999", - ["testDtoAsync"] = "Same", ["returnPrimitive"] = "123", - ["returnPrimitiveAsync"] = "123", }; + + // Include the sync assertions only when running under WebAssembly + var expectedValues = expectedAsyncValues; + if (!_serverFixture.UsingAspNetHost) + { + foreach (var kvp in expectedSyncValues) + { + expectedValues.Add(kvp.Key, kvp.Value); + } + } + var actualValues = new Dictionary(); // Act diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs index 759722e8c0..66360f85ba 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs @@ -15,25 +15,22 @@ using Xunit.Abstractions; namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests { - public class RoutingTest : BasicTestAppTestBase, IDisposable + public class RoutingTest : BasicTestAppTestBase { - private readonly ServerFixture _server; - public RoutingTest( - BrowserFixture browserFixture, - DevHostServerFixture serverFixture, + BrowserFixture browserFixture, + ToggleExecutionModeServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { - _server = serverFixture; - Navigate(ServerPathBase, noReload: true); - WaitUntilDotNetRunningInBrowser(); + Navigate(ServerPathBase, noReload: false); + WaitUntilTestSelectorReady(); } [Fact] public void CanArriveAtDefaultPage() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); @@ -47,7 +44,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests // servers to enforce a canonical URL (with trailing slash) for the homepage. // But in case they don't want to, we need to handle it the same as if the URL does // have a trailing slash. - SetUrlViaPushState($"{ServerPathBase}"); + SetUrlViaPushState(""); var app = MountTestComponent(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); @@ -57,7 +54,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests [Fact] public void CanArriveAtPageWithParameters() { - SetUrlViaPushState($"{ServerPathBase}/WithParameters/Name/Ghi/LastName/O'Jkl"); + SetUrlViaPushState("/WithParameters/Name/Ghi/LastName/O'Jkl"); var app = MountTestComponent(); Assert.Equal("Your full name is Ghi O'Jkl.", app.FindElement(By.Id("test-info")).Text); @@ -67,7 +64,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests [Fact] public void CanArriveAtNonDefaultPage() { - SetUrlViaPushState($"{ServerPathBase}/Other"); + SetUrlViaPushState("/Other"); var app = MountTestComponent(); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); @@ -77,11 +74,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests [Fact] public void CanFollowLinkToOtherPage() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other")).Click(); - Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } @@ -93,20 +90,20 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests try { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); var button = app.FindElement(By.LinkText("Other")); new Actions(Browser).KeyDown(key).Click(button).Build().Perform(); - Assert.Equal(2, Browser.WindowHandles.Count); + WaitAssert.Equal(2, () => Browser.WindowHandles.Count); } finally { // Leaving the ctrl key up new Actions(Browser).KeyUp(key).Build().Perform(); - + // Closing newly opened windows if a new one was opened while (Browser.WindowHandles.Count > 1) { @@ -123,7 +120,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests [Fact] public void CanFollowLinkToOtherPageDoesNotOpenNewWindow() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); @@ -135,106 +132,106 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests [Fact] public void CanFollowLinkToOtherPageWithBaseRelativeUrl() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click(); - Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToEmptyStringHrefAsBaseRelativeUrl() { - SetUrlViaPushState($"{ServerPathBase}/Other"); + SetUrlViaPushState("/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default with base-relative URL (matches all)")).Click(); - Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToPageWithParameters() { - SetUrlViaPushState($"{ServerPathBase}/Other"); + SetUrlViaPushState("/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("With parameters")).Click(); - Assert.Equal("Your full name is Abc McDef.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("Your full name is Abc McDef.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("With parameters"); } [Fact] public void CanFollowLinkToDefaultPage() { - SetUrlViaPushState($"{ServerPathBase}/Other"); + SetUrlViaPushState("/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default (matches all)")).Click(); - Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToOtherPageWithQueryString() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other with query")).Click(); - Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with query"); } [Fact] public void CanFollowLinkToDefaultPageWithQueryString() { - SetUrlViaPushState($"{ServerPathBase}/Other"); + SetUrlViaPushState("/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default with query")).Click(); - Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default with query"); } [Fact] public void CanFollowLinkToOtherPageWithHash() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other with hash")).Click(); - Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with hash"); } [Fact] public void CanFollowLinkToDefaultPageWithHash() { - SetUrlViaPushState($"{ServerPathBase}/Other"); + SetUrlViaPushState("/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default with hash")).Click(); - Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default with hash"); } [Fact] public void CanNavigateProgrammatically() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var app = MountTestComponent(); app.FindElement(By.TagName("button")).Click(); - Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); + WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } [Fact] public void ClickingAnchorWithNoHrefShouldNotNavigate() { - SetUrlViaPushState($"{ServerPathBase}/"); + SetUrlViaPushState("/"); var initialUrl = Browser.Url; var app = MountTestComponent(); @@ -244,25 +241,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } - public void Dispose() - { - // Clear any existing state - SetUrlViaPushState(ServerPathBase); - MountTestComponent(); - } - private void SetUrlViaPushState(string relativeUri) { + var pathBaseWithoutHash = ServerPathBase.Split('#')[0]; var jsExecutor = (IJavaScriptExecutor)Browser; - var absoluteUri = new Uri(_server.RootUri, relativeUri); + var absoluteUri = new Uri(_serverFixture.RootUri, $"{pathBaseWithoutHash}{relativeUri}"); jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')"); } private void AssertHighlightedLinks(params string[] linkTexts) { - var actual = Browser.FindElements(By.CssSelector("a.active")); - var actualTexts = actual.Select(x => x.Text); - Assert.Equal(linkTexts, actualTexts); + WaitAssert.Equal(linkTexts, () => Browser + .FindElements(By.CssSelector("a.active")) + .Select(x => x.Text)); } } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs index 5b0b382a7b..0119935d67 100644 --- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs @@ -15,15 +15,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests public class StandaloneAppTest : ServerTestBase>, IDisposable { - private readonly ServerFixture _serverFixture; - public StandaloneAppTest( BrowserFixture browserFixture, DevHostServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { - _serverFixture = serverFixture; Navigate("/", noReload: true); WaitUntilLoaded(); } diff --git a/test/testapps/BasicTestApp/CounterComponent.cshtml b/test/testapps/BasicTestApp/CounterComponent.cshtml index 49ece09814..ed58054b8f 100644 --- a/test/testapps/BasicTestApp/CounterComponent.cshtml +++ b/test/testapps/BasicTestApp/CounterComponent.cshtml @@ -1,4 +1,4 @@ -

Counter

+

Counter

Current count: @currentCount

@@ -7,6 +7,11 @@ Toggle click handler registration +@if (handleClicks) +{ +

Listening

+} + @functions { int currentCount = 0; bool handleClicks = true; diff --git a/test/testapps/BasicTestApp/Index.cshtml b/test/testapps/BasicTestApp/Index.cshtml new file mode 100644 index 0000000000..bfa961e665 --- /dev/null +++ b/test/testapps/BasicTestApp/Index.cshtml @@ -0,0 +1,64 @@ +@using Microsoft.AspNetCore.Blazor.RenderTree +
+ Select test: + + + @if (SelectedComponentType != null) + { + @(SelectedComponentType.Name.Replace(".", "/")).cshtml + } +
+
+ + + @((RenderFragment)RenderSelectedComponent) + + +@functions { + string SelectedComponentTypeName { get; set; } = "none"; + + Type SelectedComponentType + => SelectedComponentTypeName == "none" ? null : Type.GetType(SelectedComponentTypeName); + + void RenderSelectedComponent(RenderTreeBuilder builder) + { + if (SelectedComponentType != null) + { + builder.OpenComponent(0, SelectedComponentType); + builder.CloseComponent(); + } + } +} diff --git a/test/testapps/BasicTestApp/InteropComponent.cshtml b/test/testapps/BasicTestApp/InteropComponent.cshtml index baea881e74..0579d9118b 100644 --- a/test/testapps/BasicTestApp/InteropComponent.cshtml +++ b/test/testapps/BasicTestApp/InteropComponent.cshtml @@ -1,5 +1,6 @@ @using Microsoft.JSInterop @using BasicTestApp.InteropTest +@using System.Runtime.InteropServices @@ -65,18 +66,24 @@ public async Task InvokeInteropAsync() { - var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current); + var shouldSupportSyncInterop = RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")); var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123); - var instanceMethodsTarget = new JavaScriptInterop(); Console.WriteLine("Starting interop invocations."); await JSRuntime.Current.InvokeAsync( "jsInteropTests.invokeDotNetInteropMethodsAsync", + shouldSupportSyncInterop, new DotNetObjectRef(testDTOTOPassByRef), new DotNetObjectRef(instanceMethodsTarget)); + + if (shouldSupportSyncInterop) + { + InvokeInProcessInterop(); + } + Console.WriteLine("Showing interop invocation results."); - var collectResults = inProcRuntime.Invoke>("jsInteropTests.collectInteropResults"); + var collectResults = await JSRuntime.Current.InvokeAsync>("jsInteropTests.collectInteropResults"); ReturnValues = collectResults.ToDictionary(kvp => kvp.Key,kvp => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(kvp.Value))); @@ -87,15 +94,6 @@ invocations[interopResult.Key] = interopResultValue; } - try - { - inProcRuntime.Invoke("jsInteropTests.functionThrowsException"); - } - catch (JSException e) - { - ExceptionFromSyncMethod = e; - } - try { await JSRuntime.Current.InvokeAsync("jsInteropTests.asyncFunctionThrowsSyncException"); @@ -120,15 +118,39 @@ { "stringValue", "My string" }, { "testDto", new DotNetObjectRef(passDotNetObjectByRef) }, }; - ReceiveDotNetObjectByRefResult = inProcRuntime.Invoke>("receiveDotNetObjectByRef", passDotNetObjectByRefArg); ReceiveDotNetObjectByRefAsyncResult = await JSRuntime.Current.InvokeAsync>("receiveDotNetObjectByRefAsync", passDotNetObjectByRefArg); - ReceiveDotNetObjectByRefResult["testDto"] = ReceiveDotNetObjectByRefResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different"; ReceiveDotNetObjectByRefAsyncResult["testDto"] = ReceiveDotNetObjectByRefAsyncResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different"; - ReturnValues["returnPrimitive"] = inProcRuntime.Invoke("returnPrimitive").ToString(); ReturnValues["returnPrimitiveAsync"] = (await JSRuntime.Current.InvokeAsync("returnPrimitiveAsync")).ToString(); + if (shouldSupportSyncInterop) + { + ReturnValues["returnPrimitive"] = ((IJSInProcessRuntime)JSRuntime.Current).Invoke("returnPrimitive").ToString(); + } Invocations = invocations; DoneWithInterop = true; } + + public void InvokeInProcessInterop() + { + var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current); + + try + { + inProcRuntime.Invoke("jsInteropTests.functionThrowsException"); + } + catch (JSException e) + { + ExceptionFromSyncMethod = e; + } + + var passDotNetObjectByRef = new TestDTO(99999); + var passDotNetObjectByRefArg = new Dictionary + { + { "stringValue", "My string" }, + { "testDto", new DotNetObjectRef(passDotNetObjectByRef) }, + }; + ReceiveDotNetObjectByRefResult = inProcRuntime.Invoke>("receiveDotNetObjectByRef", passDotNetObjectByRefArg); + ReceiveDotNetObjectByRefResult["testDto"] = ReceiveDotNetObjectByRefResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different"; + } } diff --git a/test/testapps/BasicTestApp/Program.cs b/test/testapps/BasicTestApp/Program.cs index ab5fa9ba07..5e85aca979 100644 --- a/test/testapps/BasicTestApp/Program.cs +++ b/test/testapps/BasicTestApp/Program.cs @@ -1,32 +1,19 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNetCore.Blazor.Browser.Http; -using Microsoft.AspNetCore.Blazor.Browser.Rendering; -using Microsoft.AspNetCore.Blazor.Browser.Services; -using Microsoft.JSInterop; -using System; +using Microsoft.AspNetCore.Blazor.Hosting; namespace BasicTestApp { public class Program { - static void Main(string[] args) + public static void Main(string[] args) { - // Needed because the test server runs on a different port than the client app, - // and we want to test sending/receiving cookies undering this config - BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include; - - // Signal to tests that we're ready - GC.KeepAlive(ActivateMonoJSRuntime.EnsureActivated()); - JSRuntime.Current.InvokeAsync("testReady"); + CreateHostBuilder(args).Build().Run(); } - [JSInvokable(nameof(MountTestComponent))] - public static void MountTestComponent(string componentTypeName) - { - var componentType = Type.GetType(componentTypeName); - new BrowserRenderer().AddComponent(componentType, "app"); - } + public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) => + BlazorWebAssemblyHost.CreateDefaultBuilder() + .UseBlazorStartup(); } } diff --git a/test/testapps/BasicTestApp/Startup.cs b/test/testapps/BasicTestApp/Startup.cs new file mode 100644 index 0000000000..2819c16546 --- /dev/null +++ b/test/testapps/BasicTestApp/Startup.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Blazor.Browser.Http; +using Microsoft.AspNetCore.Blazor.Browser.Services; +using Microsoft.AspNetCore.Blazor.Builder; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Runtime.InteropServices; + +namespace BasicTestApp +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + } + + public void Configure(IBlazorApplicationBuilder app) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"))) + { + // Needed because the test server runs on a different port than the client app, + // and we want to test sending/receiving cookies undering this config + BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include; + + GC.KeepAlive(ActivateMonoJSRuntime.EnsureActivated()); + } + + app.AddComponent("root"); + } + } +} diff --git a/test/testapps/BasicTestApp/wwwroot/index.html b/test/testapps/BasicTestApp/wwwroot/index.html index 07d610309a..a7bbf503e3 100644 --- a/test/testapps/BasicTestApp/wwwroot/index.html +++ b/test/testapps/BasicTestApp/wwwroot/index.html @@ -6,71 +6,26 @@ - - - Loading... - - + Loading... diff --git a/test/testapps/BasicTestApp/wwwroot/js/jsinteroptests.js b/test/testapps/BasicTestApp/wwwroot/js/jsinteroptests.js index 4bbd4aca3b..ef5071509b 100644 --- a/test/testapps/BasicTestApp/wwwroot/js/jsinteroptests.js +++ b/test/testapps/BasicTestApp/wwwroot/js/jsinteroptests.js @@ -4,40 +4,42 @@ var results = {}; var assemblyName = 'BasicTestApp'; -function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarget) { - console.log('Invoking void sync methods.'); - DotNet.invokeMethod(assemblyName, 'VoidParameterless'); - DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef)); - DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef)); - DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef)); - DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef)); - DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef)); - DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef)); - DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef)); - DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef)); +function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetObjectByRef, instanceMethodsTarget) { + if (shouldSupportSyncInterop) { + console.log('Invoking void sync methods.'); + DotNet.invokeMethod(assemblyName, 'VoidParameterless'); + DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef)); + DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef)); + DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef)); + DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef)); + DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef)); + DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef)); + DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef)); + DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef)); - console.log('Invoking returning sync methods.'); - results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray'); - results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef)); - results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef)); - results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef)); - results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef)); - results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef)); - results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef)); - results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef)); - results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8, dotNetObjectByRef)); + console.log('Invoking returning sync methods.'); + results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray'); + results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef)); + results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef)); + results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef)); + results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef)); + results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef)); + results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef)); + results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef)); + results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8, dotNetObjectByRef)); - var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef'); - results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']); + var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef'); + results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']); - var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', { - stringValue: 'My string', - dtoByRef: dotNetObjectByRef - }); - results['instanceMethodThisTypeName'] = instanceMethodResult.thisTypeName; - results['instanceMethodStringValueUpper'] = instanceMethodResult.stringValueUpper; - results['instanceMethodIncomingByRef'] = instanceMethodResult.incomingByRef; - results['instanceMethodOutgoingByRef'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', instanceMethodResult.outgoingByRef); + var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', { + stringValue: 'My string', + dtoByRef: dotNetObjectByRef + }); + results['instanceMethodThisTypeName'] = instanceMethodResult.thisTypeName; + results['instanceMethodStringValueUpper'] = instanceMethodResult.stringValueUpper; + results['instanceMethodIncomingByRef'] = instanceMethodResult.incomingByRef; + results['instanceMethodOutgoingByRef'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', instanceMethodResult.outgoingByRef); + } console.log('Invoking void async methods.'); return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync') @@ -70,8 +72,9 @@ function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarge .then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef))) .then(r => results['result9Async'] = r) .then(() => DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync')) + .then(r => DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', r['Some async instance'])) .then(r => { - results['resultReturnDotNetObjectByRefAsync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', r['Some async instance']); + results['resultReturnDotNetObjectByRefAsync'] = r; }) .then(() => instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', { stringValue: 'My string', @@ -81,13 +84,15 @@ function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarge results['instanceMethodThisTypeNameAsync'] = r.thisTypeName; results['instanceMethodStringValueUpperAsync'] = r.stringValueUpper; results['instanceMethodIncomingByRefAsync'] = r.incomingByRef; - results['instanceMethodOutgoingByRefAsync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', r.outgoingByRef); + return DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', r.outgoingByRef); + }).then(r => { + results['instanceMethodOutgoingByRefAsync'] = r; }) }) .then(() => { console.log('Invoking methods that throw exceptions'); try { - DotNet.invokeMethod(assemblyName, 'ThrowException'); + shouldSupportSyncInterop && DotNet.invokeMethod(assemblyName, 'ThrowException'); } catch (e) { results['ThrowException'] = e.message; } @@ -224,10 +229,18 @@ function receiveDotNetObjectByRef(incomingData) { } function receiveDotNetObjectByRefAsync(incomingData) { - return new Promise(function (resolve, reject) { - setTimeout(function () { - const promiseResult = receiveDotNetObjectByRef(incomingData); - resolve(promiseResult); - }, 100); + const stringValue = incomingData.stringValue; + const testDto = incomingData.testDto; + + // To verify we received a proper reference to testDto, pass it back into .NET + // to have it evaluate something that only .NET can know + return DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', testDto).then(testDtoNonSerializedValue => { + // To show we can return a .NET object by ref anywhere in a complex structure, + // return it among other values + return { + stringValueUpper: stringValue.toUpperCase(), + testDtoNonSerializedValue: testDtoNonSerializedValue, + testDto: testDto + }; }); } diff --git a/test/testapps/TestServer/Startup.cs b/test/testapps/TestServer/Startup.cs index 44cf551b57..62af5e4f89 100644 --- a/test/testapps/TestServer/Startup.cs +++ b/test/testapps/TestServer/Startup.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,6 +22,7 @@ namespace TestServer { options.AddPolicy("AllowAll", _ => { /* Controlled below */ }); }); + services.AddServerSideBlazor(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -34,6 +35,12 @@ namespace TestServer AllowCorsForAnyLocalhostPort(app); app.UseMvc(); + + // Mount the server-side Blazor app on /subdir + app.Map("/subdir", subdirApp => + { + subdirApp.UseServerSideBlazor(); + }); } private static void AllowCorsForAnyLocalhostPort(IApplicationBuilder app) diff --git a/test/testapps/TestServer/TestServer.csproj b/test/testapps/TestServer/TestServer.csproj index 89985dc339..d89eb92294 100644 --- a/test/testapps/TestServer/TestServer.csproj +++ b/test/testapps/TestServer/TestServer.csproj @@ -7,4 +7,9 @@ + + + + +