From 4eca6906f5310ebdfb657794596cff0671031da0 Mon Sep 17 00:00:00 2001 From: Lucas Quaresma <15186323+lrqnet@users.noreply.github.com> Date: Sun, 26 Jul 2026 23:32:23 -0500 Subject: [PATCH] Stabilize NetKeep E2E network recovery --- tests/E2E/support/navigation.ts | 26 +++++++++++++++++++++++--- tests/E2E/update-feedback.spec.ts | 13 ++++++++----- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/tests/E2E/support/navigation.ts b/tests/E2E/support/navigation.ts index 7eb67ab..43e386e 100644 --- a/tests/E2E/support/navigation.ts +++ b/tests/E2E/support/navigation.ts @@ -1,16 +1,20 @@ import type { Page, Response } from '@playwright/test'; +function isRetryableNetworkError(error: unknown): boolean { + return /ERR_SSL_PROTOCOL_ERROR|ERR_NETWORK_CHANGED/.test(String(error)); +} + export async function gotoWithTlsRetry( page: Page, url: string, ): Promise { - for (let attempt = 1; attempt <= 4; attempt += 1) { + for (let attempt = 1; attempt <= 8; attempt += 1) { try { return await page.goto(url); } catch (error) { if ( - attempt === 4 || - !String(error).includes('ERR_SSL_PROTOCOL_ERROR') + attempt === 8 || + !isRetryableNetworkError(error) ) { throw error; } @@ -21,3 +25,19 @@ export async function gotoWithTlsRetry( return null; } + +export async function reloadWithNetworkRetry(page: Page): Promise { + for (let attempt = 1; attempt <= 8; attempt += 1) { + try { + return await page.reload(); + } catch (error) { + if (attempt === 8 || !isRetryableNetworkError(error)) { + throw error; + } + + await page.waitForTimeout(attempt * 1_000); + } + } + + return null; +} diff --git a/tests/E2E/update-feedback.spec.ts b/tests/E2E/update-feedback.spec.ts index 3e9890c..d1896ec 100644 --- a/tests/E2E/update-feedback.spec.ts +++ b/tests/E2E/update-feedback.spec.ts @@ -2,7 +2,10 @@ import { execFileSync } from 'node:child_process'; import { resolve } from 'node:path'; import AxeBuilder from '@axe-core/playwright'; import { expect, test } from '@playwright/test'; -import { gotoWithTlsRetry } from './support/navigation'; +import { + gotoWithTlsRetry, + reloadWithNetworkRetry, +} from './support/navigation'; const project = process.env.NETKEEP_E2E_PROJECT ?? 'netkeep-e2e'; const root = resolve(import.meta.dirname, '../..'); @@ -94,7 +97,7 @@ test('reauthenticates once and preserves update feedback across navigation and r await expect( page.getByText('Update request received', { exact: true }), ).toBeVisible(); - await page.reload(); + await reloadWithNetworkRetry(page); await expect( page.getByText('Update request received', { exact: true }), ).toBeVisible(); @@ -117,7 +120,7 @@ test('reauthenticates once and preserves update feedback across navigation and r { timeout: 120_000 }, ) .toBe(200); - await page.reload(); + await reloadWithNetworkRetry(page); await expect( page.getByText('Updating v1.0.3 to v1.0.6', { exact: true }), ).toBeVisible(); @@ -132,13 +135,13 @@ test('reauthenticates once and preserves update feedback across navigation and r "last_progress_at" => now(), ]); `); - await page.reload(); + await reloadWithNetworkRetry(page); await expect( page.getByText('NetKeep v1.0.6 was installed successfully.', { exact: true, }), ).toBeVisible(); - await page.reload(); + await reloadWithNetworkRetry(page); await expect( page.getByText('NetKeep v1.0.6 was installed successfully.', { exact: true,