11import type { Page } from '@playwright/test'
22
3+ const CI_SETTLE_DELAY_MS = 250
4+ const LOCAL_SETTLE_DELAY_MS = 100
5+
6+ function getSettleDelayMs ( ) : number {
7+ return process . env . CI ? CI_SETTLE_DELAY_MS : LOCAL_SETTLE_DELAY_MS
8+ }
9+
310/**
411 * Wait for all fonts to be loaded and a rendering frame to complete.
512 * Replaces waitForTimeout(1000) after page.goto()
@@ -8,14 +15,43 @@ import type { Page } from '@playwright/test'
815 * (page.evaluate is not available in JS-disabled contexts).
916 */
1017export async function waitForFontsReady ( page : Page ) : Promise < void > {
18+ await page . waitForLoadState ( 'load' )
19+
1120 try {
12- await page . evaluate ( async ( ) => {
13- await document . fonts . ready
14- await page . waitForLoadState ( 'load' )
15- } )
21+ await page . evaluate ( async ( settleDelayMs ) => {
22+ const wait = ( ms : number ) =>
23+ new Promise < void > ( ( resolve ) => window . setTimeout ( resolve , ms ) )
24+ const nextFrame = ( ) =>
25+ new Promise < void > ( ( resolve ) => requestAnimationFrame ( ( ) => resolve ( ) ) )
26+
27+ if ( 'fonts' in document ) {
28+ await document . fonts . ready
29+ }
30+
31+ const pendingImages = Array . from ( document . images ) . filter (
32+ ( image ) => ! image . complete ,
33+ )
34+
35+ await Promise . all (
36+ pendingImages . map (
37+ ( image ) =>
38+ new Promise < void > ( ( resolve ) => {
39+ image . addEventListener ( 'load' , ( ) => resolve ( ) , {
40+ once : true ,
41+ } )
42+ image . addEventListener ( 'error' , ( ) => resolve ( ) , {
43+ once : true ,
44+ } )
45+ } ) ,
46+ ) ,
47+ )
48+
49+ await nextFrame ( )
50+ await nextFrame ( )
51+ await wait ( settleDelayMs )
52+ } , getSettleDelayMs ( ) )
1653 } catch {
17- // JS disabled — fall back to load event (fires after fonts in CSS are loaded)
18- await page . waitForLoadState ( 'load' )
54+ await page . waitForTimeout ( getSettleDelayMs ( ) )
1955 }
2056}
2157
@@ -26,5 +62,44 @@ export async function waitForFontsReady(page: Page): Promise<void> {
2662 * Falls back to waitForLoadState('load') when JavaScript is disabled.
2763 */
2864export async function waitForStyleSettle ( page : Page ) : Promise < void > {
29- await page . waitForTimeout ( 100 )
65+ try {
66+ await page . waitForFunction ( ( ) => {
67+ return Array . from (
68+ document . querySelectorAll < HTMLLinkElement > ( 'link[rel="stylesheet"]' ) ,
69+ ) . every ( ( link ) => {
70+ if ( ! link . href ) {
71+ return true
72+ }
73+
74+ const { sheet } = link
75+ if ( ! sheet ) {
76+ return false
77+ }
78+
79+ try {
80+ void sheet . cssRules
81+ return true
82+ } catch {
83+ return false
84+ }
85+ } )
86+ } )
87+ } catch {
88+ await page . waitForLoadState ( 'load' )
89+ }
90+
91+ try {
92+ await page . evaluate ( async ( settleDelayMs ) => {
93+ const wait = ( ms : number ) =>
94+ new Promise < void > ( ( resolve ) => window . setTimeout ( resolve , ms ) )
95+ const nextFrame = ( ) =>
96+ new Promise < void > ( ( resolve ) => requestAnimationFrame ( ( ) => resolve ( ) ) )
97+
98+ await nextFrame ( )
99+ await nextFrame ( )
100+ await wait ( settleDelayMs )
101+ } , getSettleDelayMs ( ) )
102+ } catch {
103+ await page . waitForTimeout ( getSettleDelayMs ( ) )
104+ }
30105}
0 commit comments