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: