@@ -8,7 +8,7 @@ const CLS_BUDGET = 0.1;
88
99// Pages this gate guards. The homepage is the one Google flagged (0.58); add
1010// more paths here once each has been audited and reserves its media space.
11- const ROUTES = [ "/" ] ;
11+ const ROUTES = [ "/" , "/templates/movie-posters" ] ;
1212
1313// layout-shift entries aren't in the DOM lib.
1414interface LayoutShiftEntry extends PerformanceEntry {
@@ -23,6 +23,33 @@ declare global {
2323}
2424
2525test . describe ( "Cumulative Layout Shift budget" , ( ) => {
26+ test ( "template samples reserve space before the image loads" , async ( { page } ) => {
27+ let releaseImage ! : ( ) => void ;
28+ const imageReady = new Promise < void > ( ( resolve ) => { releaseImage = resolve ; } ) ;
29+ await page . route ( "**/templates/samples/movie-posters.webp" , async ( route ) => {
30+ await imageReady ;
31+ await route . continue ( ) ;
32+ } ) ;
33+
34+ try {
35+ await page . goto ( "/templates/movie-posters" , { waitUntil : "domcontentloaded" } ) ;
36+ const image = page . locator ( "figure img" ) ;
37+ const before = await image . boundingBox ( ) ;
38+ expect ( before ) . not . toBeNull ( ) ;
39+ expect ( before ! . width ) . toBeGreaterThan ( 400 ) ;
40+ expect ( before ! . height ) . toBeGreaterThan ( 600 ) ;
41+ expect ( await image . evaluate ( ( element : HTMLImageElement ) => element . complete ) ) . toBe ( false ) ;
42+
43+ releaseImage ( ) ;
44+ await image . evaluate ( ( element : HTMLImageElement ) => element . decode ( ) ) ;
45+ const after = await image . boundingBox ( ) ;
46+ expect ( after ! . width ) . toBeCloseTo ( before ! . width , 0 ) ;
47+ expect ( after ! . height ) . toBeCloseTo ( before ! . height , 0 ) ;
48+ } finally {
49+ releaseImage ( ) ;
50+ }
51+ } ) ;
52+
2653 for ( const path of ROUTES ) {
2754 test ( `${ path } stays under ${ CLS_BUDGET } CLS through a full scroll` , async ( {
2855 page,
0 commit comments