fix(ci): complete deferred script loading before brand assertions
This commit is contained in:
@@ -21,7 +21,7 @@ export async function navigateBrandDocument(navigate, url, now = Date.now) {
|
||||
}
|
||||
throw new Error(`Document navigation failed: ${url}, HTTP ${status ?? 'no response'}`);
|
||||
} catch (error) {
|
||||
if (attempt === 1 && /ECONNRESET|net::ERR_(?:CONNECTION_RESET|CONNECTION_CLOSED|EMPTY_RESPONSE)/.test(String(error))) {
|
||||
if (attempt === 1 && /ECONNRESET|net::ERR_(?:CONNECTION_RESET|CONNECTION_CLOSED|EMPTY_RESPONSE|ABORTED)/.test(String(error))) {
|
||||
console.warn('Brand navigation connection recovery', JSON.stringify({url, error: String(error), attempt}));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -16,13 +16,15 @@ test('one proxy failure can recover within the original deadline', async () => {
|
||||
}, '/canvas', () => elapsed);
|
||||
assert.deepEqual(timeouts, [60000, 45000]);
|
||||
});
|
||||
test('connection reset can recover once', async () => {
|
||||
let calls = 0;
|
||||
await navigateBrandDocument(async () => {
|
||||
if (++calls === 1) throw new Error('net::ERR_CONNECTION_RESET');
|
||||
return response(200);
|
||||
}, '/canvas');
|
||||
assert.equal(calls, 2);
|
||||
test('connection reset and interrupted bootstrap scripts can recover once', async () => {
|
||||
for(const failure of ['net::ERR_CONNECTION_RESET','Document bootstrap script request failed: net::ERR_ABORTED']){
|
||||
let calls = 0;
|
||||
await navigateBrandDocument(async () => {
|
||||
if (++calls === 1) throw new Error(failure);
|
||||
return response(200);
|
||||
}, '/canvas');
|
||||
assert.equal(calls, 2);
|
||||
}
|
||||
});
|
||||
test('persistent server failure is not hidden', async () => {
|
||||
let calls = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** CI-only checks against the disposable stack, never against personal documents. */
|
||||
import assert from 'node:assert/strict';
|
||||
import { chromium } from 'playwright-core';
|
||||
import { chromium, type Request } from 'playwright-core';
|
||||
import { navigateBrandDocument } from './brand-navigation.mjs';
|
||||
export async function verifyBrandUI(base: string) {
|
||||
for (const prefix of process.env.VERIFY_CANVAS_UI === '1' ? ['', '/studio'] : ['']) {
|
||||
@@ -19,18 +19,34 @@ export async function verifyBrandUI(base: string) {
|
||||
try {
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 960 } });
|
||||
page.setDefaultTimeout(60000);page.setDefaultNavigationTimeout(60000);
|
||||
const failures: string[] = [];
|
||||
const failures: string[] = [], scripts: {path:string;status?:number;ms:number;error?:string}[] = [];
|
||||
const scriptStarted = new WeakMap<Request,number>();
|
||||
page.on('request', request => { if(request.resourceType()==='script') scriptStarted.set(request,Date.now()); });
|
||||
page.on('response', response => { if(response.request().resourceType()==='script') { scripts.push({path:new URL(response.url()).pathname,status:response.status(),ms:Date.now()-(scriptStarted.get(response.request())??Date.now())}); if(scripts.length>24)scripts.shift(); } });
|
||||
page.on('pageerror', error => failures.push(error.message));
|
||||
page.on('requestfailed', request => failures.push(new URL(request.url()).pathname + ': ' + request.failure()?.errorText));
|
||||
page.on('requestfailed', request => { failures.push(new URL(request.url()).pathname + ': ' + request.failure()?.errorText); if(request.resourceType()==='script') scripts.push({path:new URL(request.url()).pathname,ms:Date.now()-(scriptStarted.get(request)??Date.now()),error:request.failure()?.errorText}); });
|
||||
const visit = async (path: string, theme: string) => {
|
||||
const started = Date.now();
|
||||
try {
|
||||
await navigateBrandDocument(timeout => page.goto(base + path, { waitUntil: 'commit', timeout }), base + path);
|
||||
// DOMContentLoaded also waits for this document's deferred scripts, unlike commit.
|
||||
await navigateBrandDocument(async timeout => {
|
||||
const attemptStarted=Date.now(), bootstrapFailures:string[]=[];
|
||||
const failed=(request:Request)=>{
|
||||
if(request.resourceType()==='script'&&(scriptStarted.get(request)??0)>=attemptStarted)
|
||||
bootstrapFailures.push(request.failure()?.errorText||'unknown script failure');
|
||||
};
|
||||
page.on('requestfailed',failed);
|
||||
try {
|
||||
const response=await page.goto(base+path,{waitUntil:'domcontentloaded',timeout});
|
||||
if(response?.ok()&&bootstrapFailures.length)throw new Error('Document bootstrap script request failed: '+bootstrapFailures.join('; '));
|
||||
return response;
|
||||
} finally {page.off('requestfailed',failed);}
|
||||
}, base + path);
|
||||
await page.locator('button[data-theme-ready="true"]').first().waitFor();
|
||||
await page.waitForFunction(expected => document.documentElement.dataset.theme === expected, theme);
|
||||
console.log('Brand UI route ready:', path, theme, Date.now() - started);
|
||||
} catch (error) {
|
||||
console.error('Brand UI navigation diagnostics', JSON.stringify({path, url:page.url(), elapsedMs:Date.now()-started, failures:failures.slice(-20), body:(await page.locator('body').innerText({timeout:3000}).catch(()=>'unavailable')).slice(0,1200)}));
|
||||
console.error('Brand UI navigation diagnostics', JSON.stringify({path, url:page.url(), elapsedMs:Date.now()-started, failures:failures.slice(-20), scripts:scripts.slice(-24), controls:await page.locator('[data-theme-ready]').evaluateAll(elements=>elements.map(el=>({ready:el.getAttribute('data-theme-ready'),display:getComputedStyle(el).display}))), body:(await page.locator('body').innerText({timeout:3000}).catch(()=>'unavailable')).slice(0,1200)}));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user