From 7a4b6eebb217c56bbc0b89e8c6bcb0e30eb93a6a Mon Sep 17 00:00:00 2001 From: Vito Date: Fri, 23 Oct 2026 00:08:43 -0700 Subject: [PATCH] test(shell): split the shell concept comparison per route and motion (#297) "shell geometry, tokens and controls match the concept" compared all three routes (Images, Library, Tags) under both motions in one test per size and theme: 12 page loads, 12 captures and 36 hovers under the 30 s default budget. Over the last 40 Viewer.js runs it took 22 s median and up to 33 s on CI WebKit, and timed out 6 times (main run 39886719087 among them). The CI traces show steady progress with no stalled step (the longest one is a 0.8 s navigation), so the cause is the amount of work in one test, not a hang or a race. Each route and motion is now its own test, still covering both sizes and both themes, with the same assertions and captures. Under run-pw-ci.sh on WebKit at 2 CPUs, --repeat-each=10: before 18/40 timed out (median 26.9 s, max 32.5 s); after 0/240 (median 6.5 s, p90 9.8 s, max 17.2 s). --- tests/browser/shell-concept.spec.mjs | 41 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/tests/browser/shell-concept.spec.mjs b/tests/browser/shell-concept.spec.mjs index a4076d7..1497631 100644 --- a/tests/browser/shell-concept.spec.mjs +++ b/tests/browser/shell-concept.spec.mjs @@ -178,27 +178,28 @@ async function captureFooter(page,testInfo,name) { test.beforeEach(async({request})=>{await seedConcept(request);}); -for(const [width,height] of SIZES)for(const theme of THEMES)test(`shell geometry, tokens and controls match the concept ${width}x${height} ${theme}`,async({page},testInfo)=>{ +// One test per route, size, theme and motion. All of them in one test per size and theme (12 page loads, 12 captures, +// 36 hovers) steadily took 22 s median and up to 33 s on CI WebKit, past the 30 s budget with no step stalled; one +// route in one motion is a sixth of that work under the same budget. +for(const [route,breadcrumb] of ROUTES)for(const [width,height] of SIZES)for(const theme of THEMES)for(const motion of MOTIONS)test(`shell geometry, tokens and controls match the concept on ${route} ${width}x${height} ${theme} ${motion}`,async({page},testInfo)=>{ await page.setViewportSize({width,height});const result={}; - for(const motion of MOTIONS)for(const [route,breadcrumb] of ROUTES) { - await openConcept(page,route,theme,motion);await page.mouse.move(0,0); - const reference=await inventoryOf(page,'#main'); - if(motion==='no-preference')await captureCanonicalState(page,testInfo,`concept-shell-${route}-${width}-${theme}`); - const referenceRings=FULL_HEADING.has(route)?await ringsOf(page,'#main'):null; - const referenceHovers=await hoversOf(page); - if(motion==='no-preference')await captureFooter(page,testInfo,`concept-footer-${route}-${width}-${theme}`); - await openProduction(page,route,theme,motion);await page.mouse.move(0,0); - const actual=await inventoryOf(page,'#workspace-main'); - if(motion==='no-preference')await captureCanonicalState(page,testInfo,`production-shell-${route}-${width}-${theme}`); - const actualRings=FULL_HEADING.has(route)?await ringsOf(page,'#workspace-main'):null; - const actualHovers=await hoversOf(page); - if(motion==='no-preference')await captureFooter(page,testInfo,`production-footer-${route}-${width}-${theme}`); - result[`${motion} ${route}`]={route,breadcrumb,reference,actual,diff:compare(route,reference,actual),referenceRings,actualRings, - referenceHovers,actualHovers}; - } - const path=testInfo.outputPath('shell-geometry.json');await writeFile(path,JSON.stringify(result,null,2)); - await testInfo.attach('shell-geometry',{path,contentType:'application/json'}); - for(const [key,{route,breadcrumb,reference,actual,diff,referenceRings,actualRings,referenceHovers,actualHovers}] of Object.entries(result)) { + await openConcept(page,route,theme,motion);await page.mouse.move(0,0); + const reference=await inventoryOf(page,'#main'); + if(motion==='no-preference')await captureCanonicalState(page,testInfo,`concept-shell-${route}-${width}-${theme}`); + const referenceRings=FULL_HEADING.has(route)?await ringsOf(page,'#main'):null; + const referenceHovers=await hoversOf(page); + if(motion==='no-preference')await captureFooter(page,testInfo,`concept-footer-${route}-${width}-${theme}`); + await openProduction(page,route,theme,motion);await page.mouse.move(0,0); + const actual=await inventoryOf(page,'#workspace-main'); + if(motion==='no-preference')await captureCanonicalState(page,testInfo,`production-shell-${route}-${width}-${theme}`); + const actualRings=FULL_HEADING.has(route)?await ringsOf(page,'#workspace-main'):null; + const actualHovers=await hoversOf(page); + if(motion==='no-preference')await captureFooter(page,testInfo,`production-footer-${route}-${width}-${theme}`); + result[`${motion} ${route}`]={reference,actual,diff:compare(route,reference,actual),referenceRings,actualRings, + referenceHovers,actualHovers}; + const path=testInfo.outputPath(`shell-geometry-${route}-${motion}.json`);await writeFile(path,JSON.stringify(result,null,2)); + await testInfo.attach(`shell-geometry-${route}-${motion}`,{path,contentType:'application/json'}); + for(const [key,{reference,actual,diff,referenceRings,actualRings,referenceHovers,actualHovers}] of Object.entries(result)) { // The concept itself is what this comparison assumes: its sample counts and its shell controls. expect.soft(reference.chrome.navs[0].rows.map(row=>[row.text,row.count?.text??null]),`${key} concept WORKSPACE rows`) .toEqual([['Images','24'],['Prompt library','6'],['Tags & categories','15'],['Inspect a file',null]]);