Compare commits

...

2 Commits

Author SHA1 Message Date
codex-bot 150e4b3af8 fix(ci): accept hydrated documents with cancelled preload requests
creative workspace review checks / verify-deployed-canvas (pull_request) Successful in 1m7s
creative workspace review checks / verify (pull_request) Successful in 26m21s
2026-11-01 22:14:37 +08:00
Zayden-Jung e36b8aea6c Merge pull request 'fix(ci): 等待浏览器脚本与账户状态就绪后再操作' (#40) from codex/ci-browser-readiness into main
creative workspace CI and deployment / verify-and-deploy (push) Failing after 10m12s
Reviewed-on: #40
2026-11-01 13:58:14 +00:00
2 changed files with 24 additions and 11 deletions
+14 -7
View File
@@ -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;
}
@@ -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']);
});