diff --git a/tests/DrawTogether.End2End.Tests/DrawingMouseInteractionTests.cs b/tests/DrawTogether.End2End.Tests/DrawingMouseInteractionTests.cs index 1bd28ed..6d478d9 100644 --- a/tests/DrawTogether.End2End.Tests/DrawingMouseInteractionTests.cs +++ b/tests/DrawTogether.End2End.Tests/DrawingMouseInteractionTests.cs @@ -49,8 +49,10 @@ public async Task CanDrawWithMouse() _output.WriteLine($"Navigating to {endpoint}"); - // Navigate to the app - await page.GotoAsync(endpoint.ToString()); + // Navigate to the app. Retry on ERR_NETWORK_CHANGED: a freshly launched + // Chromium can abort its first navigation when Aspire's container network + // is still settling. + await GotoWithNetworkChangeRetryAsync(page, endpoint.ToString(), output: _output); // Handle authentication if needed - example for form-based auth await HandleNewUserRegistration(page, endpoint, _output); diff --git a/tests/DrawTogether.End2End.Tests/DrawingTouchInteractionTests.cs b/tests/DrawTogether.End2End.Tests/DrawingTouchInteractionTests.cs index d39c8a6..6f5cef8 100644 --- a/tests/DrawTogether.End2End.Tests/DrawingTouchInteractionTests.cs +++ b/tests/DrawTogether.End2End.Tests/DrawingTouchInteractionTests.cs @@ -1,4 +1,4 @@ -using DrawTogether.End2End.Tests.Util; +using DrawTogether.End2End.Tests.Util; using Microsoft.Playwright; using Xunit.Abstractions; using static DrawTogether.End2End.Tests.Util.DrawTogetherPlaywrightHelpers; @@ -63,8 +63,10 @@ public async Task CanDrawWithTouch(int viewPortX, int viewPortY) _output.WriteLine($"Navigating to {endpoint}"); - // Navigate to the app - await page.GotoAsync(endpoint.ToString()); + // Navigate to the app. Retry on ERR_NETWORK_CHANGED: a freshly launched + // Chromium can abort its first navigation when Aspire's container network + // is still settling. + await GotoWithNetworkChangeRetryAsync(page, endpoint.ToString(), output: _output); // Handle authentication if needed - example for form-based auth await HandleNewUserRegistration(page, endpoint, _output); diff --git a/tests/DrawTogether.End2End.Tests/Util/DrawTogetherPlaywrightHelpers.cs b/tests/DrawTogether.End2End.Tests/Util/DrawTogetherPlaywrightHelpers.cs index 1ff262f..c42bc8e 100644 --- a/tests/DrawTogether.End2End.Tests/Util/DrawTogetherPlaywrightHelpers.cs +++ b/tests/DrawTogether.End2End.Tests/Util/DrawTogetherPlaywrightHelpers.cs @@ -5,6 +5,42 @@ namespace DrawTogether.End2End.Tests.Util; public static class DrawTogetherPlaywrightHelpers { + /// + /// The first navigation from a freshly launched Chromium can be aborted with + /// net::ERR_NETWORK_CHANGED: the browser tears down in-flight requests when it + /// observes a host network-state change, and Docker creating/removing container bridge + /// interfaces while Aspire starts resources produces exactly that. + /// See https://github.com/microsoft/playwright/issues/13062 (same signature, retry clears it) + /// and https://github.com/Grizzly-Endeavors/residuum/issues/592 (Docker bridge churn). + /// The abort happens before the page renders anything, so retrying is side-effect free. + /// + private const string NetworkChangedError = "ERR_NETWORK_CHANGED"; + + public static async Task GotoWithNetworkChangeRetryAsync( + IPage page, + string url, + PageGotoOptions? options = null, + ITestOutputHelper? output = null, + int maxAttempts = 3) + { + for (var attempt = 1; ; attempt++) + { + try + { + return await page.GotoAsync(url, options); + } + catch (PlaywrightException ex) when ( + attempt < maxAttempts && + ex.Message.Contains(NetworkChangedError, StringComparison.Ordinal)) + { + output?.WriteLine( + $"Navigation to {url} aborted by {NetworkChangedError} " + + $"(attempt {attempt}/{maxAttempts}); retrying."); + await Task.Delay(TimeSpan.FromMilliseconds(250 * attempt)); + } + } + } + public static async Task LocateDrawingBox(IPage page, ITestOutputHelper output) { // Wait for the canvas to be present