// 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.Linq; using System.Runtime.InteropServices; using BasicTestApp; using BasicTestApp.RouterTest; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure; using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; using Xunit; using Xunit.Abstractions; namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests { public class RoutingTest : BasicTestAppTestBase, IDisposable { private readonly ServerFixture _server; public RoutingTest( BrowserFixture browserFixture, DevHostServerFixture serverFixture, ITestOutputHelper output) : base(browserFixture, serverFixture, output) { _server = serverFixture; Navigate(ServerPathBase, noReload: true); WaitUntilDotNetRunningInBrowser(); } [Fact] public void CanArriveAtDefaultPage() { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } [Fact] public void CanArriveAtDefaultPageWithoutTrailingSlash() { // This is a bit of a degenerate case because ideally devs would configure their // servers to enforce a canonical URL (with trailing slash) for the homepage. // But in case they don't want to, we need to handle it the same as if the URL does // have a trailing slash. SetUrlViaPushState($"{ServerPathBase}"); var app = MountTestComponent(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } [Fact] public void CanArriveAtPageWithParameters() { SetUrlViaPushState($"{ServerPathBase}/WithParameters/Name/Ghi/LastName/O'Jkl"); var app = MountTestComponent(); Assert.Equal("Your full name is Ghi O'Jkl.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks(); } [Fact] public void CanArriveAtNonDefaultPage() { SetUrlViaPushState($"{ServerPathBase}/Other"); var app = MountTestComponent(); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToOtherPage() { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other")).Click(); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToOtherPageWithCtrlClick() { // On macOS we need to hold the command key not the control for opening a popup var key = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? Keys.Command : Keys.Control; try { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); var button = app.FindElement(By.LinkText("Other")); new Actions(Browser).KeyDown(key).Click(button).Build().Perform(); Assert.Equal(2, Browser.WindowHandles.Count); } finally { // Leaving the ctrl key up new Actions(Browser).KeyUp(key).Build().Perform(); // Closing newly opened windows if a new one was opened while (Browser.WindowHandles.Count > 1) { Browser.SwitchTo().Window(Browser.WindowHandles.Last()); Browser.Close(); } // Needed otherwise Selenium tries to direct subsequent commands // to the tab that has already been closed Browser.SwitchTo().Window(Browser.WindowHandles.First()); } } [Fact] public void CanFollowLinkToOtherPageDoesNotOpenNewWindow() { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other")).Click(); Assert.Single(Browser.WindowHandles); } [Fact] public void CanFollowLinkToOtherPageWithBaseRelativeUrl() { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click(); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToEmptyStringHrefAsBaseRelativeUrl() { SetUrlViaPushState($"{ServerPathBase}/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default with base-relative URL (matches all)")).Click(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToPageWithParameters() { SetUrlViaPushState($"{ServerPathBase}/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("With parameters")).Click(); Assert.Equal("Your full name is Abc McDef.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("With parameters"); } [Fact] public void CanFollowLinkToDefaultPage() { SetUrlViaPushState($"{ServerPathBase}/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default (matches all)")).Click(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } [Fact] public void CanFollowLinkToOtherPageWithQueryString() { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other with query")).Click(); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with query"); } [Fact] public void CanFollowLinkToDefaultPageWithQueryString() { SetUrlViaPushState($"{ServerPathBase}/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default with query")).Click(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default with query"); } [Fact] public void CanFollowLinkToOtherPageWithHash() { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); app.FindElement(By.LinkText("Other with hash")).Click(); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with hash"); } [Fact] public void CanFollowLinkToDefaultPageWithHash() { SetUrlViaPushState($"{ServerPathBase}/Other"); var app = MountTestComponent(); app.FindElement(By.LinkText("Default with hash")).Click(); Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Default with hash"); } [Fact] public void CanNavigateProgrammatically() { SetUrlViaPushState($"{ServerPathBase}/"); var app = MountTestComponent(); app.FindElement(By.TagName("button")).Click(); Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text); AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)"); } [Fact] public void ClickingAnchorWithNoHrefShouldNotNavigate() { SetUrlViaPushState($"{ServerPathBase}/"); var initialUrl = Browser.Url; var app = MountTestComponent(); app.FindElement(By.Id("anchor-with-no-href")).Click(); Assert.Equal(initialUrl, Browser.Url); AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)"); } public void Dispose() { // Clear any existing state SetUrlViaPushState(ServerPathBase); MountTestComponent(); } private void SetUrlViaPushState(string relativeUri) { var jsExecutor = (IJavaScriptExecutor)Browser; var absoluteUri = new Uri(_server.RootUri, relativeUri); jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')"); } private void AssertHighlightedLinks(params string[] linkTexts) { var actual = Browser.FindElements(By.CssSelector("a.active")); var actualTexts = actual.Select(x => x.Text); Assert.Equal(linkTexts, actualTexts); } } }