1919import { test , expect } from '@wordpress/e2e-test-utils-playwright' ;
2020
2121/**
22- * Waits for resources and fonts to finish loading .
22+ * Waits for network activity, fonts, and jQuery animations to settle .
2323 *
2424 * @param {import('@playwright/test').Page } page
2525 */
2626async function waitForPageReady ( page ) {
2727 await page . waitForLoadState ( 'load' ) ;
28+
29+ // Wait for in-flight requests (AJAX heartbeat, dashboard widgets) to
30+ // finish. The 5 s timeout keeps the suite moving when a long-poll
31+ // endpoint (e.g. heartbeat-tick) holds the connection open.
32+ await page
33+ . waitForLoadState ( 'networkidle' , { timeout : 5000 } )
34+ . catch ( ( ) => { } ) ;
35+
2836 // If a webfont fails to load (network issue, Docker DNS), this resolves
2937 // with fallback fonts and the diff will surface the discrepancy.
3038 await page . evaluate ( ( ) => document . fonts . ready ) ;
39+
40+ // Wait for jQuery animations (e.g. dashboard widget slide-in) to
41+ // complete. CSS animations are already disabled in the Playwright config,
42+ // but jQuery .animate() bypasses that setting.
43+ await page . evaluate ( ( ) => {
44+ if ( typeof jQuery === 'undefined' ) {
45+ return ;
46+ }
47+ return new Promise ( ( resolve ) => {
48+ if ( jQuery . active === 0 && jQuery ( ':animated' ) . length === 0 ) {
49+ resolve ( ) ;
50+ return ;
51+ }
52+ const interval = setInterval ( ( ) => {
53+ if ( jQuery . active === 0 && jQuery ( ':animated' ) . length === 0 ) {
54+ clearInterval ( interval ) ;
55+ resolve ( ) ;
56+ }
57+ } , 100 ) ;
58+ } ) ;
59+ } ) ;
3160}
3261
3362/**
@@ -247,9 +276,21 @@ test.describe( 'Admin Visual Snapshots', () => {
247276
248277 let screenshotOptions = { } ;
249278 if ( Array . isArray ( masks ) ) {
250- screenshotOptions = {
251- mask : masks . map ( ( s ) => page . locator ( s ) ) ,
252- } ;
279+ const locators = masks . map ( ( s ) => page . locator ( s ) ) ;
280+
281+ // Warn when a mask selector matches nothing — the volatile
282+ // element may have been removed or renamed, causing false diffs.
283+ for ( let i = 0 ; i < locators . length ; i ++ ) {
284+ const count = await locators [ i ] . count ( ) ;
285+ if ( count === 0 ) {
286+ // eslint-disable-next-line no-console
287+ console . warn (
288+ `[${ name } ] mask selector "${ masks [ i ] } " matched 0 elements`
289+ ) ;
290+ }
291+ }
292+
293+ screenshotOptions = { mask : locators } ;
253294 } else if ( typeof masks === 'function' ) {
254295 screenshotOptions = { mask : masks ( page ) } ;
255296 }
@@ -260,7 +301,16 @@ test.describe( 'Admin Visual Snapshots', () => {
260301 ) ;
261302 } finally {
262303 if ( teardown ) {
263- await teardown ( requestUtils , data ) ;
304+ try {
305+ await teardown ( requestUtils , data ) ;
306+ } catch ( err ) {
307+ // Log but don't mask the original assertion failure.
308+ // eslint-disable-next-line no-console
309+ console . error (
310+ `[${ name } ] teardown failed:` ,
311+ err . message
312+ ) ;
313+ }
264314 }
265315 }
266316 } ) ;
0 commit comments