Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
66d2b0b
Supress enhanced nav per test.
ilonatommy Aug 5, 2025
4b1f572
Each test should have a testId.
ilonatommy Aug 5, 2025
945c9dd
Move granting test id to the base class.
ilonatommy Aug 6, 2025
e9bf1bf
Remove unused namespace.
ilonatommy Aug 6, 2025
f9601f1
Not all tests can use session storage.
ilonatommy Aug 6, 2025
2cd3a18
Improve exception message.
ilonatommy Aug 6, 2025
5fd1050
Fix build + fix multiple blank lines.
ilonatommy Aug 6, 2025
7c0bf05
redirection tests do not ned supression.
ilonatommy Aug 6, 2025
b2e8441
Merge branch 'main' into improve-enhanced-nav-supression
ilonatommy Aug 6, 2025
08c99cf
`fixture.Navigate` clears the storage, reading testId has to be done …
ilonatommy Aug 6, 2025
73abbda
NonInteractivityTests contain tests that require supression and tests…
ilonatommy Aug 6, 2025
5ef86a1
Wait for the base page to be loaded to make sure session storage is a…
ilonatommy Aug 20, 2025
357192c
Move the id initialization to the enhanced nav supression method.
ilonatommy Aug 20, 2025
92cad9a
Cleanup.
ilonatommy Aug 20, 2025
8c5c3b1
Go back to setting test id only when supression happens but try to cl…
ilonatommy Aug 21, 2025
189db63
Improve cleanup - supression flag can also get removed.
ilonatommy Aug 21, 2025
fa057a8
Fix
ilonatommy Aug 21, 2025
ee4aa34
Fix `RefreshCanFallBackOnFullPageReload`
ilonatommy Aug 21, 2025
795b665
Fix tests.
ilonatommy Aug 25, 2025
a6856fc
Cleanup - removal of storage items can be done once, on disposal.
ilonatommy Aug 25, 2025
249f03a
Feedback.
ilonatommy Aug 25, 2025
763ea9d
BasicTestApp did not have h1 "Hello" that we expect on cleanup. Fix it.
ilonatommy Aug 25, 2025
5696430
Tests that closed the browser do not have to clean the session storage.
ilonatommy Aug 25, 2025
e1b6931
Another test needs a more specific selector.
ilonatommy Aug 25, 2025
1a8d9bf
Fix `DragDrop_CanTrigger` that requires specific layout of elements o…
ilonatommy Aug 25, 2025
1339daa
Add logs in case of "Failed to execute script after 3 retries." error.
ilonatommy Aug 25, 2025
21051c2
Avoid searching for just h1 tag, use specific ids.
ilonatommy Aug 25, 2025
0bd2afc
Limit relying on JS execution for checking the element position + inc…
ilonatommy Aug 26, 2025
7ac455c
Grant ID for each test on initialization. Change BinaryHttpClientTest…
ilonatommy Aug 27, 2025
18ee068
Try on CI if tests run with small or big window (toggle bar has probl…
ilonatommy Aug 28, 2025
6dcfd16
Fix failing tests.
ilonatommy Aug 29, 2025
413dd78
Revert "Fix failing tests."
ilonatommy Aug 30, 2025
88135be
Revert "Try on CI if tests run with small or big window (toggle bar h…
ilonatommy Aug 30, 2025
8b62ae8
Revert "Grant ID for each test on initialization. Change BinaryHttpCl…
ilonatommy Aug 30, 2025
19793d7
@javiercn's feedback: selenium gives absolute position.
ilonatommy Sep 2, 2025
37b0a3c
Remove the "tax" on each test, we can return early from cleaning if s…
ilonatommy Sep 2, 2025
0f28172
Rename according to feedback.
ilonatommy Sep 2, 2025
ba57845
Use element id to check if page got loaded.
ilonatommy Sep 2, 2025
860752c
Fix tests - use specific ID, not TagName that picks the first element…
ilonatommy Sep 2, 2025
047bd95
ID is not necessary if we're cleaning and not creating it for every t…
ilonatommy Sep 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@page "/"

<h1>Hello, world!</h1>
<h1 id="session-storage-anchor">Hello, world!</h1>

Welcome to your new app.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected override void InitializeAsyncCore()
{
Navigate(ServerPathBase);
Browser.MountTestComponent<GracefulTermination>();
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<TestSink>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)]
Expand All @@ -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();
Expand Down Expand Up @@ -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)]
Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests;

public static class EnhancedNavigationTestUtil
{
private static bool _isSuppressed;

public static void SuppressEnhancedNavigation<TServerFixture>(ServerTestBase<TServerFixture> fixture, bool shouldSuppress, bool skipNavigation = false)
where TServerFixture : ServerFixture
{
Expand All @@ -20,16 +22,76 @@ public static void SuppressEnhancedNavigation<TServerFixture>(ServerTestBase<TSe

if (!skipNavigation)
{
// Normally we need to navigate here first otherwise the browser isn't on the correct origin to access
// localStorage. But some tests are already in the right place and need to avoid extra navigation.
fixture.Navigate($"{fixture.ServerPathBase}/");
browser.Equal("Hello", () => 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<TServerFixture>(ServerTestBase<TServerFixture> 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;
}
}
Comment on lines +74 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than doing this. Can we check Browser.SessionId == null and just return? Another option might be Browser.WindowHandles

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can try. This was added because we have failures in tests that close the browser intentionally, it should be easy to check if Browser.SessionId == null works for them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Browser.SessionId does not exist. We can cast browser to driver instance but then..the session id exists even when there are no windows handlers and the browser is closed.
Referencing WindowsHandles or CurrentWindowHandle when the session is out creates the exact exception that we were catching.
I don't see a good replacement for the exception flow.


private static void NavigateToOrigin<TServerFixture>(ServerTestBase<TServerFixture> 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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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]
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@inject NavigationManager navigationManager

<h1>Graceful Termination</h1>
<h1 id="graceful-termination-title">Graceful Termination</h1>

<a href="mailto:[email protected]" id="mailto-link">Send Email</a>
<a href="download" download id="download-href">Download Link</a>
Expand Down
Loading
Loading