From 150e4b3af864ad2e48e4c24ed017f2a21639c5d5 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 1 Nov 2026 22:14:37 +0800 Subject: [PATCH] fix(ci): accept hydrated documents with cancelled preload requests --- .../waterfall-wiki/tests/brand-navigation.mjs | 21 ++++++++++++------- .../tests/brand-navigation.test.mjs | 14 +++++++++---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/waterfall-wiki/tests/brand-navigation.mjs b/apps/waterfall-wiki/tests/brand-navigation.mjs index 7079839..c6d359e 100644 --- a/apps/waterfall-wiki/tests/brand-navigation.mjs +++ b/apps/waterfall-wiki/tests/brand-navigation.mjs @@ -38,8 +38,8 @@ export async function navigateBrandDocument(navigate, url, now = Date.now) { * @param {boolean} reload */ export async function visitBrowserDocument(page, url, reload = false) { - const started = Date.now(); - const response = await navigateBrandDocument(async timeout => { + return navigateBrandDocument(async timeout => { + const started = Date.now(); const scripts = new Set(), failures = []; const requested = request => { if (request.resourceType() === 'script') scripts.add(request); }; const failed = request => { if (scripts.has(request)) failures.push(request.failure()?.errorText || 'unknown script failure'); }; @@ -47,15 +47,22 @@ export async function visitBrowserDocument(page, url, reload = false) { page.on('requestfailed', failed); try { const response = await (reload ? page.reload({waitUntil: 'domcontentloaded', timeout}) : page.goto(url, {waitUntil: 'domcontentloaded', timeout})); - if (response?.ok() && failures.length) throw new Error('Document bootstrap script request failed: ' + failures.join('; ')); + if (response?.ok()) { + const remaining = timeout - (Date.now() - started); + if (remaining <= 0) throw new Error('Document hydration budget exhausted: ' + url); + try { + // A cancelled script may be a preload duplicate. Hydration is the + // authority; recover only when cancellation actually blocks startup. + await page.locator('button[data-theme-ready="true"]').first().waitFor({timeout: failures.length ? Math.min(5000, remaining) : remaining}); + } catch (error) { + if (failures.length) throw new Error('Document bootstrap script request failed: ' + failures.join('; '), {cause: error}); + throw error; + } + } return response; } finally { page.off('request', requested); page.off('requestfailed', failed); } }, url); - const remaining = 60000 - (Date.now() - started); - if (remaining <= 0) throw new Error('Document hydration budget exhausted: ' + url); - await page.locator('button[data-theme-ready="true"]').first().waitFor({timeout: remaining}); - return response; } diff --git a/apps/waterfall-wiki/tests/brand-navigation.test.mjs b/apps/waterfall-wiki/tests/brand-navigation.test.mjs index bdcc5a6..b3ebd02 100644 --- a/apps/waterfall-wiki/tests/brand-navigation.test.mjs +++ b/apps/waterfall-wiki/tests/brand-navigation.test.mjs @@ -49,7 +49,7 @@ test('recovery cannot extend the total navigation budget', async () => { assert.equal(calls, 1); }); -function browserFixture(failBootstrap = false, failHydration = false) { +function browserFixture(failBootstrap = false, failHydration = false, blockedOnce = false) { const listeners = new Map(), calls = []; const page = { on(event, fn) { if (!listeners.has(event)) listeners.set(event, new Set()); listeners.get(event).add(fn); }, @@ -61,7 +61,7 @@ function browserFixture(failBootstrap = false, failHydration = false) { if (failBootstrap && calls.length === 1) for (const fn of listeners.get('requestfailed') || []) fn(request); return response(200); }, - locator() { return {first: () => ({waitFor: async () => { calls.push('hydrated'); if (failHydration) throw Error('Hydration timeout'); }})}; }, + locator() { return {first: () => ({waitFor: async () => { calls.push('hydrated'); if (failHydration || (blockedOnce && calls.length === 2)) throw Error('Hydration timeout'); }})}; }, }; return {page, calls, listeners}; } @@ -72,12 +72,18 @@ test('document visit completes deferred scripts before checking hydration and re assert.ok([...listeners.values()].every(set => set.size === 0)); }); test('interrupted bootstrap retries only document loading before hydration', async () => { - const {page, calls} = browserFixture(true); + const {page, calls} = browserFixture(true, false, true); await visitBrowserDocument(page, '/account'); - assert.deepEqual(calls, ['domcontentloaded', 'domcontentloaded', 'hydrated']); + assert.deepEqual(calls, ['domcontentloaded', 'hydrated', 'domcontentloaded', 'hydrated']); }); test('hydration failure is surfaced without retrying the application', async () => { const {page, calls} = browserFixture(false, true); await assert.rejects(visitBrowserDocument(page, '/account'), /Hydration timeout/); assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); }); + +test('cancelled script does not reject an already hydrated document', async () => { + const {page, calls} = browserFixture(true); + await visitBrowserDocument(page, '/account'); + assert.deepEqual(calls, ['domcontentloaded', 'hydrated']); +}); -- 2.52.0