diff --git a/src/Components/Web.JS/src/Rendering/Renderer.ts b/src/Components/Web.JS/src/Rendering/Renderer.ts index 976e5a9c75d1..a5619de92702 100644 --- a/src/Components/Web.JS/src/Rendering/Renderer.ts +++ b/src/Components/Web.JS/src/Rendering/Renderer.ts @@ -95,7 +95,7 @@ export function resetScrollAfterNextBatch(): void { shouldResetScrollAfterNextBatch = true; } -function resetScrollIfNeeded() { +export function resetScrollIfNeeded() { if (shouldResetScrollAfterNextBatch) { shouldResetScrollAfterNextBatch = false; diff --git a/src/Components/Web.JS/src/Services/NavigationEnhancement.ts b/src/Components/Web.JS/src/Services/NavigationEnhancement.ts index d7cf652a156a..9965f2b727e2 100644 --- a/src/Components/Web.JS/src/Services/NavigationEnhancement.ts +++ b/src/Components/Web.JS/src/Services/NavigationEnhancement.ts @@ -3,6 +3,7 @@ import { synchronizeDomContent } from '../Rendering/DomMerging/DomSync'; import { attachProgrammaticEnhancedNavigationHandler, handleClickForNavigationInterception, hasInteractiveRouter, isForSamePath, isSamePageWithHash, notifyEnhancedNavigationListeners, performScrollToElementOnTheSamePage } from './NavigationUtils'; +import { resetScrollAfterNextBatch, resetScrollIfNeeded } from '../Rendering/Renderer'; /* In effect, we have two separate client-side navigation mechanisms: @@ -70,13 +71,19 @@ export function detachProgressivelyEnhancedNavigationListener() { window.removeEventListener('popstate', onPopState); } -function performProgrammaticEnhancedNavigation(absoluteInternalHref: string, replace: boolean) { +function performProgrammaticEnhancedNavigation(absoluteInternalHref: string, replace: boolean) : void { + const originalLocation = location.href; + if (replace) { history.replaceState(null, /* ignored title */ '', absoluteInternalHref); } else { history.pushState(null, /* ignored title */ '', absoluteInternalHref); } + if (!isForSamePath(absoluteInternalHref, originalLocation)) { + resetScrollAfterNextBatch(); + } + performEnhancedPageLoad(absoluteInternalHref, /* interceptedLink */ false); } @@ -90,13 +97,20 @@ function onDocumentClick(event: MouseEvent) { } handleClickForNavigationInterception(event, absoluteInternalHref => { + const originalLocation = location.href; + const shouldScrollToHash = isSamePageWithHash(absoluteInternalHref); history.pushState(null, /* ignored title */ '', absoluteInternalHref); if (shouldScrollToHash) { performScrollToElementOnTheSamePage(absoluteInternalHref); } else { + let isSelfNavigation = isForSamePath(absoluteInternalHref, originalLocation); performEnhancedPageLoad(absoluteInternalHref, /* interceptedLink */ true); + if (!isSelfNavigation) { + resetScrollAfterNextBatch(); + resetScrollIfNeeded(); + } } }); } @@ -106,6 +120,7 @@ function onPopState(state: PopStateEvent) { return; } + // load the new page performEnhancedPageLoad(location.href, /* interceptedLink */ false); } diff --git a/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs b/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs index e7b2771f6514..34276e78613e 100644 --- a/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs +++ b/src/Components/test/E2ETest/Infrastructure/WebDriverExtensions/WebDriverExtensions.cs @@ -2,11 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. 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); @@ -14,4 +19,53 @@ public static void Navigate(this IWebDriver browser, Uri baseUri, string relativ browser.Navigate().GoToUrl("about:blank"); browser.Navigate().GoToUrl(absoluteUrl); } + + public static void WaitForElementToBeVisible(this IWebDriver browser, By by, int timeoutInSeconds = 5) + { + var wait = new DefaultWait(browser) + { + Timeout = TimeSpan.FromSeconds(timeoutInSeconds), + PollingInterval = TimeSpan.FromMilliseconds(100) + }; + wait.IgnoreExceptionTypes(typeof(NoSuchElementException), typeof(StaleElementReferenceException)); + wait.Until(driver => + { + try + { + var element = driver.FindElement(by); + return element.Displayed; + } + catch (StaleElementReferenceException) + { + // Retry finding the element + return false; + } + }); + } + + 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)); + for (int i = 0; i < retryCount; i++) + { + try + { + var result = jsExecutor.ExecuteScript(script); + if (result != null) + { + return (long)result; + } + } + catch (OpenQA.Selenium.JavaScriptException) + { + // JavaScript execution failed, retry + } + + Thread.Sleep(delayBetweenRetriesMs); + } + + throw new Exception($"Failed to execute script after {retryCount} retries."); + } } diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs index ba6526360afc..8d04c76de03b 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTest.cs @@ -1,14 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using System.Threading.Tasks; +using Components.TestServer.RazorComponents; +using Microsoft.AspNetCore.Components.E2ETest; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; -using TestServer; -using Xunit.Abstractions; -using Components.TestServer.RazorComponents; using OpenQA.Selenium; +using OpenQA.Selenium.BiDi.Communication; using OpenQA.Selenium.Support.Extensions; +using TestServer; +using Xunit.Abstractions; +using Xunit.Sdk; namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests; @@ -662,6 +666,203 @@ public void CanUpdateHrefOnLinkTagWithIntegrity() Browser.Equal("rgba(0, 0, 255, 1)", () => originalH1Elem.GetCssValue("color")); } + [Theory] + [InlineData(false, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(true, false, false)] + // [InlineData(false, false, true)] programmatic navigation doesn't work without enhanced navigation + [InlineData(false, true, true)] + [InlineData(true, true, true)] + // [InlineData(true, false, true)] programmatic navigation doesn't work without enhanced navigation + public void EnhancedNavigationScrollBehavesSameAsBrowserOnNavigation(bool enableStreaming, bool useEnhancedNavigation, bool programmaticNavigation) + { + // This test checks if the navigation to another path moves the scroll to the top of the page, + // or to the beginning of a fragment, regardless of the previous scroll position + string landingPageSuffix = enableStreaming ? "" : "-no-streaming"; + string buttonKeyword = programmaticNavigation ? "-programmatic" : ""; + 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(); + + // staleness check is used to assert enhanced navigation is enabled/disabled, as requested + var elementForStalenessCheckOnNextPage = Browser.Exists(By.TagName("html")); + + var button1Id = $"do{buttonKeyword}-navigation"; + var button1Pos = Browser.GetElementPositionWithRetry(button1Id); + Browser.SetScrollY(button1Pos); + Browser.Exists(By.Id(button1Id)).Click(); + + // "next" page: check if we landed at 0, then navigate to "landing" + AssertWeAreOnNextPage(); + WaitStreamingRendersFullPage(enableStreaming); + string fragmentId = "some-content"; + Browser.WaitForElementToBeVisible(By.Id(fragmentId)); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnNextPage); + Assert.Equal(0, Browser.GetScrollY()); + var elementForStalenessCheckOnLandingPage = Browser.Exists(By.TagName("html")); + var fragmentScrollPosition = Browser.GetElementPositionWithRetry(fragmentId); + Browser.Exists(By.Id(button1Id)).Click(); + + // "landing" page: navigate to a fragment on another page - we should land at the beginning of the fragment + AssertWeAreOnLandingPage(); + WaitStreamingRendersFullPage(enableStreaming); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnLandingPage); + + var button2Id = $"do{buttonKeyword}-navigation-with-fragment"; + Browser.Exists(By.Id(button2Id)).Click(); + AssertWeAreOnNextPage(); + WaitStreamingRendersFullPage(enableStreaming); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnNextPage); + var expectedFragmentScrollPosition = fragmentScrollPosition - 1; + Assert.Equal(expectedFragmentScrollPosition, Browser.GetScrollY()); + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(true, false, false)] + // [InlineData(false, false, true)] programmatic navigation doesn't work without enhanced navigation + [InlineData(false, true, true)] + [InlineData(true, true, true)] + // [InlineData(true, false, true)] programmatic navigation doesn't work without enhanced navigation + public void EnhancedNavigationScrollBehavesSameAsBrowserOnBackwardsForwardsAction(bool enableStreaming, bool useEnhancedNavigation, bool programmaticNavigation) + { + // This test checks if the scroll position is preserved after backwards/forwards action + string landingPageSuffix = enableStreaming ? "" : "-no-streaming"; + string buttonKeyword = programmaticNavigation ? "-programmatic" : ""; + Navigate($"{ServerPathBase}/nav/scroll-test{landingPageSuffix}"); + EnhancedNavigationTestUtil.SuppressEnhancedNavigation(this, shouldSuppress: !useEnhancedNavigation, skipNavigation: true); + + // "landing" page: scroll to pos1, navigate away + AssertWeAreOnLandingPage(); + WaitStreamingRendersFullPage(enableStreaming); + + // staleness check is used to assert enhanced navigation is enabled/disabled, as requested + var elementForStalenessCheckOnNextPage = Browser.Exists(By.TagName("html")); + + var buttonId = $"do{buttonKeyword}-navigation"; + Browser.WaitForElementToBeVisible(By.Id(buttonId)); + var landingPagePos1 = Browser.GetElementPositionWithRetry(buttonId) - 100; + Browser.SetScrollY(landingPagePos1); + Browser.Exists(By.Id(buttonId)).Click(); + + // "next" page: scroll to pos1, navigate away + AssertWeAreOnNextPage(); + WaitStreamingRendersFullPage(enableStreaming); + Browser.WaitForElementToBeVisible(By.Id(buttonId)); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnNextPage); + var elementForStalenessCheckOnLandingPage = Browser.Exists(By.TagName("html")); + var nextPagePos1 = Browser.GetElementPositionWithRetry(buttonId) - 100; + // make sure we are expecting different scroll positions on the 1st and the 2nd page + Assert.NotEqual(landingPagePos1, nextPagePos1); + Browser.SetScrollY(nextPagePos1); + Browser.Exists(By.Id(buttonId)).Click(); + + // "landing" page: scroll to pos2, go backwards + AssertWeAreOnLandingPage(); + WaitStreamingRendersFullPage(enableStreaming); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnLandingPage); + var landingPagePos2 = 500; + Browser.SetScrollY(landingPagePos2); + Browser.Navigate().Back(); + + // "next" page: check if we landed on pos1, move the scroll to pos2, go backwards + AssertWeAreOnNextPage(); + WaitStreamingRendersFullPage(enableStreaming); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnNextPage); + AssertScrollPositionCorrect(useEnhancedNavigation, nextPagePos1); + + var nextPagePos2 = 600; + Browser.SetScrollY(nextPagePos2); + Browser.Navigate().Back(); + + // "landing" page: check if we landed on pos1, move the scroll to pos3, go forwards + AssertWeAreOnLandingPage(); + WaitStreamingRendersFullPage(enableStreaming); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnLandingPage); + AssertScrollPositionCorrect(useEnhancedNavigation, landingPagePos1); + var landingPagePos3 = 700; + Browser.SetScrollY(landingPagePos3); + Browser.Navigate().Forward(); + + // "next" page: check if we landed on pos1, go forwards + AssertWeAreOnNextPage(); + WaitStreamingRendersFullPage(enableStreaming); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnNextPage); + AssertScrollPositionCorrect(useEnhancedNavigation, nextPagePos2); + Browser.Navigate().Forward(); + + // "scroll" page: check if we landed on pos2 + AssertWeAreOnLandingPage(); + WaitStreamingRendersFullPage(enableStreaming); + AssertEnhancedNavigation(useEnhancedNavigation, elementForStalenessCheckOnLandingPage); + AssertScrollPositionCorrect(useEnhancedNavigation, landingPagePos2); + } + + private void AssertScrollPositionCorrect(bool useEnhancedNavigation, long previousScrollPosition) + { + // from some reason, scroll position sometimes differs by 1 pixel between enhanced and browser's navigation + // browser's navigation is not precisely going backwards/forwards to the previous state + var currentScrollPosition = Browser.GetScrollY(); + string messagePart = useEnhancedNavigation ? $"{previousScrollPosition}" : $"{previousScrollPosition} or {previousScrollPosition - 1}"; + bool isPreciselyWhereItWasLeft = currentScrollPosition == previousScrollPosition; + bool isPixelLowerThanItWasLeft = currentScrollPosition == (previousScrollPosition - 1); + bool success = useEnhancedNavigation + ? isPreciselyWhereItWasLeft + : (isPreciselyWhereItWasLeft || isPixelLowerThanItWasLeft); + Assert.True(success, $"The expected scroll position was {messagePart}, but it was found at {currentScrollPosition}."); + } + + private void AssertEnhancedNavigation(bool useEnhancedNavigation, IWebElement elementForStalenessCheck, int retryCount = 3, int delayBetweenRetriesMs = 100) + { + bool enhancedNavigationDetected = false; + for (int i = 0; i < retryCount; i++) + { + try + { + enhancedNavigationDetected = !IsElementStale(elementForStalenessCheck); + Assert.Equal(useEnhancedNavigation, enhancedNavigationDetected); + return; + } + catch (XunitException) + { + // Maybe the check was done too early to change the DOM ref, retry + } + + Thread.Sleep(delayBetweenRetriesMs); + } + string expectedNavigation = useEnhancedNavigation ? "enhanced navigation" : "browser navigation"; + 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}"); + } + + private void AssertWeAreOnLandingPage() + { + string infoName = "test-info-1"; + Browser.WaitForElementToBeVisible(By.Id(infoName), timeoutInSeconds: 20); + 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.Equal("Scroll tests next page", () => Browser.Exists(By.Id(infoName)).Text); + } + + private void WaitStreamingRendersFullPage(bool enableStreaming) + { + if (enableStreaming) + { + Browser.WaitForElementToBeVisible(By.Id("some-content")); + } + } + private void AssertEnhancedUpdateCountEquals(long count) => Browser.Equal(count, () => ((IJavaScriptExecutor)Browser).ExecuteScript("return window.enhancedPageUpdateCount;")); diff --git a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs index 27de50df3bc3..799b915f8fdd 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/EnhancedNavigationTestUtil.cs @@ -32,4 +32,7 @@ public static void SuppressEnhancedNavigation(ServerTestBase Convert.ToInt64(((IJavaScriptExecutor)browser).ExecuteScript("return window.scrollY"), CultureInfo.CurrentCulture); + + public static long SetScrollY(this IWebDriver browser, long value) + => Convert.ToInt64(((IJavaScriptExecutor)browser).ExecuteScript($"window.scrollTo(0, {value})"), CultureInfo.CurrentCulture); } diff --git a/src/Components/test/E2ETest/ServerRenderingTests/FocusOnNavigateTest.cs b/src/Components/test/E2ETest/ServerRenderingTests/FocusOnNavigateTest.cs index 14a16f49dd8f..a5599eab306f 100644 --- a/src/Components/test/E2ETest/ServerRenderingTests/FocusOnNavigateTest.cs +++ b/src/Components/test/E2ETest/ServerRenderingTests/FocusOnNavigateTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Components.TestServer.RazorComponents; +using Microsoft.AspNetCore.Components.E2ETest; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; using Microsoft.AspNetCore.E2ETesting; @@ -59,7 +60,10 @@ public void FocusIsMoved_OnEnhancedNavigation_WhenAnyElementMatchesSelector() { Navigate($"{ServerPathBase}/focus-on-navigate"); Browser.Click(By.LinkText("Statically rendered")); - Browser.True(() => Browser.SwitchTo().ActiveElement().GetDomAttribute("data-focus-on-navigate") is not null); + Browser.True( + () => Browser.SwitchTo().ActiveElement().GetDomAttribute("data-focus-on-navigate") is not null, + TimeSpan.FromSeconds(5) + ); } [Fact] @@ -67,11 +71,23 @@ public void FocusIsPreserved_OnEnhancedFormPost_WhenAnyElementMatchesSelector() { Navigate($"{ServerPathBase}/focus-on-navigate"); Browser.Click(By.LinkText("Form submission")); - Browser.True(() => Browser.SwitchTo().ActiveElement().GetDomAttribute("id") == "value-to-submit"); - Browser.FindElement(By.Id("value-to-submit")).ReplaceText("Some value"); - Browser.Click(By.Id("submit-button")); + string valueToSubmit = "value-to-submit"; + AssertFocusPreserved(valueToSubmit); + Browser.FindElement(By.Id(valueToSubmit)).ReplaceText("Some value"); + string submitButtonId = "submit-button"; + Browser.Click(By.Id(submitButtonId)); Browser.Equal("Some value", () => Browser.FindElement(By.Id("submitted-value")).Text); - Browser.True(() => Browser.SwitchTo().ActiveElement().GetDomAttribute("id") == "submit-button"); + AssertFocusPreserved(submitButtonId); + } + + private void AssertFocusPreserved(string elementId) + { + Browser.WaitForElementToBeVisible(By.Id(elementId)); + Browser.True( + () => Browser.SwitchTo().ActiveElement().GetDomAttribute("id") == elementId, + TimeSpan.FromSeconds(5), + $"Expected element with id '{elementId}' to be focused, but found '{Browser.SwitchTo().ActiveElement().GetDomAttribute("id")}' instead." + ); } [Fact] diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestLandingPage.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestLandingPage.razor new file mode 100644 index 000000000000..effc2ffc205f --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestLandingPage.razor @@ -0,0 +1,50 @@ +@page "/nav/scroll-test" +@inject NavigationManager NavigationManager +@attribute [StreamRendering] +@rendermode RenderMode.InteractiveServer + +Page for testing scroll position + +

Scroll tests landing page

+ +

If you scroll down a long way, you'll find more content. We add it asynchronously via streaming rendering.

+ +
spacer top
+ +@if (showContent) +{ + + Navigation to another page with fragment + + + Navigation to another page + + + +} + +
spacer bottom
+ +@if (showContent) +{ +

Some content

+

This is the content.

+} + +@code { + bool showContent; + string uriOnPageLoad; + string hashPagePath = "nav/scroll-test-next"; + string hashPageFragmentPath = "nav/scroll-test-next#some-content"; + + protected override async Task OnInitializedAsync() + { + uriOnPageLoad = NavigationManager.Uri; + await Task.Delay(1000); + showContent = true; + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestLandingPageNoStreaming.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestLandingPageNoStreaming.razor new file mode 100644 index 000000000000..160dd0db39a7 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestLandingPageNoStreaming.razor @@ -0,0 +1,32 @@ +@page "/nav/scroll-test-no-streaming" +@inject NavigationManager NavigationManager +@rendermode RenderMode.InteractiveServer +@* This is a copy of ScrollTestLandingPage, without StreamRendering *@ + +Page for testing scroll position + +

Scroll tests landing page

+ +
spacer top
+ + + Navigation to another page with fragment + + + Navigation to another page + + + + +
spacer bottom
+ +

Some content

+ +@code { + string hashPagePath = "nav/scroll-test-next-no-streaming"; + string hashPageFragmentPath = "nav/scroll-test-next-no-streaming#some-content"; +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestNextPage.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestNextPage.razor new file mode 100644 index 000000000000..f26d1b677bf2 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestNextPage.razor @@ -0,0 +1,38 @@ +@page "/nav/scroll-test-next" +@inject NavigationManager NavigationManager +@attribute [StreamRendering] +@rendermode RenderMode.InteractiveServer + +Next page for testing scroll position + +

Scroll tests next page

+ +

If you scroll down a long way, you'll find more content. We add it asynchronously via streaming rendering.

+ +
top spacer
+ +@if (showContent) +{ +

Some content

+ + Navigation to another page + + + +

This is the content.

+} + +
bottom spacer
+ +@code { + bool showContent; + string scrollPagePath = "nav/scroll-test"; + + protected override async Task OnInitializedAsync() + { + await Task.Delay(1000); + showContent = true; + } +} diff --git a/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestNextPageNoStreaming.razor b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestNextPageNoStreaming.razor new file mode 100644 index 000000000000..ededc32aa929 --- /dev/null +++ b/src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/EnhancedNav/ScrollTestNextPageNoStreaming.razor @@ -0,0 +1,24 @@ +@page "/nav/scroll-test-next-no-streaming" +@inject NavigationManager NavigationManager +@rendermode RenderMode.InteractiveServer +@* This is a copy of ScrollTestNextPage, without StreamRendering *@ + +Next page for testing scroll position + +

Scroll tests next page

+ +
top spacer
+ +

Some content

+ + Navigation to another page + + + +
bottom spacer
+ +@code { + string scrollPagePath = "nav/scroll-test-no-streaming"; +} diff --git a/src/Shared/E2ETesting/WaitAssert.cs b/src/Shared/E2ETesting/WaitAssert.cs index 41928b07a003..71c8ae6b7c41 100644 --- a/src/Shared/E2ETesting/WaitAssert.cs +++ b/src/Shared/E2ETesting/WaitAssert.cs @@ -35,6 +35,9 @@ public static void True(this IWebDriver driver, Func actual) public static void True(this IWebDriver driver, Func actual, TimeSpan timeout) => WaitAssertCore(driver, () => Assert.True(actual()), timeout); + public static void True(this IWebDriver driver, Func actual, TimeSpan timeout, string message) + => WaitAssertCore(driver, () => Assert.True(actual(), message), timeout); + public static void False(this IWebDriver driver, Func actual) => WaitAssertCore(driver, () => Assert.False(actual()));