Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f2fe4d5
Supress enhanced nav per test.
ilonatommy Aug 5, 2025
4576df3
Each test should have a testId.
ilonatommy Aug 5, 2025
e154da5
Move granting test id to the base class.
ilonatommy Aug 6, 2025
fb9352a
Remove unused namespace.
ilonatommy Aug 6, 2025
edd7b9d
Not all tests can use session storage.
ilonatommy Aug 6, 2025
09f4518
Improve exception message.
ilonatommy Aug 6, 2025
e89ae09
Fix build + fix multiple blank lines.
ilonatommy Aug 6, 2025
8903a36
redirection tests do not ned supression.
ilonatommy Aug 6, 2025
62d4473
`fixture.Navigate` clears the storage, reading testId has to be done …
ilonatommy Aug 6, 2025
6c54aeb
NonInteractivityTests contain tests that require supression and tests…
ilonatommy Aug 6, 2025
843d4e8
Wait for the base page to be loaded to make sure session storage is a…
ilonatommy Aug 20, 2025
ff01e2c
Move the id initialization to the enhanced nav supression method.
ilonatommy Aug 20, 2025
8244b55
Cleanup.
ilonatommy Aug 20, 2025
57365d1
Go back to setting test id only when supression happens but try to cl…
ilonatommy Aug 21, 2025
4f894a7
Improve cleanup - supression flag can also get removed.
ilonatommy Aug 21, 2025
a727aeb
Fix
ilonatommy Aug 21, 2025
acd7444
Fix `RefreshCanFallBackOnFullPageReload`
ilonatommy Aug 21, 2025
a6de604
Fix tests.
ilonatommy Aug 25, 2025
ec6c13d
Cleanup - removal of storage items can be done once, on disposal.
ilonatommy Aug 25, 2025
d6eddc6
Feedback.
ilonatommy Aug 25, 2025
d5fae4b
BasicTestApp did not have h1 "Hello" that we expect on cleanup. Fix it.
ilonatommy Aug 25, 2025
b40fd89
Tests that closed the browser do not have to clean the session storage.
ilonatommy Aug 25, 2025
20e56f4
Another test needs a more specific selector.
ilonatommy Aug 25, 2025
57aedfb
Fix `DragDrop_CanTrigger` that requires specific layout of elements o…
ilonatommy Aug 25, 2025
9a9a913
Add logs in case of "Failed to execute script after 3 retries." error.
ilonatommy Aug 25, 2025
ea933f7
Avoid searching for just h1 tag, use specific ids.
ilonatommy Aug 25, 2025
be88283
Limit relying on JS execution for checking the element position + inc…
ilonatommy Aug 26, 2025
2afc2ae
Grant ID for each test on initialization. Change BinaryHttpClientTest…
ilonatommy Aug 27, 2025
cf2cef5
Try on CI if tests run with small or big window (toggle bar has probl…
ilonatommy Aug 28, 2025
8174443
Fix failing tests.
ilonatommy Aug 29, 2025
4e9ff73
Revert "Fix failing tests."
ilonatommy Aug 30, 2025
0ce17b6
Revert "Try on CI if tests run with small or big window (toggle bar h…
ilonatommy Aug 30, 2025
8c5364c
Revert "Grant ID for each test on initialization. Change BinaryHttpCl…
ilonatommy Aug 30, 2025
68753a0
@javiercn's feedback: selenium gives absolute position.
ilonatommy Sep 2, 2025
f5bbd40
Remove the "tax" on each test, we can return early from cleaning if s…
ilonatommy Sep 2, 2025
a267588
Rename according to feedback.
ilonatommy Sep 2, 2025
e07e484
Use element id to check if page got loaded.
ilonatommy Sep 2, 2025
0994342
Fix tests - use specific ID, not TagName that picks the first element…
ilonatommy Sep 2, 2025
3e5d431
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;
}
}

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:test@example.com" id="mailto-link">Send Email</a>
<a href="download" download id="download-href">Download Link</a>
Expand Down
Loading
Loading