Run E2E tests for server execution as well as WebAssembly. Fixes several

server-execution bugs uncovered by the E2E tests.
This commit is contained in:
Steve Sanderson 2018-07-18 14:48:54 +01:00
parent 154289ed3d
commit 41fcf65c05
30 changed files with 697 additions and 346 deletions

View File

@ -17,7 +17,7 @@ function boot() {
}); });
connection = new signalR.HubConnectionBuilder() connection = new signalR.HubConnectionBuilder()
.withUrl('/_blazor') .withUrl('_blazor')
.withHubProtocol(new MessagePackHubProtocol()) .withHubProtocol(new MessagePackHubProtocol())
.configureLogging(signalR.LogLevel.Information) .configureLogging(signalR.LogLevel.Information)
.build(); .build();

View File

@ -118,7 +118,8 @@ class OutOfProcessRenderTreeFrameReader implements RenderTreeFrameReader {
} }
elementReferenceCaptureId(frame: RenderTreeFrame) { 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) { componentId(frame: RenderTreeFrame) {
@ -159,14 +160,15 @@ class OutOfProcessStringReader {
} }
readString(index: number): string | null { readString(index: number): string | null {
const stringTableEntry = readInt32LE(this.batchDataUint8, this.stringTableStartIndex + index * stringTableEntryLength); if (index === -1) { // Special value encodes 'null'
if (stringTableEntry === -1) { // Special value encodes 'null'
return null; return null;
} else { } else {
const stringTableEntryPos = readInt32LE(this.batchDataUint8, this.stringTableStartIndex + index * stringTableEntryLength);
// By default, .NET's BinaryWriter gives LEB128-length-prefixed UTF-8 data. // By default, .NET's BinaryWriter gives LEB128-length-prefixed UTF-8 data.
// This is convenient enough to decode in JavaScript. // This is convenient enough to decode in JavaScript.
const numUtf8Bytes = readLEB128(this.batchDataUint8, stringTableEntry); const numUtf8Bytes = readLEB128(this.batchDataUint8, stringTableEntryPos);
const charsStart = stringTableEntry + numLEB128Bytes(numUtf8Bytes); const charsStart = stringTableEntryPos + numLEB128Bytes(numUtf8Bytes);
const utf8Data = new DataView( const utf8Data = new DataView(
this.batchDataUint8.buffer, this.batchDataUint8.buffer,
this.batchDataUint8.byteOffset + charsStart, this.batchDataUint8.byteOffset + charsStart,

View File

@ -138,7 +138,21 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
{ {
case RenderTreeFrameType.Attribute: case RenderTreeFrameType.Attribute:
WriteString(frame.AttributeName); 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); _binaryWriter.Write(frame.AttributeEventHandlerId);
break; break;
case RenderTreeFrameType.Component: case RenderTreeFrameType.Component:

View File

@ -72,14 +72,25 @@ namespace Microsoft.JSInterop
? null ? null
: jsRuntimeBaseInstance.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId); : 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 there was no callId, the caller does not want to be notified about the result
if (callId != null) if (callId != null)
{ {
// Invoke and coerce the result to a Task so the caller can use the same async API // Invoke and coerce the result to a Task so the caller can use the same async API
// for both synchronous and asynchronous methods // for both synchronous and asynchronous methods
var task = syncResult is Task syncResultTask ? syncResultTask : Task.FromResult(syncResult); var task = CoerceToTask(syncResult, syncException);
task.ContinueWith(completedTask => task.ContinueWith(completedTask =>
{ {
try 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) private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson)
{ {
if (targetInstance != null) if (targetInstance != null)

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicTestApp; using BasicTestApp;
@ -11,13 +11,14 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{ {
public class BasicTestAppTestBase : ServerTestBase<DevHostServerFixture<Program>> public class BasicTestAppTestBase : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
{ {
public const string ServerPathBase = "/subdir"; public string ServerPathBase
=> "/subdir" + (_serverFixture.UsingAspNetHost ? "#server" : "");
public BasicTestAppTestBase( public BasicTestAppTestBase(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture, ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
@ -27,19 +28,18 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
protected IWebElement MountTestComponent<TComponent>() where TComponent : IComponent protected IWebElement MountTestComponent<TComponent>() where TComponent : IComponent
{ {
var componentTypeName = typeof(TComponent).FullName; var componentTypeName = typeof(TComponent).FullName;
WaitUntilDotNetRunningInBrowser(); var testSelector = WaitUntilTestSelectorReady();
((IJavaScriptExecutor)Browser).ExecuteScript( testSelector.SelectByValue("none");
$"mountTestComponent('{componentTypeName}')"); testSelector.SelectByValue(componentTypeName);
return Browser.FindElement(By.TagName("app")); return Browser.FindElement(By.TagName("app"));
} }
protected void WaitUntilDotNetRunningInBrowser() protected SelectElement WaitUntilTestSelectorReady()
{ {
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(driver => var elemToFind = By.CssSelector("#test-selector > select");
{ new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
return ((IJavaScriptExecutor)driver) driver => driver.FindElement(elemToFind) != null);
.ExecuteScript("return window.isTestReady;"); return new SelectElement(Browser.FindElement(elemToFind));
});
} }
} }
} }

View File

@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
try try
{ {
var driver = new RemoteWebDriver(opts); var driver = new RemoteWebDriver(opts);
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1);
Browser = driver; Browser = driver;
Logs = new RemoteLogs(driver); Logs = new RemoteLogs(driver);
} }

View File

@ -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<TClientProgram>
: 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<TClientProgram>();
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();
}
}
}

View File

@ -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. // 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; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
: BrowserTestBase, IClassFixture<TServerFixture> : BrowserTestBase, IClassFixture<TServerFixture>
where TServerFixture: ServerFixture where TServerFixture: ServerFixture
{ {
private readonly TServerFixture _serverFixture; protected readonly TServerFixture _serverFixture;
public ServerTestBase(BrowserFixture browserFixture, TServerFixture serverFixture, ITestOutputHelper output) public ServerTestBase(BrowserFixture browserFixture, TServerFixture serverFixture, ITestOutputHelper output)
: base(browserFixture, output) : base(browserFixture, output)
@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
} }
} }
Browser.Navigate().GoToUrl("about:blank");
Browser.Navigate().GoToUrl(absoluteUrl); Browser.Navigate().GoToUrl(absoluteUrl);
} }
} }

View File

@ -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>(T expected, Func<T> actual)
=> WaitAssertCore(() => Assert.Equal(expected, actual()));
public static void True(Func<bool> actual)
=> WaitAssertCore(() => Assert.True(actual()));
public static void False(Func<bool> actual)
=> WaitAssertCore(() => Assert.False(actual()));
public static void Contains(string expectedSubstring, Func<string> actualString)
=> WaitAssertCore(() => Assert.Contains(expectedSubstring, actualString()));
public static void Collection<T>(Func<IEnumerable<T>> actualValues, params Action<T>[] elementInspectors)
=> WaitAssertCore(() => Assert.Collection(actualValues(), elementInspectors));
public static void Empty(Func<IEnumerable> actualValues)
=> WaitAssertCore(() => Assert.Empty(actualValues()));
public static void Single(Func<IEnumerable> 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();
}
}
}
}

View File

@ -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<T> WithServerExecution<T>(this ToggleExecutionModeServerFixture<T> serverFixture)
{
serverFixture.UseAspNetHost(TestServer.Program.BuildWebHost);
return serverFixture;
}
}
}

View File

@ -10,23 +10,20 @@ using System.Linq;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests namespace Microsoft.AspNetCore.Blazor.E2ETest.ServerExecutionTests
{ {
public class ServerSideBlazorTest : ServerTestBase<AspNetSiteServerFixture>, IDisposable public class ServerSideAppTest : ServerTestBase<AspNetSiteServerFixture>
{ {
private readonly AspNetSiteServerFixture _serverFixture; public ServerSideAppTest(
public ServerSideBlazorTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
AspNetSiteServerFixture serverFixture, AspNetSiteServerFixture serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
_serverFixture = serverFixture;
_serverFixture.Environment = AspNetEnvironment.Development; _serverFixture.Environment = AspNetEnvironment.Development;
_serverFixture.BuildWebHostMethod = ServerSideBlazor.Server.Program.BuildWebHost; _serverFixture.BuildWebHostMethod = ServerSideBlazor.Server.Program.BuildWebHost;
Navigate("/", noReload: true); Navigate("/", noReload: false);
WaitUntilLoaded(); WaitUntilLoaded();
} }
@ -58,13 +55,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.LinkText("Counter")).Click(); Browser.FindElement(By.LinkText("Counter")).Click();
// Verify we're now on the counter page, with that nav link (only) highlighted // 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), Assert.Collection(Browser.FindElements(activeNavLinksSelector),
item => Assert.Equal("Counter", item.Text)); item => Assert.Equal("Counter", item.Text));
// Verify we can navigate back to home too // Verify we can navigate back to home too
Browser.FindElement(By.LinkText("Home")).Click(); 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), Assert.Collection(Browser.FindElements(activeNavLinksSelector),
item => Assert.Equal("Home", item.Text)); item => Assert.Equal("Home", item.Text));
} }
@ -74,7 +71,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{ {
// Navigate to "Counter" // Navigate to "Counter"
Browser.FindElement(By.LinkText("Counter")).Click(); 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 // Observe the initial value is zero
var countDisplayElement = Browser.FindElement(By.CssSelector("h1 + p")); var countDisplayElement = Browser.FindElement(By.CssSelector("h1 + p"));
@ -83,17 +80,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Click the button; see it counts // Click the button; see it counts
var button = Browser.FindElement(By.CssSelector(".main button")); var button = Browser.FindElement(By.CssSelector(".main button"));
button.Click(); button.Click();
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
button.Click(); button.Click();
WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
button.Click(); 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() public void HasFetchDataPage()
{ {
// Navigate to "Fetch Data" // Navigate to "Fetch Data"
Browser.FindElement(By.LinkText("Fetch data")).Click(); 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 // Wait until loaded
var tableSelector = By.CssSelector("table.table"); var tableSelector = By.CssSelector("table.table");
@ -115,12 +114,5 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until( new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
driver => driver.FindElement(By.TagName("app")).Text != "Loading..."); 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();
}
} }
} }

View File

@ -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<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerBindTest : BindTest
{
public ServerBindTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerEventBubblingTest : EventBubblingTest
{
public ServerEventBubblingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerEventTest : EventTest
{
public ServerEventTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerInteropTest : InteropTest
{
public ServerInteropTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerRoutingTest : RoutingTest
{
public ServerRoutingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
}

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicTestApp.HttpClientTest; using BasicTestApp.HttpClientTest;
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public BinaryHttpClientTest( public BinaryHttpClientTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<BasicTestApp.Program> devHostServerFixture, ToggleExecutionModeServerFixture<BasicTestApp.Program> devHostServerFixture,
AspNetSiteServerFixture apiServerFixture, AspNetSiteServerFixture apiServerFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, devHostServerFixture, output) : base(browserFixture, devHostServerFixture, output)

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicTestApp; using BasicTestApp;
@ -14,12 +14,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public class BindTest : BasicTestAppTestBase public class BindTest : BasicTestAppTestBase
{ {
public BindTest( public BindTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture, ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, 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<BindCasesComponent>(); MountTestComponent<BindCasesComponent>();
} }
@ -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, boundValue.Text); // Doesn't update until change event
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
target.SendKeys("\t"); target.SendKeys("\t");
Assert.Equal("Changed value", boundValue.Text); WaitAssert.Equal("Changed value", () => boundValue.Text);
Assert.Equal("Changed value", mirrorValue.GetAttribute("value")); Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
// Remove the value altogether // Remove the value altogether
setNullButton.Click(); 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, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); 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 // Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear(); target.Clear();
target.SendKeys("Changed value\t"); target.SendKeys("Changed value\t");
Assert.Equal("Changed value", boundValue.Text); WaitAssert.Equal("Changed value", () => boundValue.Text);
Assert.Equal("Changed value", mirrorValue.GetAttribute("value")); Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
// Remove the value altogether // Remove the value altogether
setNullButton.Click(); 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, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value")); Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
} }
@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
target.SendKeys("Changed value"); target.SendKeys("Changed value");
Assert.Equal(string.Empty, boundValue.Text); // Don't update as there's no change event fired yet. Assert.Equal(string.Empty, boundValue.Text); // Don't update as there's no change event fired yet.
target.SendKeys("\t"); target.SendKeys("\t");
Assert.Equal("Changed value", boundValue.Text); WaitAssert.Equal("Changed value", () => boundValue.Text);
} }
[Fact] [Fact]
@ -99,7 +100,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated // Modify target; verify value is updated
target.Clear(); target.Clear();
target.SendKeys("Changed value\t"); target.SendKeys("Changed value\t");
Assert.Equal("Changed value", boundValue.Text); WaitAssert.Equal("Changed value", () => boundValue.Text);
} }
[Fact] [Fact]
@ -113,12 +114,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated // Modify target; verify value is updated
target.Click(); target.Click();
Assert.True(target.Selected); WaitAssert.True(() => target.Selected);
Assert.Equal("True", boundValue.Text); Assert.Equal("True", boundValue.Text);
// Modify data; verify checkbox is updated // Modify data; verify checkbox is updated
invertButton.Click(); invertButton.Click();
Assert.False(target.Selected); WaitAssert.False(() => target.Selected);
Assert.Equal("False", boundValue.Text); Assert.Equal("False", boundValue.Text);
} }
@ -133,12 +134,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated // Modify target; verify value is updated
target.Click(); target.Click();
Assert.False(target.Selected); WaitAssert.False(() => target.Selected);
Assert.Equal("False", boundValue.Text); Assert.Equal("False", boundValue.Text);
// Modify data; verify checkbox is updated // Modify data; verify checkbox is updated
invertButton.Click(); invertButton.Click();
Assert.True(target.Selected); WaitAssert.True(() => target.Selected);
Assert.Equal("True", boundValue.Text); Assert.Equal("True", boundValue.Text);
} }
@ -152,13 +153,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated // Modify target; verify value is updated
target.SelectByText("Third choice"); target.SelectByText("Third choice");
Assert.Equal("Third", boundValue.Text); WaitAssert.Equal("Third", () => boundValue.Text);
// Also verify we can add and select new options atomically // Also verify we can add and select new options atomically
// Don't move this into a separate test, because then the previous assertions // 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) // would be dependent on test execution order (or would require a full page reload)
Browser.FindElement(By.Id("select-box-add-option")).Click(); 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); 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 // Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear(); target.Clear();
target.SendKeys("42\t"); target.SendKeys("42\t");
Assert.Equal("42", boundValue.Text); WaitAssert.Equal("42", () => boundValue.Text);
Assert.Equal("42", mirrorValue.GetAttribute("value")); 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 // Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear(); target.Clear();
target.SendKeys("-3000000000\t"); target.SendKeys("-3000000000\t");
Assert.Equal("-3000000000", boundValue.Text); WaitAssert.Equal("-3000000000", () => boundValue.Text);
Assert.Equal("-3000000000", mirrorValue.GetAttribute("value")); 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 // Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear(); target.Clear();
target.SendKeys("-3.141\t"); target.SendKeys("-3.141\t");
Assert.Equal("-3.141", boundValue.Text); WaitAssert.Equal("-3.141", () => boundValue.Text);
Assert.Equal("-3.141", mirrorValue.GetAttribute("value")); 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 // Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear(); target.Clear();
target.SendKeys("-3.14159265359\t"); target.SendKeys("-3.14159265359\t");
Assert.Equal("-3.14159265359", boundValue.Text); WaitAssert.Equal("-3.14159265359", () => boundValue.Text);
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value")); Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated // Modify target; verify value is updated and that textboxes linked to the same data are updated
// Double shouldn't preserve trailing zeros // Double shouldn't preserve trailing zeros
target.Clear(); target.Clear();
target.SendKeys("0.010\t"); target.SendKeys("0.010\t");
Assert.Equal("0.01", boundValue.Text); WaitAssert.Equal("0.01", () => boundValue.Text);
Assert.Equal("0.01", mirrorValue.GetAttribute("value")); Assert.Equal("0.01", mirrorValue.GetAttribute("value"));
} }
@ -251,7 +252,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Decimal should preserve trailing zeros // Decimal should preserve trailing zeros
target.Clear(); target.Clear();
target.SendKeys("0.010\t"); target.SendKeys("0.010\t");
Assert.Equal("0.010", boundValue.Text); WaitAssert.Equal("0.010", () => boundValue.Text);
Assert.Equal("0.010", mirrorValue.GetAttribute("value")); Assert.Equal("0.010", mirrorValue.GetAttribute("value"));
} }
} }

View File

@ -11,6 +11,7 @@ using BasicTestApp.HierarchicalImportsTest.Subdir;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium; using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -20,11 +21,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{ {
public ComponentRenderingTest( public ComponentRenderingTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture, ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
Navigate(ServerPathBase, noReload: true); Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
} }
[Fact] [Fact]
@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking button increments count // Clicking button increments count
appElement.FindElement(By.TagName("button")).Click(); appElement.FindElement(By.TagName("button")).Click();
Assert.Equal("Current count: 1", countDisplayElement.Text); WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
} }
[Fact] [Fact]
@ -84,11 +85,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking 'tick' changes the state, and starts a task // Clicking 'tick' changes the state, and starts a task
appElement.FindElement(By.Id("tick")).Click(); 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 // Clicking 'tock' completes the task, which updates the state
appElement.FindElement(By.Id("tock")).Click(); appElement.FindElement(By.Id("tock")).Click();
Assert.Equal("Stopped", stateElement.Text); WaitAssert.Equal("Stopped", () => stateElement.Text);
} }
[Fact] [Fact]
@ -103,12 +104,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Typing adds element // Typing adds element
inputElement.SendKeys("a"); inputElement.SendKeys("a");
Assert.Collection(liElements(), WaitAssert.Collection(liElements,
li => Assert.Equal("a", li.Text)); li => Assert.Equal("a", li.Text));
// Typing again adds another element // Typing again adds another element
inputElement.SendKeys("b"); inputElement.SendKeys("b");
Assert.Collection(liElements(), WaitAssert.Collection(liElements,
li => Assert.Equal("a", li.Text), li => Assert.Equal("a", li.Text),
li => Assert.Equal("b", 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 // Initial count is zero; clicking button increments count
Assert.Equal("Current count: 0", countDisplayElement.Text); Assert.Equal("Current count: 0", countDisplayElement.Text);
incrementButton.Click(); incrementButton.Click();
Assert.Equal("Current count: 1", countDisplayElement.Text); WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
// We can remove an event handler // We can remove an event handler
toggleClickHandlerCheckbox.Click(); toggleClickHandlerCheckbox.Click();
WaitAssert.Empty(() => appElement.FindElements(By.Id("listening-message")));
incrementButton.Click(); incrementButton.Click();
Assert.Equal("Current count: 1", countDisplayElement.Text); WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
// We can add an event handler // We can add an event handler
toggleClickHandlerCheckbox.Click(); toggleClickHandlerCheckbox.Click();
appElement.FindElement(By.Id("listening-message"));
incrementButton.Click(); incrementButton.Click();
Assert.Equal("Current count: 2", countDisplayElement.Text); WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
} }
[Fact] [Fact]
@ -164,7 +167,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking increments count in child component // Clicking increments count in child component
appElement.FindElement(By.TagName("button")).Click(); appElement.FindElement(By.TagName("button")).Click();
Assert.Equal("Current count: 1", counterDisplay.Text); WaitAssert.Equal("Current count: 1", () => counterDisplay.Text);
} }
[Fact] [Fact]
@ -179,7 +182,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking increments count in child element // Clicking increments count in child element
appElement.FindElement(By.TagName("button")).Click(); appElement.FindElement(By.TagName("button")).Click();
Assert.Equal("1", messageElementInChild.Text); WaitAssert.Equal("1", () => messageElementInChild.Text);
} }
[Fact] [Fact]
@ -192,12 +195,22 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Func<IEnumerable<IWebElement>> childComponentWrappers = () => appElement.FindElements(By.TagName("p")); Func<IEnumerable<IWebElement>> childComponentWrappers = () => appElement.FindElements(By.TagName("p"));
Assert.Empty(childComponentWrappers()); Assert.Empty(childComponentWrappers());
// Click to add some child components // Click to add/remove some child components
addButton.Click(); addButton.Click();
WaitAssert.Collection(childComponentWrappers,
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
addButton.Click(); 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(); removeButton.Click();
WaitAssert.Collection(childComponentWrappers,
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
addButton.Click(); addButton.Click();
Assert.Collection(childComponentWrappers(), WaitAssert.Collection(childComponentWrappers,
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text), elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
elem => Assert.Equal("Child 3", 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 // When property changes, child is renotified before rerender
incrementButton.Click(); incrementButton.Click();
Assert.Equal("You supplied: 101", suppliedValueElement.Text); WaitAssert.Equal("You supplied: 101", () => suppliedValueElement.Text);
Assert.Equal("I computed: 202", computedValueElement.Text); Assert.Equal("I computed: 202", computedValueElement.Text);
} }
@ -225,8 +238,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Initially, the region isn't shown // Initially, the region isn't shown
var appElement = MountTestComponent<RenderFragmentToggler>(); var appElement = MountTestComponent<RenderFragmentToggler>();
var originalButton = appElement.FindElement(By.TagName("button")); var originalButton = appElement.FindElement(By.TagName("button"));
var fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]")); Func<IEnumerable<IWebElement>> fragmentElements = () => appElement.FindElements(By.CssSelector("p[name=fragment-element]"));
Assert.Empty(fragmentElements); Assert.Empty(fragmentElements());
// The JS-side DOM builder handles regions correctly, placing elements // The JS-side DOM builder handles regions correctly, placing elements
// after the region after the corresponding 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 // When we click the button, the region is shown
originalButton.Click(); originalButton.Click();
fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]")); WaitAssert.Single(fragmentElements);
Assert.Single(fragmentElements);
// The button itself was preserved, so we can click it again and see the effect // The button itself was preserved, so we can click it again and see the effect
originalButton.Click(); originalButton.Click();
fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]")); WaitAssert.Empty(fragmentElements);
Assert.Empty(fragmentElements);
} }
[Fact] [Fact]
@ -263,11 +274,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// .NET code access to browser APIs // .NET code access to browser APIs
var showPromptButton = appElement.FindElements(By.TagName("button")).First(); var showPromptButton = appElement.FindElements(By.TagName("button")).First();
showPromptButton.Click(); showPromptButton.Click();
var modal = Browser.SwitchTo().Alert();
var modal = new WebDriverWait(Browser, TimeSpan.FromSeconds(3))
.Until(SwitchToAlert);
modal.SendKeys("Some value from test"); modal.SendKeys("Some value from test");
modal.Accept(); modal.Accept();
var promptResult = appElement.FindElement(By.TagName("strong")); 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 // NuGet packages can also embed entire Blazor components (themselves
// authored as Razor files), including static content. The CSS value // 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")); var externalComponentButton = specialStyleDiv.FindElement(By.TagName("button"));
Assert.Equal("Click me", externalComponentButton.Text); Assert.Equal("Click me", externalComponentButton.Text);
externalComponentButton.Click(); externalComponentButton.Click();
Assert.Equal("It works", externalComponentButton.Text); WaitAssert.Equal("It works", () => externalComponentButton.Text);
} }
[Fact] [Fact]
@ -324,9 +337,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Assert.Equal(string.Empty, inputElement.GetAttribute("value")); Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
buttonElement.Click(); buttonElement.Click();
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value")); WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
buttonElement.Click(); buttonElement.Click();
Assert.Equal("Clicks: 2", inputElement.GetAttribute("value")); WaitAssert.Equal("Clicks: 2", () => inputElement.GetAttribute("value"));
} }
[Fact] [Fact]
@ -342,17 +355,16 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Remove the captured element // Remove the captured element
checkbox.Click(); checkbox.Click();
Assert.Empty(appElement.FindElements(By.Id("capturedElement"))); WaitAssert.Empty(() => appElement.FindElements(By.Id("capturedElement")));
// Re-add it; observe it starts empty again // Re-add it; observe it starts empty again
checkbox.Click(); checkbox.Click();
var inputElement = appElement.FindElement(By.Id("capturedElement")); var inputElement = appElement.FindElement(By.Id("capturedElement"));
Assert.NotNull(inputElement);
Assert.Equal(string.Empty, inputElement.GetAttribute("value")); Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
// See that the capture variable was automatically updated to reference the new instance // See that the capture variable was automatically updated to reference the new instance
buttonElement.Click(); buttonElement.Click();
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value")); WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
} }
[Fact] [Fact]
@ -363,26 +375,27 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var currentCountTextSelector = By.CssSelector("#child-component p:first-of-type"); var currentCountTextSelector = By.CssSelector("#child-component p:first-of-type");
var resetButton = appElement.FindElement(By.Id("reset-child")); var resetButton = appElement.FindElement(By.Id("reset-child"));
var toggleChildCheckbox = appElement.FindElement(By.Id("toggle-child")); var toggleChildCheckbox = appElement.FindElement(By.Id("toggle-child"));
Func<string> currentCountText = () => appElement.FindElement(currentCountTextSelector).Text;
// Verify the reference was captured initially // Verify the reference was captured initially
appElement.FindElement(incrementButtonSelector).Click(); appElement.FindElement(incrementButtonSelector).Click();
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text); WaitAssert.Equal("Current count: 1", currentCountText);
resetButton.Click(); resetButton.Click();
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text); WaitAssert.Equal("Current count: 0", currentCountText);
appElement.FindElement(incrementButtonSelector).Click(); 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 // Remove and re-add a new instance of the child, checking the text was reset
toggleChildCheckbox.Click(); toggleChildCheckbox.Click();
Assert.Empty(appElement.FindElements(incrementButtonSelector)); WaitAssert.Empty(() => appElement.FindElements(incrementButtonSelector));
toggleChildCheckbox.Click(); toggleChildCheckbox.Click();
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text); WaitAssert.Equal("Current count: 0", currentCountText);
// Verify we have a new working reference // Verify we have a new working reference
appElement.FindElement(incrementButtonSelector).Click(); appElement.FindElement(incrementButtonSelector).Click();
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text); WaitAssert.Equal("Current count: 1", currentCountText);
resetButton.Click(); resetButton.Click();
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text); WaitAssert.Equal("Current count: 0", currentCountText);
} }
[Fact] [Fact]
@ -392,5 +405,17 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var inputElement = appElement.FindElement(By.TagName("input")); var inputElement = appElement.FindElement(By.TagName("input"));
Assert.Equal("Value set after render", inputElement.GetAttribute("value")); Assert.Equal("Value set after render", inputElement.GetAttribute("value"));
} }
static IAlert SwitchToAlert(IWebDriver driver)
{
try
{
return driver.SwitchTo().Alert();
}
catch (NoAlertPresentException)
{
return null;
}
}
} }
} }

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicTestApp; 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. // the one that doesn't match the 'bubbles' flag on the received event object.
public EventBubblingTest( public EventBubblingTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture, ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
Navigate(ServerPathBase, noReload: true); Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
MountTestComponent<EventBubblingComponent>(); MountTestComponent<EventBubblingComponent>();
} }
@ -35,9 +35,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("button-with-onclick")).Click(); Browser.FindElement(By.Id("button-with-onclick")).Click();
// Triggers event on target and ancestors with handler in upwards direction // Triggers event on target and ancestors with handler in upwards direction
Assert.Equal( WaitAssert.Equal(
new[] { "target onclick", "parent onclick" }, new[] { "target onclick", "parent onclick" },
GetLogLines()); GetLogLines);
} }
[Fact] [Fact]
@ -46,9 +46,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("button-without-onclick")).Click(); Browser.FindElement(By.Id("button-without-onclick")).Click();
// Triggers event on ancestors with handler in upwards direction // Triggers event on ancestors with handler in upwards direction
Assert.Equal( WaitAssert.Equal(
new[] { "parent onclick" }, new[] { "parent onclick" },
GetLogLines()); GetLogLines);
} }
[Fact] [Fact]
@ -57,9 +57,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
TriggerCustomBubblingEvent("element-with-onsneeze", "sneeze"); TriggerCustomBubblingEvent("element-with-onsneeze", "sneeze");
// Triggers event on target and ancestors with handler in upwards direction // Triggers event on target and ancestors with handler in upwards direction
Assert.Equal( WaitAssert.Equal(
new[] { "target onsneeze", "parent onsneeze" }, new[] { "target onsneeze", "parent onsneeze" },
GetLogLines()); GetLogLines);
} }
[Fact] [Fact]
@ -68,9 +68,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
TriggerCustomBubblingEvent("element-without-onsneeze", "sneeze"); TriggerCustomBubblingEvent("element-without-onsneeze", "sneeze");
// Triggers event on ancestors with handler in upwards direction // Triggers event on ancestors with handler in upwards direction
Assert.Equal( WaitAssert.Equal(
new[] { "parent onsneeze" }, new[] { "parent onsneeze" },
GetLogLines()); GetLogLines);
} }
[Fact] [Fact]
@ -79,7 +79,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("input-with-onfocus")).Click(); Browser.FindElement(By.Id("input-with-onfocus")).Click();
// Triggers event only on target, not other ancestors with event handler // Triggers event only on target, not other ancestors with event handler
Assert.Equal(new[] { "target onfocus" }, GetLogLines()); WaitAssert.Equal(
new[] { "target onfocus" },
GetLogLines);
} }
[Fact] [Fact]
@ -88,7 +90,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("input-without-onfocus")).Click(); Browser.FindElement(By.Id("input-without-onfocus")).Click();
// Triggers no event // Triggers no event
Assert.Empty(GetLogLines()); WaitAssert.Empty(GetLogLines);
} }
private string[] GetLogLines() private string[] GetLogLines()

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using BasicTestApp; using BasicTestApp;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium; using OpenQA.Selenium;
using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -16,8 +14,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public class EventTest : BasicTestAppTestBase public class EventTest : BasicTestAppTestBase
{ {
public EventTest( public EventTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture, ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
@ -38,13 +36,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Focus the target, verify onfocusin is fired // Focus the target, verify onfocusin is fired
input.Click(); input.Click();
Assert.Equal("onfocus,onfocusin,", output.Text); WaitAssert.Equal("onfocus,onfocusin,", () => output.Text);
// Focus something else, verify onfocusout is also fired // Focus something else, verify onfocusout is also fired
var other = Browser.FindElement(By.Id("other")); var other = Browser.FindElement(By.Id("other"));
other.Click(); other.Click();
Assert.Equal("onfocus,onfocusin,onblur,onfocusout,", output.Text); WaitAssert.Equal("onfocus,onfocusin,onblur,onfocusout,", () => output.Text);
} }
[Fact] [Fact]
@ -65,7 +63,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
.MoveToElement(other); .MoveToElement(other);
actions.Perform(); actions.Perform();
Assert.Equal("onmouseover,onmouseout,", output.Text); WaitAssert.Equal("onmouseover,onmouseout,", () => output.Text);
} }
[Fact] [Fact]
@ -84,7 +82,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
.MoveToElement(input, 10, 10); .MoveToElement(input, 10, 10);
actions.Perform(); actions.Perform();
Assert.Contains("onmousemove,", output.Text); WaitAssert.Contains("onmousemove,", () => output.Text);
} }
[Fact] [Fact]
@ -103,12 +101,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var actions = new Actions(Browser).ClickAndHold(input); var actions = new Actions(Browser).ClickAndHold(input);
actions.Perform(); actions.Perform();
Assert.Equal("onmousedown,", output.Text); WaitAssert.Equal("onmousedown,", () => output.Text);
actions = new Actions(Browser).Release(input); actions = new Actions(Browser).Release(input);
actions.Perform(); actions.Perform();
Assert.Equal("onmousedown,onmouseup,", output.Text); WaitAssert.Equal("onmousedown,onmouseup,", () => output.Text);
} }
} }
} }

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using BasicTestApp.HttpClientTest; using BasicTestApp.HttpClientTest;
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public HttpClientTest( public HttpClientTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<BasicTestApp.Program> devHostServerFixture, ToggleExecutionModeServerFixture<BasicTestApp.Program> devHostServerFixture,
AspNetSiteServerFixture apiServerFixture, AspNetSiteServerFixture apiServerFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, devHostServerFixture, output) : base(browserFixture, devHostServerFixture, output)

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{ {
public InteropTest( public InteropTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture, ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
@ -26,7 +26,42 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public void CanInvokeDotNetMethods() public void CanInvokeDotNetMethods()
{ {
// Arrange // Arrange
var expectedValues = new Dictionary<string, string> var expectedAsyncValues = new Dictionary<string, string>
{
["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<string, string>
{ {
["VoidParameterless"] = "[]", ["VoidParameterless"] = "[]",
["VoidWithOneParameter"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", ["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]", ["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]]", ["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}]", ["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]", ["result1"] = @"[0.1,0.2]",
["result2"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]", ["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]", ["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]", ["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]]", ["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}]", ["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!", ["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!", ["ExceptionFromSyncMethod"] = "Function threw an exception!",
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
["resultReturnDotNetObjectByRefSync"] = "1000", ["resultReturnDotNetObjectByRefSync"] = "1000",
["resultReturnDotNetObjectByRefAsync"] = "1001",
["instanceMethodThisTypeName"] = @"""JavaScriptInterop""", ["instanceMethodThisTypeName"] = @"""JavaScriptInterop""",
["instanceMethodStringValueUpper"] = @"""MY STRING""", ["instanceMethodStringValueUpper"] = @"""MY STRING""",
["instanceMethodIncomingByRef"] = "123", ["instanceMethodIncomingByRef"] = "123",
["instanceMethodOutgoingByRef"] = "1234", ["instanceMethodOutgoingByRef"] = "1234",
["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""",
["instanceMethodStringValueUpperAsync"] = @"""MY STRING""",
["instanceMethodIncomingByRefAsync"] = "123",
["instanceMethodOutgoingByRefAsync"] = "1234",
["stringValueUpperSync"] = "MY STRING", ["stringValueUpperSync"] = "MY STRING",
["testDtoNonSerializedValueSync"] = "99999", ["testDtoNonSerializedValueSync"] = "99999",
["testDtoSync"] = "Same", ["testDtoSync"] = "Same",
["stringValueUpperAsync"] = "MY STRING",
["testDtoNonSerializedValueAsync"] = "99999",
["testDtoAsync"] = "Same",
["returnPrimitive"] = "123", ["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<string, string>(); var actualValues = new Dictionary<string, string>();
// Act // Act

View File

@ -15,25 +15,22 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{ {
public class RoutingTest : BasicTestAppTestBase, IDisposable public class RoutingTest : BasicTestAppTestBase
{ {
private readonly ServerFixture _server;
public RoutingTest( public RoutingTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture, ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
_server = serverFixture; Navigate(ServerPathBase, noReload: false);
Navigate(ServerPathBase, noReload: true); WaitUntilTestSelectorReady();
WaitUntilDotNetRunningInBrowser();
} }
[Fact] [Fact]
public void CanArriveAtDefaultPage() public void CanArriveAtDefaultPage()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); 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. // 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 // But in case they don't want to, we need to handle it the same as if the URL does
// have a trailing slash. // have a trailing slash.
SetUrlViaPushState($"{ServerPathBase}"); SetUrlViaPushState("");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); 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] [Fact]
public void CanArriveAtPageWithParameters() public void CanArriveAtPageWithParameters()
{ {
SetUrlViaPushState($"{ServerPathBase}/WithParameters/Name/Ghi/LastName/O'Jkl"); SetUrlViaPushState("/WithParameters/Name/Ghi/LastName/O'Jkl");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
Assert.Equal("Your full name is Ghi O'Jkl.", app.FindElement(By.Id("test-info")).Text); 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] [Fact]
public void CanArriveAtNonDefaultPage() public void CanArriveAtNonDefaultPage()
{ {
SetUrlViaPushState($"{ServerPathBase}/Other"); SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
@ -77,11 +74,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact] [Fact]
public void CanFollowLinkToOtherPage() public void CanFollowLinkToOtherPage()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other")).Click(); 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)"); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
} }
@ -93,20 +90,20 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
try try
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
var button = app.FindElement(By.LinkText("Other")); var button = app.FindElement(By.LinkText("Other"));
new Actions(Browser).KeyDown(key).Click(button).Build().Perform(); new Actions(Browser).KeyDown(key).Click(button).Build().Perform();
Assert.Equal(2, Browser.WindowHandles.Count); WaitAssert.Equal(2, () => Browser.WindowHandles.Count);
} }
finally finally
{ {
// Leaving the ctrl key up // Leaving the ctrl key up
new Actions(Browser).KeyUp(key).Build().Perform(); new Actions(Browser).KeyUp(key).Build().Perform();
// Closing newly opened windows if a new one was opened // Closing newly opened windows if a new one was opened
while (Browser.WindowHandles.Count > 1) while (Browser.WindowHandles.Count > 1)
{ {
@ -123,7 +120,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact] [Fact]
public void CanFollowLinkToOtherPageDoesNotOpenNewWindow() public void CanFollowLinkToOtherPageDoesNotOpenNewWindow()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
@ -135,106 +132,106 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact] [Fact]
public void CanFollowLinkToOtherPageWithBaseRelativeUrl() public void CanFollowLinkToOtherPageWithBaseRelativeUrl()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click(); 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)"); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
} }
[Fact] [Fact]
public void CanFollowLinkToEmptyStringHrefAsBaseRelativeUrl() public void CanFollowLinkToEmptyStringHrefAsBaseRelativeUrl()
{ {
SetUrlViaPushState($"{ServerPathBase}/Other"); SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default with base-relative URL (matches all)")).Click(); 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)"); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
} }
[Fact] [Fact]
public void CanFollowLinkToPageWithParameters() public void CanFollowLinkToPageWithParameters()
{ {
SetUrlViaPushState($"{ServerPathBase}/Other"); SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("With parameters")).Click(); 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"); AssertHighlightedLinks("With parameters");
} }
[Fact] [Fact]
public void CanFollowLinkToDefaultPage() public void CanFollowLinkToDefaultPage()
{ {
SetUrlViaPushState($"{ServerPathBase}/Other"); SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default (matches all)")).Click(); 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)"); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
} }
[Fact] [Fact]
public void CanFollowLinkToOtherPageWithQueryString() public void CanFollowLinkToOtherPageWithQueryString()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other with query")).Click(); 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"); AssertHighlightedLinks("Other", "Other with query");
} }
[Fact] [Fact]
public void CanFollowLinkToDefaultPageWithQueryString() public void CanFollowLinkToDefaultPageWithQueryString()
{ {
SetUrlViaPushState($"{ServerPathBase}/Other"); SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default with query")).Click(); 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"); AssertHighlightedLinks("Default with query");
} }
[Fact] [Fact]
public void CanFollowLinkToOtherPageWithHash() public void CanFollowLinkToOtherPageWithHash()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other with hash")).Click(); 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"); AssertHighlightedLinks("Other", "Other with hash");
} }
[Fact] [Fact]
public void CanFollowLinkToDefaultPageWithHash() public void CanFollowLinkToDefaultPageWithHash()
{ {
SetUrlViaPushState($"{ServerPathBase}/Other"); SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default with hash")).Click(); 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"); AssertHighlightedLinks("Default with hash");
} }
[Fact] [Fact]
public void CanNavigateProgrammatically() public void CanNavigateProgrammatically()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
app.FindElement(By.TagName("button")).Click(); 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)"); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
} }
[Fact] [Fact]
public void ClickingAnchorWithNoHrefShouldNotNavigate() public void ClickingAnchorWithNoHrefShouldNotNavigate()
{ {
SetUrlViaPushState($"{ServerPathBase}/"); SetUrlViaPushState("/");
var initialUrl = Browser.Url; var initialUrl = Browser.Url;
var app = MountTestComponent<TestRouter>(); var app = MountTestComponent<TestRouter>();
@ -244,25 +241,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
} }
public void Dispose()
{
// Clear any existing state
SetUrlViaPushState(ServerPathBase);
MountTestComponent<TextOnlyComponent>();
}
private void SetUrlViaPushState(string relativeUri) private void SetUrlViaPushState(string relativeUri)
{ {
var pathBaseWithoutHash = ServerPathBase.Split('#')[0];
var jsExecutor = (IJavaScriptExecutor)Browser; 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("'", "\\'")}')"); jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')");
} }
private void AssertHighlightedLinks(params string[] linkTexts) private void AssertHighlightedLinks(params string[] linkTexts)
{ {
var actual = Browser.FindElements(By.CssSelector("a.active")); WaitAssert.Equal(linkTexts, () => Browser
var actualTexts = actual.Select(x => x.Text); .FindElements(By.CssSelector("a.active"))
Assert.Equal(linkTexts, actualTexts); .Select(x => x.Text));
} }
} }
} }

View File

@ -15,15 +15,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public class StandaloneAppTest public class StandaloneAppTest
: ServerTestBase<DevHostServerFixture<StandaloneApp.Program>>, IDisposable : ServerTestBase<DevHostServerFixture<StandaloneApp.Program>>, IDisposable
{ {
private readonly ServerFixture _serverFixture;
public StandaloneAppTest( public StandaloneAppTest(
BrowserFixture browserFixture, BrowserFixture browserFixture,
DevHostServerFixture<StandaloneApp.Program> serverFixture, DevHostServerFixture<StandaloneApp.Program> serverFixture,
ITestOutputHelper output) ITestOutputHelper output)
: base(browserFixture, serverFixture, output) : base(browserFixture, serverFixture, output)
{ {
_serverFixture = serverFixture;
Navigate("/", noReload: true); Navigate("/", noReload: true);
WaitUntilLoaded(); WaitUntilLoaded();
} }

View File

@ -1,4 +1,4 @@
<h1>Counter</h1> <h1>Counter</h1>
<p>Current count: @currentCount</p> <p>Current count: @currentCount</p>
<p><button onclick="@((handleClicks ? (Action)IncrementCount : null))">Click me</button></p> <p><button onclick="@((handleClicks ? (Action)IncrementCount : null))">Click me</button></p>
@ -7,6 +7,11 @@
Toggle click handler registration Toggle click handler registration
</label> </label>
@if (handleClicks)
{
<p id="listening-message">Listening</p>
}
@functions { @functions {
int currentCount = 0; int currentCount = 0;
bool handleClicks = true; bool handleClicks = true;

View File

@ -0,0 +1,64 @@
@using Microsoft.AspNetCore.Blazor.RenderTree
<div id="test-selector">
Select test:
<select bind=@SelectedComponentTypeName>
<option value="none">Choose...</option>
<option value="BasicTestApp.InteropComponent">Interop component</option>
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
<option value="BasicTestApp.CounterComponent">Counter</option>
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
<option value="BasicTestApp.FocusEventComponent">Focus events</option>
<option value="BasicTestApp.KeyPressEventComponent">Key press event</option>
<option value="BasicTestApp.MouseEventComponent">Mouse events</option>
<option value="BasicTestApp.TouchEventComponent">Touch events</option>
<option value="BasicTestApp.ParentChildComponent">Parent component with child</option>
<option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option>
<option value="BasicTestApp.RedTextComponent">Red text</option>
<option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option>
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
<option value="BasicTestApp.HttpClientTest.HttpRequestsComponent">HttpClient tester</option>
<option value="BasicTestApp.HttpClientTest.BinaryHttpRequestsComponent">Binary HttpClient tester</option>
<option value="BasicTestApp.HttpClientTest.CookieCounterComponent">HttpClient cookies</option>
<option value="BasicTestApp.BindCasesComponent">bind cases</option>
<option value="BasicTestApp.DataDashComponent">data-* attribute rendering</option>
<option value="BasicTestApp.ExternalContentPackage">External content package</option>
<option value="BasicTestApp.SvgComponent">SVG</option>
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
<option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option>
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
<option value="BasicTestApp.AfterRenderInteropComponent">After-render interop component</option>
<option value="BasicTestApp.EventCasesComponent">Event cases</option>
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
</select>
@if (SelectedComponentType != null)
{
<span id="source-info"><code><tt>@(SelectedComponentType.Name.Replace(".", "/")).cshtml</tt></code></span>
}
<hr />
</div>
<app>
@((RenderFragment)RenderSelectedComponent)
</app>
@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();
}
}
}

View File

@ -1,5 +1,6 @@
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using BasicTestApp.InteropTest @using BasicTestApp.InteropTest
@using System.Runtime.InteropServices
<button id="btn-interop" onclick="@InvokeInteropAsync">Invoke interop!</button> <button id="btn-interop" onclick="@InvokeInteropAsync">Invoke interop!</button>
@ -65,18 +66,24 @@
public async Task InvokeInteropAsync() public async Task InvokeInteropAsync()
{ {
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current); var shouldSupportSyncInterop = RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"));
var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123); var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123);
var instanceMethodsTarget = new JavaScriptInterop(); var instanceMethodsTarget = new JavaScriptInterop();
Console.WriteLine("Starting interop invocations."); Console.WriteLine("Starting interop invocations.");
await JSRuntime.Current.InvokeAsync<object>( await JSRuntime.Current.InvokeAsync<object>(
"jsInteropTests.invokeDotNetInteropMethodsAsync", "jsInteropTests.invokeDotNetInteropMethodsAsync",
shouldSupportSyncInterop,
new DotNetObjectRef(testDTOTOPassByRef), new DotNetObjectRef(testDTOTOPassByRef),
new DotNetObjectRef(instanceMethodsTarget)); new DotNetObjectRef(instanceMethodsTarget));
if (shouldSupportSyncInterop)
{
InvokeInProcessInterop();
}
Console.WriteLine("Showing interop invocation results."); Console.WriteLine("Showing interop invocation results.");
var collectResults = inProcRuntime.Invoke<Dictionary<string,string>>("jsInteropTests.collectInteropResults"); var collectResults = await JSRuntime.Current.InvokeAsync<Dictionary<string,string>>("jsInteropTests.collectInteropResults");
ReturnValues = collectResults.ToDictionary(kvp => kvp.Key,kvp => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(kvp.Value))); ReturnValues = collectResults.ToDictionary(kvp => kvp.Key,kvp => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(kvp.Value)));
@ -87,15 +94,6 @@
invocations[interopResult.Key] = interopResultValue; invocations[interopResult.Key] = interopResultValue;
} }
try
{
inProcRuntime.Invoke<object>("jsInteropTests.functionThrowsException");
}
catch (JSException e)
{
ExceptionFromSyncMethod = e;
}
try try
{ {
await JSRuntime.Current.InvokeAsync<object>("jsInteropTests.asyncFunctionThrowsSyncException"); await JSRuntime.Current.InvokeAsync<object>("jsInteropTests.asyncFunctionThrowsSyncException");
@ -120,15 +118,39 @@
{ "stringValue", "My string" }, { "stringValue", "My string" },
{ "testDto", new DotNetObjectRef(passDotNetObjectByRef) }, { "testDto", new DotNetObjectRef(passDotNetObjectByRef) },
}; };
ReceiveDotNetObjectByRefResult = inProcRuntime.Invoke<Dictionary<string, object>>("receiveDotNetObjectByRef", passDotNetObjectByRefArg);
ReceiveDotNetObjectByRefAsyncResult = await JSRuntime.Current.InvokeAsync<Dictionary<string, object>>("receiveDotNetObjectByRefAsync", passDotNetObjectByRefArg); ReceiveDotNetObjectByRefAsyncResult = await JSRuntime.Current.InvokeAsync<Dictionary<string, object>>("receiveDotNetObjectByRefAsync", passDotNetObjectByRefArg);
ReceiveDotNetObjectByRefResult["testDto"] = ReceiveDotNetObjectByRefResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
ReceiveDotNetObjectByRefAsyncResult["testDto"] = ReceiveDotNetObjectByRefAsyncResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different"; ReceiveDotNetObjectByRefAsyncResult["testDto"] = ReceiveDotNetObjectByRefAsyncResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
ReturnValues["returnPrimitive"] = inProcRuntime.Invoke<int>("returnPrimitive").ToString();
ReturnValues["returnPrimitiveAsync"] = (await JSRuntime.Current.InvokeAsync<int>("returnPrimitiveAsync")).ToString(); ReturnValues["returnPrimitiveAsync"] = (await JSRuntime.Current.InvokeAsync<int>("returnPrimitiveAsync")).ToString();
if (shouldSupportSyncInterop)
{
ReturnValues["returnPrimitive"] = ((IJSInProcessRuntime)JSRuntime.Current).Invoke<int>("returnPrimitive").ToString();
}
Invocations = invocations; Invocations = invocations;
DoneWithInterop = true; DoneWithInterop = true;
} }
public void InvokeInProcessInterop()
{
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current);
try
{
inProcRuntime.Invoke<object>("jsInteropTests.functionThrowsException");
}
catch (JSException e)
{
ExceptionFromSyncMethod = e;
}
var passDotNetObjectByRef = new TestDTO(99999);
var passDotNetObjectByRefArg = new Dictionary<string, object>
{
{ "stringValue", "My string" },
{ "testDto", new DotNetObjectRef(passDotNetObjectByRef) },
};
ReceiveDotNetObjectByRefResult = inProcRuntime.Invoke<Dictionary<string, object>>("receiveDotNetObjectByRef", passDotNetObjectByRefArg);
ReceiveDotNetObjectByRefResult["testDto"] = ReceiveDotNetObjectByRefResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
}
} }

View File

@ -1,32 +1,19 @@
// 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. // 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.Hosting;
using Microsoft.AspNetCore.Blazor.Browser.Rendering;
using Microsoft.AspNetCore.Blazor.Browser.Services;
using Microsoft.JSInterop;
using System;
namespace BasicTestApp namespace BasicTestApp
{ {
public class Program 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, CreateHostBuilder(args).Build().Run();
// 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<object>("testReady");
} }
[JSInvokable(nameof(MountTestComponent))] public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
public static void MountTestComponent(string componentTypeName) BlazorWebAssemblyHost.CreateDefaultBuilder()
{ .UseBlazorStartup<Startup>();
var componentType = Type.GetType(componentTypeName);
new BrowserRenderer().AddComponent(componentType, "app");
}
} }
} }

View File

@ -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<Index>("root");
}
}
}

View File

@ -6,71 +6,26 @@
<base href="/subdir/" /> <base href="/subdir/" />
</head> </head>
<body> <body>
<div id="test-selector" style="display: none;"> <root>Loading...</root>
Select test:
<select onchange="mountTestComponent(event.target.value)">
<option value="">Choose...</option>
<option value="BasicTestApp.InteropComponent">Interop component</option>
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
<option value="BasicTestApp.CounterComponent">Counter</option>
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
<option value="BasicTestApp.FocusEventComponent">Focus events</option>
<option value="BasicTestApp.KeyPressEventComponent">Key press event</option>
<option value="BasicTestApp.MouseEventComponent">Mouse events</option>
<option value="BasicTestApp.TouchEventComponent">Touch events</option>
<option value="BasicTestApp.ParentChildComponent">Parent component with child</option>
<option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option>
<option value="BasicTestApp.RedTextComponent">Red text</option>
<option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option>
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
<option value="BasicTestApp.HttpClientTest.HttpRequestsComponent">HttpClient tester</option>
<option value="BasicTestApp.HttpClientTest.BinaryHttpRequestsComponent">Binary HttpClient tester</option>
<option value="BasicTestApp.HttpClientTest.CookieCounterComponent">HttpClient cookies</option>
<option value="BasicTestApp.BindCasesComponent">@bind cases</option>
<option value="BasicTestApp.ExternalContentPackage">External content package</option>
<option value="BasicTestApp.SvgComponent">SVG</option>
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
<option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option>
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
<option value="BasicTestApp.AfterRenderInteropComponent">After-render interop component</option>
<option value="BasicTestApp.EventCasesComponent">Event cases</option>
<!--<option value="BasicTestApp.RouterTest.Default">Router</option> Excluded because it requires additional setup to work correctly when loaded manually -->
</select>
&nbsp;
<span id="source-info"></span>
<hr />
</div>
<app>Loading...</app>
<script src="_framework/blazor.webassembly.js"></script>
<!-- Used for testing interop scenarios between JS and .NET --> <!-- Used for testing interop scenarios between JS and .NET -->
<script src="js/jsinteroptests.js"></script> <script src="js/jsinteroptests.js"></script>
<script> <script>
// The client-side .NET code calls this when it is ready to be called from test code
// The Xunit test code polls until it sees the flag is set
function testReady() {
window.isTestReady = true;
document.getElementsByTagName('APP')[0].textContent = '';
document.getElementById('test-selector').style.display = 'block';
}
// The Xunit test code calls this when setting up tests for specific components
function mountTestComponent(typeName) {
document.getElementById('source-info').innerHTML = '<code><tt>' + typeName.replace(/\./g, '/') + '.cshtml</code></strong>';
DotNet.invokeMethodAsync('BasicTestApp', 'MountTestComponent', typeName);
}
// Used by ElementRefComponent // Used by ElementRefComponent
function setElementValue(element, newValue) { function setElementValue(element, newValue) {
element.value = newValue; element.value = newValue;
} }
(function () {
// Load either blazor.webassembly.js or blazor.server.js depending
// on the hash part of the URL. This is just to give a way for the
// test runner to make the selection.
var src = location.hash === '#server'
? 'blazor.server.js'
: 'blazor.webassembly.js';
document.write('<script src="_framework/' + src + '"><' + '/script>');
})();
</script> </script>
</body> </body>
</html> </html>

View File

@ -4,40 +4,42 @@
var results = {}; var results = {};
var assemblyName = 'BasicTestApp'; var assemblyName = 'BasicTestApp';
function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarget) { function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetObjectByRef, instanceMethodsTarget) {
console.log('Invoking void sync methods.'); if (shouldSupportSyncInterop) {
DotNet.invokeMethod(assemblyName, 'VoidParameterless'); console.log('Invoking void sync methods.');
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidParameterless');
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef)); DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef));
console.log('Invoking returning sync methods.'); console.log('Invoking returning sync methods.');
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray'); results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef)); results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef));
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef)); results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef)); results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef)); results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef));
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef)); results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef)); results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef));
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef)); results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8, dotNetObjectByRef)); results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8, dotNetObjectByRef));
var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef'); var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef');
results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']); results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']);
var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', { var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', {
stringValue: 'My string', stringValue: 'My string',
dtoByRef: dotNetObjectByRef dtoByRef: dotNetObjectByRef
}); });
results['instanceMethodThisTypeName'] = instanceMethodResult.thisTypeName; results['instanceMethodThisTypeName'] = instanceMethodResult.thisTypeName;
results['instanceMethodStringValueUpper'] = instanceMethodResult.stringValueUpper; results['instanceMethodStringValueUpper'] = instanceMethodResult.stringValueUpper;
results['instanceMethodIncomingByRef'] = instanceMethodResult.incomingByRef; results['instanceMethodIncomingByRef'] = instanceMethodResult.incomingByRef;
results['instanceMethodOutgoingByRef'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', instanceMethodResult.outgoingByRef); results['instanceMethodOutgoingByRef'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', instanceMethodResult.outgoingByRef);
}
console.log('Invoking void async methods.'); console.log('Invoking void async methods.');
return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync') return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync')
@ -70,8 +72,9 @@ function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarge
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef))) .then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
.then(r => results['result9Async'] = r) .then(r => results['result9Async'] = r)
.then(() => DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync')) .then(() => DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync'))
.then(r => DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', r['Some async instance']))
.then(r => { .then(r => {
results['resultReturnDotNetObjectByRefAsync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', r['Some async instance']); results['resultReturnDotNetObjectByRefAsync'] = r;
}) })
.then(() => instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', { .then(() => instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', {
stringValue: 'My string', stringValue: 'My string',
@ -81,13 +84,15 @@ function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarge
results['instanceMethodThisTypeNameAsync'] = r.thisTypeName; results['instanceMethodThisTypeNameAsync'] = r.thisTypeName;
results['instanceMethodStringValueUpperAsync'] = r.stringValueUpper; results['instanceMethodStringValueUpperAsync'] = r.stringValueUpper;
results['instanceMethodIncomingByRefAsync'] = r.incomingByRef; 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(() => { .then(() => {
console.log('Invoking methods that throw exceptions'); console.log('Invoking methods that throw exceptions');
try { try {
DotNet.invokeMethod(assemblyName, 'ThrowException'); shouldSupportSyncInterop && DotNet.invokeMethod(assemblyName, 'ThrowException');
} catch (e) { } catch (e) {
results['ThrowException'] = e.message; results['ThrowException'] = e.message;
} }
@ -224,10 +229,18 @@ function receiveDotNetObjectByRef(incomingData) {
} }
function receiveDotNetObjectByRefAsync(incomingData) { function receiveDotNetObjectByRefAsync(incomingData) {
return new Promise(function (resolve, reject) { const stringValue = incomingData.stringValue;
setTimeout(function () { const testDto = incomingData.testDto;
const promiseResult = receiveDotNetObjectByRef(incomingData);
resolve(promiseResult); // To verify we received a proper reference to testDto, pass it back into .NET
}, 100); // 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
};
}); });
} }

View File

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -22,6 +22,7 @@ namespace TestServer
{ {
options.AddPolicy("AllowAll", _ => { /* Controlled below */ }); options.AddPolicy("AllowAll", _ => { /* Controlled below */ });
}); });
services.AddServerSideBlazor<BasicTestApp.Startup>();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -34,6 +35,12 @@ namespace TestServer
AllowCorsForAnyLocalhostPort(app); AllowCorsForAnyLocalhostPort(app);
app.UseMvc(); app.UseMvc();
// Mount the server-side Blazor app on /subdir
app.Map("/subdir", subdirApp =>
{
subdirApp.UseServerSideBlazor<BasicTestApp.Startup>();
});
} }
private static void AllowCorsForAnyLocalhostPort(IApplicationBuilder app) private static void AllowCorsForAnyLocalhostPort(IApplicationBuilder app)

View File

@ -7,4 +7,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="$(AspNetCorePackageVersion)" /> <PackageReference Include="Microsoft.AspNetCore.App" Version="$(AspNetCorePackageVersion)" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Blazor.Server\Microsoft.AspNetCore.Blazor.Server.csproj" />
<ProjectReference Include="..\BasicTestApp\BasicTestApp.csproj" />
</ItemGroup>
</Project> </Project>