From 33fe1776b0d7c18e05efe98b5409e9a735784fc1 Mon Sep 17 00:00:00 2001 From: Dan Luu Date: Mon, 27 Apr 2026 17:13:59 -0700 Subject: [PATCH 1/6] Avoid missed site editor canvas loader wait --- .../src/admin/visit-site-editor.ts | 119 ++++++++++++++---- 1 file changed, 96 insertions(+), 23 deletions(-) diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts index c610dabadf0af8..fcebba57583eae 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts @@ -12,6 +12,11 @@ interface SiteEditorOptions { showWelcomeGuide?: boolean; } +interface CanvasReadyWaitArgs { + canvasLoaderSelector: string; + readySelector: string; +} + /** * Visits the Site Editor main page. * @@ -59,32 +64,100 @@ export async function visitSiteEditor( * loading is done. */ if ( ! query.size || postId || canvas === 'edit' ) { - const canvasLoader = this.page.locator( + const canvasLoaderSelector = // Spinner was used instead of the progress bar in an earlier // version of the site editor. - '.edit-site-canvas-loader, .edit-site-canvas-spinner' + '.edit-site-canvas-loader, .edit-site-canvas-spinner'; + const readySelector = [ + '.edit-site-editor__editor-interface', + 'iframe[src*="wp_site_preview=1"]', + ].join( ', ' ); + // Larger timeout is needed for large entities, like the Large Post HTML + // fixture that we load for performance tests. + const canvasLoadTimeout = 60_000; + + // The loader can finish before this helper starts waiting. Wait for + // either the loader or a ready canvas state, then verify the loader is gone. + await this.page.waitForFunction( + ( { + canvasLoaderSelector: loader, + readySelector: ready, + }: CanvasReadyWaitArgs ) => { + const isVisibleElement = ( element: Element ) => { + if ( ! element.getClientRects().length ) { + return false; + } + const style = window.getComputedStyle( element ); + return ( + style.display !== 'none' && + style.visibility !== 'hidden' + ); + }; + + const isReadyElement = ( element: Element ) => { + if ( ! isVisibleElement( element ) ) { + return false; + } + if ( element instanceof HTMLIFrameElement ) { + return ( + element.contentDocument?.readyState === 'complete' + ); + } + return true; + }; + + return ( + Array.from( document.querySelectorAll( loader ) ).some( + isVisibleElement + ) || + Array.from( document.querySelectorAll( ready ) ).some( + isReadyElement + ) + ); + }, + { canvasLoaderSelector, readySelector }, + { timeout: canvasLoadTimeout } ); - try { - // Wait for the canvas loader to appear first, so that the locator that - // waits for the hidden state doesn't resolve prematurely. The loader - // either renders within a tick or it is skipped entirely (when the - // editor finishes resolving inside the artificial-delay window in - // `useIsSiteEditorLoading`), so a long visible-state timeout is just - // wasted wall clock when the loader never shows up. - await canvasLoader.waitFor( { state: 'visible', timeout: 3_000 } ); - await canvasLoader.waitFor( { - state: 'hidden', - // Bigger timeout is needed for larger entities, like the Large Post - // HTML fixture that we load for performance tests, which often - // doesn't make it under the default timeout value. - timeout: 60_000, - } ); - } catch { - // If the canvas loader is already disappeared, skip the waiting. - await this.page - .getByRole( 'region', { name: 'Editor content' } ) - .waitFor(); - } + await this.page.waitForFunction( + ( { + canvasLoaderSelector: loader, + readySelector: ready, + }: CanvasReadyWaitArgs ) => { + const isVisibleElement = ( element: Element ) => { + if ( ! element.getClientRects().length ) { + return false; + } + const style = window.getComputedStyle( element ); + return ( + style.display !== 'none' && + style.visibility !== 'hidden' + ); + }; + + const isReadyElement = ( element: Element ) => { + if ( ! isVisibleElement( element ) ) { + return false; + } + if ( element instanceof HTMLIFrameElement ) { + return ( + element.contentDocument?.readyState === 'complete' + ); + } + return true; + }; + + return ( + ! Array.from( document.querySelectorAll( loader ) ).some( + isVisibleElement + ) && + Array.from( document.querySelectorAll( ready ) ).some( + isReadyElement + ) + ); + }, + { canvasLoaderSelector, readySelector }, + { timeout: canvasLoadTimeout } + ); } } From ed040606ac073a95f6a34a6a0fc6e05fdd3c4738 Mon Sep 17 00:00:00 2001 From: Dan Luu Date: Tue, 28 Apr 2026 10:41:42 -0700 Subject: [PATCH 2/6] Refactor site editor canvas wait predicate --- .../src/admin/visit-site-editor.ts | 122 +++++++----------- 1 file changed, 48 insertions(+), 74 deletions(-) diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts index fcebba57583eae..a56e00352c574c 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts @@ -15,6 +15,42 @@ interface SiteEditorOptions { interface CanvasReadyWaitArgs { canvasLoaderSelector: string; readySelector: string; + state: 'loading-or-ready' | 'loaded'; +} + +function isCanvasReadyState( { + canvasLoaderSelector: loader, + readySelector: ready, + state, +}: CanvasReadyWaitArgs ) { + const isVisibleElement = ( element: Element ) => { + if ( ! element.getClientRects().length ) { + return false; + } + const style = window.getComputedStyle( element ); + return style.display !== 'none' && style.visibility !== 'hidden'; + }; + + const isReadyElement = ( element: Element ) => { + if ( ! isVisibleElement( element ) ) { + return false; + } + if ( element instanceof HTMLIFrameElement ) { + return element.contentDocument?.readyState === 'complete'; + } + return true; + }; + + const hasVisibleLoader = Array.from( + document.querySelectorAll( loader ) + ).some( isVisibleElement ); + const hasReadyCanvas = Array.from( + document.querySelectorAll( ready ) + ).some( isReadyElement ); + + return state === 'loading-or-ready' + ? hasVisibleLoader || hasReadyCanvas + : ! hasVisibleLoader && hasReadyCanvas; } /** @@ -79,84 +115,22 @@ export async function visitSiteEditor( // The loader can finish before this helper starts waiting. Wait for // either the loader or a ready canvas state, then verify the loader is gone. await this.page.waitForFunction( - ( { - canvasLoaderSelector: loader, - readySelector: ready, - }: CanvasReadyWaitArgs ) => { - const isVisibleElement = ( element: Element ) => { - if ( ! element.getClientRects().length ) { - return false; - } - const style = window.getComputedStyle( element ); - return ( - style.display !== 'none' && - style.visibility !== 'hidden' - ); - }; - - const isReadyElement = ( element: Element ) => { - if ( ! isVisibleElement( element ) ) { - return false; - } - if ( element instanceof HTMLIFrameElement ) { - return ( - element.contentDocument?.readyState === 'complete' - ); - } - return true; - }; - - return ( - Array.from( document.querySelectorAll( loader ) ).some( - isVisibleElement - ) || - Array.from( document.querySelectorAll( ready ) ).some( - isReadyElement - ) - ); - }, - { canvasLoaderSelector, readySelector }, + isCanvasReadyState, + { + canvasLoaderSelector, + readySelector, + state: 'loading-or-ready', + } satisfies CanvasReadyWaitArgs, { timeout: canvasLoadTimeout } ); await this.page.waitForFunction( - ( { - canvasLoaderSelector: loader, - readySelector: ready, - }: CanvasReadyWaitArgs ) => { - const isVisibleElement = ( element: Element ) => { - if ( ! element.getClientRects().length ) { - return false; - } - const style = window.getComputedStyle( element ); - return ( - style.display !== 'none' && - style.visibility !== 'hidden' - ); - }; - - const isReadyElement = ( element: Element ) => { - if ( ! isVisibleElement( element ) ) { - return false; - } - if ( element instanceof HTMLIFrameElement ) { - return ( - element.contentDocument?.readyState === 'complete' - ); - } - return true; - }; - - return ( - ! Array.from( document.querySelectorAll( loader ) ).some( - isVisibleElement - ) && - Array.from( document.querySelectorAll( ready ) ).some( - isReadyElement - ) - ); - }, - { canvasLoaderSelector, readySelector }, + isCanvasReadyState, + { + canvasLoaderSelector, + readySelector, + state: 'loaded', + } satisfies CanvasReadyWaitArgs, { timeout: canvasLoadTimeout } ); } From 0a1bf7d7590fe8dd269bcc73b9813ea828401304 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 28 Apr 2026 16:50:14 -0500 Subject: [PATCH 3/6] Rearrange types, adjust helper function, move selectors into helper. --- .../src/admin/visit-site-editor.ts | 62 ++++++++----------- 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts index a56e00352c574c..96ba424f86acb1 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts @@ -12,17 +12,26 @@ interface SiteEditorOptions { showWelcomeGuide?: boolean; } -interface CanvasReadyWaitArgs { - canvasLoaderSelector: string; - readySelector: string; - state: 'loading-or-ready' | 'loaded'; -} +/** + * Returns whether the editor canvas is loaded in the given state. + * + * When the editor canvas loads, there are races with the loading spinner + * and with the test assertions. This function examines the combination of + * the canvas and the loading spinner to determine the loading state, and + * indicates if it matches the requested state. + * + * @param state 'loading-or-ready' will match once the editor has started + * to load as well as once it has fully loaded. 'loaded' only + * matches once the spinner is gone and the canvas is visible. + * @return Whether the given state matches the current editor canvas loading state. + */ +function isCanvasReadyState( state: 'loading-or-ready' | 'loaded' ): boolean { + // Spinner was used instead of the progress bar in an earlier + // version of the site editor. + const loader = '.edit-site-canvas-loader, .edit-site-canvas-spinner'; + const ready = + '.edit-site-editor__editor-interface, iframe[src*="wp_site_preview=1"]'; -function isCanvasReadyState( { - canvasLoaderSelector: loader, - readySelector: ready, - state, -}: CanvasReadyWaitArgs ) { const isVisibleElement = ( element: Element ) => { if ( ! element.getClientRects().length ) { return false; @@ -48,9 +57,7 @@ function isCanvasReadyState( { document.querySelectorAll( ready ) ).some( isReadyElement ); - return state === 'loading-or-ready' - ? hasVisibleLoader || hasReadyCanvas - : ! hasVisibleLoader && hasReadyCanvas; + return hasVisibleLoader ? 'loading-or-ready' === state : hasReadyCanvas; } /** @@ -93,21 +100,12 @@ export async function visitSiteEditor( } ); } - /** - * @todo This is a workaround for the fact that the editor canvas is seen as - * ready and visible before the loading spinner is hidden. Ideally, the - * content underneath the loading overlay should be marked inert until the - * loading is done. + /* + * It’s necessary here to wait not only until the editor canvas has loaded, + * but also until the loading spinner is hidden. Ideally, the content underneath + * the loading overlay should be marked inert until the loading is done. */ if ( ! query.size || postId || canvas === 'edit' ) { - const canvasLoaderSelector = - // Spinner was used instead of the progress bar in an earlier - // version of the site editor. - '.edit-site-canvas-loader, .edit-site-canvas-spinner'; - const readySelector = [ - '.edit-site-editor__editor-interface', - 'iframe[src*="wp_site_preview=1"]', - ].join( ', ' ); // Larger timeout is needed for large entities, like the Large Post HTML // fixture that we load for performance tests. const canvasLoadTimeout = 60_000; @@ -116,21 +114,13 @@ export async function visitSiteEditor( // either the loader or a ready canvas state, then verify the loader is gone. await this.page.waitForFunction( isCanvasReadyState, - { - canvasLoaderSelector, - readySelector, - state: 'loading-or-ready', - } satisfies CanvasReadyWaitArgs, + 'loading-or-ready' as const, { timeout: canvasLoadTimeout } ); await this.page.waitForFunction( isCanvasReadyState, - { - canvasLoaderSelector, - readySelector, - state: 'loaded', - } satisfies CanvasReadyWaitArgs, + 'loaded' as const, { timeout: canvasLoadTimeout } ); } From b9e34bb27fdf41c20b01a6e3ae2baaa28029884d Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 4 May 2026 21:18:33 -0700 Subject: [PATCH 4/6] Revert "Rearrange types, adjust helper function, move selectors into helper." This reverts commit 0a1bf7d7590fe8dd269bcc73b9813ea828401304. --- .../src/admin/visit-site-editor.ts | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts index 96ba424f86acb1..a56e00352c574c 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts @@ -12,26 +12,17 @@ interface SiteEditorOptions { showWelcomeGuide?: boolean; } -/** - * Returns whether the editor canvas is loaded in the given state. - * - * When the editor canvas loads, there are races with the loading spinner - * and with the test assertions. This function examines the combination of - * the canvas and the loading spinner to determine the loading state, and - * indicates if it matches the requested state. - * - * @param state 'loading-or-ready' will match once the editor has started - * to load as well as once it has fully loaded. 'loaded' only - * matches once the spinner is gone and the canvas is visible. - * @return Whether the given state matches the current editor canvas loading state. - */ -function isCanvasReadyState( state: 'loading-or-ready' | 'loaded' ): boolean { - // Spinner was used instead of the progress bar in an earlier - // version of the site editor. - const loader = '.edit-site-canvas-loader, .edit-site-canvas-spinner'; - const ready = - '.edit-site-editor__editor-interface, iframe[src*="wp_site_preview=1"]'; +interface CanvasReadyWaitArgs { + canvasLoaderSelector: string; + readySelector: string; + state: 'loading-or-ready' | 'loaded'; +} +function isCanvasReadyState( { + canvasLoaderSelector: loader, + readySelector: ready, + state, +}: CanvasReadyWaitArgs ) { const isVisibleElement = ( element: Element ) => { if ( ! element.getClientRects().length ) { return false; @@ -57,7 +48,9 @@ function isCanvasReadyState( state: 'loading-or-ready' | 'loaded' ): boolean { document.querySelectorAll( ready ) ).some( isReadyElement ); - return hasVisibleLoader ? 'loading-or-ready' === state : hasReadyCanvas; + return state === 'loading-or-ready' + ? hasVisibleLoader || hasReadyCanvas + : ! hasVisibleLoader && hasReadyCanvas; } /** @@ -100,12 +93,21 @@ export async function visitSiteEditor( } ); } - /* - * It’s necessary here to wait not only until the editor canvas has loaded, - * but also until the loading spinner is hidden. Ideally, the content underneath - * the loading overlay should be marked inert until the loading is done. + /** + * @todo This is a workaround for the fact that the editor canvas is seen as + * ready and visible before the loading spinner is hidden. Ideally, the + * content underneath the loading overlay should be marked inert until the + * loading is done. */ if ( ! query.size || postId || canvas === 'edit' ) { + const canvasLoaderSelector = + // Spinner was used instead of the progress bar in an earlier + // version of the site editor. + '.edit-site-canvas-loader, .edit-site-canvas-spinner'; + const readySelector = [ + '.edit-site-editor__editor-interface', + 'iframe[src*="wp_site_preview=1"]', + ].join( ', ' ); // Larger timeout is needed for large entities, like the Large Post HTML // fixture that we load for performance tests. const canvasLoadTimeout = 60_000; @@ -114,13 +116,21 @@ export async function visitSiteEditor( // either the loader or a ready canvas state, then verify the loader is gone. await this.page.waitForFunction( isCanvasReadyState, - 'loading-or-ready' as const, + { + canvasLoaderSelector, + readySelector, + state: 'loading-or-ready', + } satisfies CanvasReadyWaitArgs, { timeout: canvasLoadTimeout } ); await this.page.waitForFunction( isCanvasReadyState, - 'loaded' as const, + { + canvasLoaderSelector, + readySelector, + state: 'loaded', + } satisfies CanvasReadyWaitArgs, { timeout: canvasLoadTimeout } ); } From 4f2535df94fffedd017895572024433ac5fc734a Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 4 May 2026 21:18:39 -0700 Subject: [PATCH 5/6] Revert "Refactor site editor canvas wait predicate" This reverts commit ed040606ac073a95f6a34a6a0fc6e05fdd3c4738. --- .../src/admin/visit-site-editor.ts | 122 +++++++++++------- 1 file changed, 74 insertions(+), 48 deletions(-) diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts index a56e00352c574c..fcebba57583eae 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts @@ -15,42 +15,6 @@ interface SiteEditorOptions { interface CanvasReadyWaitArgs { canvasLoaderSelector: string; readySelector: string; - state: 'loading-or-ready' | 'loaded'; -} - -function isCanvasReadyState( { - canvasLoaderSelector: loader, - readySelector: ready, - state, -}: CanvasReadyWaitArgs ) { - const isVisibleElement = ( element: Element ) => { - if ( ! element.getClientRects().length ) { - return false; - } - const style = window.getComputedStyle( element ); - return style.display !== 'none' && style.visibility !== 'hidden'; - }; - - const isReadyElement = ( element: Element ) => { - if ( ! isVisibleElement( element ) ) { - return false; - } - if ( element instanceof HTMLIFrameElement ) { - return element.contentDocument?.readyState === 'complete'; - } - return true; - }; - - const hasVisibleLoader = Array.from( - document.querySelectorAll( loader ) - ).some( isVisibleElement ); - const hasReadyCanvas = Array.from( - document.querySelectorAll( ready ) - ).some( isReadyElement ); - - return state === 'loading-or-ready' - ? hasVisibleLoader || hasReadyCanvas - : ! hasVisibleLoader && hasReadyCanvas; } /** @@ -115,22 +79,84 @@ export async function visitSiteEditor( // The loader can finish before this helper starts waiting. Wait for // either the loader or a ready canvas state, then verify the loader is gone. await this.page.waitForFunction( - isCanvasReadyState, - { - canvasLoaderSelector, - readySelector, - state: 'loading-or-ready', - } satisfies CanvasReadyWaitArgs, + ( { + canvasLoaderSelector: loader, + readySelector: ready, + }: CanvasReadyWaitArgs ) => { + const isVisibleElement = ( element: Element ) => { + if ( ! element.getClientRects().length ) { + return false; + } + const style = window.getComputedStyle( element ); + return ( + style.display !== 'none' && + style.visibility !== 'hidden' + ); + }; + + const isReadyElement = ( element: Element ) => { + if ( ! isVisibleElement( element ) ) { + return false; + } + if ( element instanceof HTMLIFrameElement ) { + return ( + element.contentDocument?.readyState === 'complete' + ); + } + return true; + }; + + return ( + Array.from( document.querySelectorAll( loader ) ).some( + isVisibleElement + ) || + Array.from( document.querySelectorAll( ready ) ).some( + isReadyElement + ) + ); + }, + { canvasLoaderSelector, readySelector }, { timeout: canvasLoadTimeout } ); await this.page.waitForFunction( - isCanvasReadyState, - { - canvasLoaderSelector, - readySelector, - state: 'loaded', - } satisfies CanvasReadyWaitArgs, + ( { + canvasLoaderSelector: loader, + readySelector: ready, + }: CanvasReadyWaitArgs ) => { + const isVisibleElement = ( element: Element ) => { + if ( ! element.getClientRects().length ) { + return false; + } + const style = window.getComputedStyle( element ); + return ( + style.display !== 'none' && + style.visibility !== 'hidden' + ); + }; + + const isReadyElement = ( element: Element ) => { + if ( ! isVisibleElement( element ) ) { + return false; + } + if ( element instanceof HTMLIFrameElement ) { + return ( + element.contentDocument?.readyState === 'complete' + ); + } + return true; + }; + + return ( + ! Array.from( document.querySelectorAll( loader ) ).some( + isVisibleElement + ) && + Array.from( document.querySelectorAll( ready ) ).some( + isReadyElement + ) + ); + }, + { canvasLoaderSelector, readySelector }, { timeout: canvasLoadTimeout } ); } From a99596985d538cfbf127b3d3fa0da0bdbfdff6fd Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Mon, 4 May 2026 21:18:47 -0700 Subject: [PATCH 6/6] Revert "Avoid missed site editor canvas loader wait" This reverts commit 33fe1776b0d7c18e05efe98b5409e9a735784fc1. --- .../src/admin/visit-site-editor.ts | 119 ++++-------------- 1 file changed, 23 insertions(+), 96 deletions(-) diff --git a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts index fcebba57583eae..c610dabadf0af8 100644 --- a/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts +++ b/packages/e2e-test-utils-playwright/src/admin/visit-site-editor.ts @@ -12,11 +12,6 @@ interface SiteEditorOptions { showWelcomeGuide?: boolean; } -interface CanvasReadyWaitArgs { - canvasLoaderSelector: string; - readySelector: string; -} - /** * Visits the Site Editor main page. * @@ -64,100 +59,32 @@ export async function visitSiteEditor( * loading is done. */ if ( ! query.size || postId || canvas === 'edit' ) { - const canvasLoaderSelector = + const canvasLoader = this.page.locator( // Spinner was used instead of the progress bar in an earlier // version of the site editor. - '.edit-site-canvas-loader, .edit-site-canvas-spinner'; - const readySelector = [ - '.edit-site-editor__editor-interface', - 'iframe[src*="wp_site_preview=1"]', - ].join( ', ' ); - // Larger timeout is needed for large entities, like the Large Post HTML - // fixture that we load for performance tests. - const canvasLoadTimeout = 60_000; - - // The loader can finish before this helper starts waiting. Wait for - // either the loader or a ready canvas state, then verify the loader is gone. - await this.page.waitForFunction( - ( { - canvasLoaderSelector: loader, - readySelector: ready, - }: CanvasReadyWaitArgs ) => { - const isVisibleElement = ( element: Element ) => { - if ( ! element.getClientRects().length ) { - return false; - } - const style = window.getComputedStyle( element ); - return ( - style.display !== 'none' && - style.visibility !== 'hidden' - ); - }; - - const isReadyElement = ( element: Element ) => { - if ( ! isVisibleElement( element ) ) { - return false; - } - if ( element instanceof HTMLIFrameElement ) { - return ( - element.contentDocument?.readyState === 'complete' - ); - } - return true; - }; - - return ( - Array.from( document.querySelectorAll( loader ) ).some( - isVisibleElement - ) || - Array.from( document.querySelectorAll( ready ) ).some( - isReadyElement - ) - ); - }, - { canvasLoaderSelector, readySelector }, - { timeout: canvasLoadTimeout } + '.edit-site-canvas-loader, .edit-site-canvas-spinner' ); - await this.page.waitForFunction( - ( { - canvasLoaderSelector: loader, - readySelector: ready, - }: CanvasReadyWaitArgs ) => { - const isVisibleElement = ( element: Element ) => { - if ( ! element.getClientRects().length ) { - return false; - } - const style = window.getComputedStyle( element ); - return ( - style.display !== 'none' && - style.visibility !== 'hidden' - ); - }; - - const isReadyElement = ( element: Element ) => { - if ( ! isVisibleElement( element ) ) { - return false; - } - if ( element instanceof HTMLIFrameElement ) { - return ( - element.contentDocument?.readyState === 'complete' - ); - } - return true; - }; - - return ( - ! Array.from( document.querySelectorAll( loader ) ).some( - isVisibleElement - ) && - Array.from( document.querySelectorAll( ready ) ).some( - isReadyElement - ) - ); - }, - { canvasLoaderSelector, readySelector }, - { timeout: canvasLoadTimeout } - ); + try { + // Wait for the canvas loader to appear first, so that the locator that + // waits for the hidden state doesn't resolve prematurely. The loader + // either renders within a tick or it is skipped entirely (when the + // editor finishes resolving inside the artificial-delay window in + // `useIsSiteEditorLoading`), so a long visible-state timeout is just + // wasted wall clock when the loader never shows up. + await canvasLoader.waitFor( { state: 'visible', timeout: 3_000 } ); + await canvasLoader.waitFor( { + state: 'hidden', + // Bigger timeout is needed for larger entities, like the Large Post + // HTML fixture that we load for performance tests, which often + // doesn't make it under the default timeout value. + timeout: 60_000, + } ); + } catch { + // If the canvas loader is already disappeared, skip the waiting. + await this.page + .getByRole( 'region', { name: 'Editor content' } ) + .waitFor(); + } } }