diff --git a/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor index 16dac3192520..71201aa021ee 100644 --- a/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor +++ b/src/Components/WebAssembly/testassets/StandaloneApp/Pages/Index.razor @@ -1,5 +1,5 @@ @page "/" -

Hello, world!

+

Hello, world!

Welcome to your new app. diff --git a/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs b/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs index 841cecb32695..315b80cd2248 100644 --- a/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs +++ b/src/Components/test/E2ETest/Infrastructure/ServerTestBase.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; using Microsoft.AspNetCore.E2ETesting; using OpenQA.Selenium; using Xunit.Abstractions; @@ -31,6 +32,12 @@ public void Navigate(string relativeUrl) Browser.Navigate(_serverFixture.RootUri, relativeUrl); } + public override async Task DisposeAsync() + { + EnhancedNavigationTestUtil.CleanEnhancedNavigationSuppression(this); + await base.DisposeAsync(); + } + protected override void InitializeAsyncCore() { // Clear logs - we check these during tests in some cases. diff --git a/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs b/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs index 34276e78613e..fd24da459497 100644 --- a/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs +++ b/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs @@ -3,15 +3,11 @@ using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; -using System; namespace Microsoft.AspNetCore.Components.E2ETest; internal static class WebDriverExtensions { - private static string GetFindPositionScript(string elementId) => - $"return Math.round(document.getElementById('{elementId}').getBoundingClientRect().top + window.scrollY);"; - public static void Navigate(this IWebDriver browser, Uri baseUri, string relativeUrl) { var absoluteUrl = new Uri(baseUri, relativeUrl); @@ -45,27 +41,27 @@ public static void WaitForElementToBeVisible(this IWebDriver browser, By by, int public static long GetElementPositionWithRetry(this IWebDriver browser, string elementId, int retryCount = 3, int delayBetweenRetriesMs = 100) { - var jsExecutor = (IJavaScriptExecutor)browser; - string script = GetFindPositionScript(elementId); - browser.WaitForElementToBeVisible(By.Id(elementId)); + string log = ""; + for (int i = 0; i < retryCount; i++) { try { - var result = jsExecutor.ExecuteScript(script); - if (result != null) - { - return (long)result; - } + browser.WaitForElementToBeVisible(By.Id(elementId)); + var element = browser.FindElement(By.Id(elementId)); + return element.Location.Y; } - catch (OpenQA.Selenium.JavaScriptException) + catch (Exception ex) { - // JavaScript execution failed, retry + log += $"Attempt {i + 1}: - {ex.Message}. "; } - Thread.Sleep(delayBetweenRetriesMs); + if (i < retryCount - 1) + { + Thread.Sleep(delayBetweenRetriesMs); + } } - throw new Exception($"Failed to execute script after {retryCount} retries."); + throw new Exception($"Failed to get position for element '{elementId}' after {retryCount} retries. Debug log: {log}"); } } diff --git a/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs b/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs index 91c2b580fe60..5fe0f8a2ee37 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/CircuitContextTest.cs @@ -41,7 +41,7 @@ public void ComponentMethods_HaveCircuitContext_OnInitialPageLoad() // Internal for reuse in Blazor Web tests internal static void TestCircuitContextCore(IWebDriver browser) { - browser.Equal("Circuit Context", () => browser.Exists(By.TagName("h1")).Text); + browser.Equal("Circuit Context", () => browser.Exists(By.Id("circuit-context-title")).Text); browser.Click(By.Id("trigger-click-event-button")); diff --git a/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs b/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs index ff7e56f9a363..37ecf327872a 100644 --- a/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs +++ b/src/Components/test/E2ETest/ServerExecutionTests/CircuitGracefulTerminationTests.cs @@ -40,7 +40,7 @@ protected override void InitializeAsyncCore() { Navigate(ServerPathBase); Browser.MountTestComponent(); - Browser.Equal("Graceful Termination", () => Browser.Exists(By.TagName("h1")).Text); + Browser.Equal("Graceful Termination", () => Browser.Exists(By.Id("graceful-termination-title")).Text); GracefulDisconnectCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); Sink = _serverFixture.Host.Services.GetRequiredService(); diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs index 7dfaf3523287..49ca00e06915 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs @@ -317,6 +317,9 @@ public void RefreshCanFallBackOnFullPageReload(string renderMode) Browser.Navigate().Refresh(); Browser.Equal("Page with interactive components that navigate", () => Browser.Exists(By.TagName("h1")).Text); + // if we don't clean up the suppression, all subsequent navigations will be suppressed by default + EnhancedNavigationTestUtil.CleanEnhancedNavigationSuppression(this, skipNavigation: true); + // Normally, you shouldn't store references to elements because they could become stale references // after the page re-renders. However, we want to explicitly test that the element becomes stale // across renders to ensure that a full page reload occurs. @@ -677,11 +680,10 @@ public void CanUpdateHrefOnLinkTagWithIntegrity() } [Theory] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/60875")] - // [InlineData(false, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(false, false, false)] [InlineData(false, true, false)] [InlineData(true, true, false)] - // [InlineData(true, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(true, false, false)] // [InlineData(false, false, true)] programmatic navigation doesn't work without enhanced navigation [InlineData(false, true, true)] [InlineData(true, true, true)] @@ -692,8 +694,8 @@ public void EnhancedNavigationScrollBehavesSameAsBrowserOnNavigation(bool enable // or to the beginning of a fragment, regardless of the previous scroll position string landingPageSuffix = enableStreaming ? "" : "-no-streaming"; string buttonKeyword = programmaticNavigation ? "-programmatic" : ""; + EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation); Navigate($"{ServerPathBase}/nav/scroll-test{landingPageSuffix}"); - EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation, skipNavigation: true); // "landing" page: scroll maximally down and go to "next" page - we should land at the top of that page AssertWeAreOnLandingPage(); @@ -732,10 +734,10 @@ public void EnhancedNavigationScrollBehavesSameAsBrowserOnNavigation(bool enable } [Theory] - // [InlineData(false, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(false, false, false)] [InlineData(false, true, false)] [InlineData(true, true, false)] - // [InlineData(true, false, false)] // https://github.com/dotnet/aspnetcore/issues/60875 + [InlineData(true, false, false)] // [InlineData(false, false, true)] programmatic navigation doesn't work without enhanced navigation [InlineData(false, true, true)] [InlineData(true, true, true)] @@ -745,8 +747,8 @@ public void EnhancedNavigationScrollBehavesSameAsBrowserOnBackwardsForwardsActio // This test checks if the scroll position is preserved after backwards/forwards action string landingPageSuffix = enableStreaming ? "" : "-no-streaming"; string buttonKeyword = programmaticNavigation ? "-programmatic" : ""; + EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation); Navigate($"{ServerPathBase}/nav/scroll-test{landingPageSuffix}"); - EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation, skipNavigation: true); // "landing" page: scroll to pos1, navigate away AssertWeAreOnLandingPage(); @@ -831,6 +833,8 @@ private void AssertScrollPositionCorrect(bool useEnhancedNavigation, long previo private void AssertEnhancedNavigation(bool useEnhancedNavigation, IWebElement elementForStalenessCheck, int retryCount = 3, int delayBetweenRetriesMs = 1000) { bool enhancedNavigationDetected = false; + string logging = ""; + string isNavigationSuppressed = ""; for (int i = 0; i < retryCount; i++) { try @@ -841,28 +845,32 @@ private void AssertEnhancedNavigation(bool useEnhancedNavigation, IWebElement el } catch (XunitException) { + var logs = Browser.GetBrowserLogs(LogLevel.Warning); + logging += $"{string.Join(", ", logs.Select(l => l.Message))}\n"; + isNavigationSuppressed = (string)((IJavaScriptExecutor)Browser).ExecuteScript("return sessionStorage.getItem('suppress-enhanced-navigation');"); + + logging += $" isNavigationSuppressed: {isNavigationSuppressed}\n"; // Maybe the check was done too early to change the DOM ref, retry } Thread.Sleep(delayBetweenRetriesMs); } - string expectedNavigation = useEnhancedNavigation ? "enhanced navigation" : "browser navigation"; + string expectedNavigation = useEnhancedNavigation ? "enhanced navigation" : "full page load"; string isStale = enhancedNavigationDetected ? "is not stale" : "is stale"; - var isNavigationSupressed = (string)((IJavaScriptExecutor)Browser).ExecuteScript("return sessionStorage.getItem('suppress-enhanced-navigation');"); - throw new Exception($"Expected to use {expectedNavigation} because 'suppress-enhanced-navigation' is set to {isNavigationSupressed} but the element from previous path {isStale}"); + throw new Exception($"Expected to use {expectedNavigation} because 'suppress-enhanced-navigation' is set to {isNavigationSuppressed} but the element from previous path {isStale}. logging={logging}"); } private void AssertWeAreOnLandingPage() { string infoName = "test-info-1"; - Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 20); + Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 30); Browser.Equal("Scroll tests landing page", () => Browser.Exists(By.Id(infoName)).Text); } private void AssertWeAreOnNextPage() { string infoName = "test-info-2"; - Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 20); + Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 30); Browser.Equal("Scroll tests next page", () => Browser.Exists(By.Id(infoName)).Text); } diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs index 799b915f8fdd..ffd0287063a0 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; public static class EnhancedNavigationTestUtil { + private static bool _isSuppressed; + public static void SuppressEnhancedNavigation(ServerTestBase fixture, bool shouldSuppress, bool skipNavigation = false) where TServerFixture : ServerFixture { @@ -20,16 +22,76 @@ public static void SuppressEnhancedNavigation(ServerTestBase browser.Exists(By.TagName("h1")).Text); + NavigateToOrigin(fixture); + } + + try + { + ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.length"); + } + catch (Exception ex) + { + throw new InvalidOperationException("Session storage not found. Ensure that the browser is on the correct origin by navigating to a page or by setting skipNavigation to false.", ex); } ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.setItem('suppress-enhanced-navigation', 'true')"); + + var suppressEnhancedNavigation = ((IJavaScriptExecutor)browser).ExecuteScript("return sessionStorage.getItem('suppress-enhanced-navigation');"); + Assert.True(suppressEnhancedNavigation is not null && (string)suppressEnhancedNavigation == "true", + "Expected 'suppress-enhanced-navigation' to be set in sessionStorage."); + _isSuppressed = true; } } + public static void CleanEnhancedNavigationSuppression(ServerTestBase fixture, bool skipNavigation = false) + where TServerFixture : ServerFixture + { + if (!_isSuppressed) + { + return; + } + + var browser = fixture.Browser; + + try + { + // First, ensure we're on the correct origin to access sessionStorage + try + { + // Check if we can access sessionStorage from current location + ((IJavaScriptExecutor)browser).ExecuteScript("sessionStorage.length"); + } + catch + { + if (skipNavigation) + { + throw new InvalidOperationException("Session storage not found. Ensure that the browser is on the correct origin by navigating to a page or by setting skipNavigation to false."); + } + NavigateToOrigin(fixture); + } + ((IJavaScriptExecutor)browser).ExecuteScript($"sessionStorage.removeItem('suppress-enhanced-navigation')"); + } + catch (WebDriverException ex) when (ex.Message.Contains("invalid session id")) + { + // Browser session is no longer valid (e.g., browser was closed) + // Session storage is automatically cleared when browser closes, so cleanup is already done + // This is expected in some tests, so we silently return + return; + } + finally + { + _isSuppressed = false; + } + } + + private static void NavigateToOrigin(ServerTestBase fixture) + where TServerFixture : ServerFixture + { + // Navigate to the test origin to ensure the browser is on the correct state to access sessionStorage + fixture.Navigate($"{fixture.ServerPathBase}/"); + fixture.Browser.Exists(By.Id("session-storage-anchor")); + } + public static long GetScrollY(this IWebDriver browser) => Convert.ToInt64(((IJavaScriptExecutor)browser).ExecuteScript("return window.scrollY"), CultureInfo.CurrentCulture); diff --git a/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs b/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs index bd69e484a5f8..73a860124ecb 100644 --- a/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs +++ b/src/Components/test/E2ETest/Tests/SectionsWithCascadingParametersTest.cs @@ -35,7 +35,7 @@ public void RenderSectionContent_CascadingParameterForSectionOutletIsDeterminedB Browser.FindElement(By.Id("render-section-outlet")).Click(); Browser.FindElement(By.Id("render-second-section-content")).Click(); - Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -46,7 +46,7 @@ public void ChangeCascadingValueForSectionContent_CascadingValueForSectionOutlet Browser.FindElement(By.Id("change-cascading-value")).Click(); - Browser.Equal("First Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -56,7 +56,7 @@ public void RenderTwoSectionContentsWithSameId_CascadingParameterForSectionOutle Browser.FindElement(By.Id("render-first-section-content")).Click(); Browser.FindElement(By.Id("render-section-outlet")).Click(); - Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -68,7 +68,7 @@ public void SecondSectionContentIdChanged_CascadingParameterForSectionOutletIsDe Browser.FindElement(By.Id("change-second-section-content-id")).Click(); - Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -80,7 +80,7 @@ public void SecondSectionContentDisposed_CascadingParameterForSectionOutletIsDet Browser.FindElement(By.Id("dispose-second-section-content")).Click(); - Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("First Section with additional text for first section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -92,7 +92,7 @@ public void FirstSectionContentDisposedThenRenderSecondSectionContent_CascadingP Browser.FindElement(By.Id("dispose-first-section-content")).Click(); Browser.FindElement(By.Id("render-second-section-content")).Click(); - Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } [Fact] @@ -105,6 +105,6 @@ public void SectionOutletIdChanged_CascadingParameterForSectionOutletIsDetermine Browser.FindElement(By.Id("change-section-outlet-id")).Click(); - Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Second Section with additional text for second section", () => Browser.Exists(By.Id("text-component")).Text); } } diff --git a/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs b/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs index 3509030754af..25106e9ba4f2 100644 --- a/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs +++ b/src/Components/test/E2ETest/Tests/SectionsWithErrorBoundaryTest.cs @@ -37,7 +37,7 @@ public void RenderSectionContent_ErrorBoundaryForSectionOutletContentIsDetermine Browser.FindElement(By.Id("error-button")).Click(); - Browser.Equal("Sorry!", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Sorry!", () => Browser.Exists(By.Id("error-content")).Text); } [Fact] @@ -88,7 +88,7 @@ public void FirstSectionContentDisposedThenRenderSecondSectionContent_ErrorBound Browser.FindElement(By.Id("render-second-section-content")).Click(); Browser.FindElement(By.Id("error-button")).Click(); - Browser.Equal("Sorry!", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Sorry!", () => Browser.Exists(By.Id("error-content")).Text); } [Fact] @@ -102,6 +102,6 @@ public void SectionOutletIdChanged_ErrorBoundaryForSectionOutletIsDeterminedByMa Browser.FindElement(By.Id("change-section-outlet-id")).Click(); Browser.FindElement(By.Id("error-button")).Click(); - Browser.Equal("Sorry!", () => Browser.Exists(By.TagName("p")).Text); + Browser.Equal("Sorry!", () => Browser.Exists(By.Id("error-content")).Text); } } diff --git a/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor b/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor index 967775faa4bb..8109f027687f 100644 --- a/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor +++ b/src/Components/test/testassets/BasicTestApp/GracefulTermination.razor @@ -1,6 +1,6 @@ @inject NavigationManager navigationManager -

Graceful Termination

+

Graceful Termination

Send Email Download Link diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 6e8d20b391a2..a3bc250f0634 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -1,6 +1,8 @@ @using Microsoft.AspNetCore.Components.Rendering @using System.Web @inject NavigationManager NavigationManager + +

Select test: