Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 150e4b3af8 | |||
| e36b8aea6c |
@@ -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']);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user