Page readiness and browser state
These helpers are exported by @lullabot/playwright-testing and work without Drupal. takeAccessibleScreenshot() runs the readiness and incidental browser-state cleanup stages automatically; use the individual helpers for custom capture flows.
Lazy-loaded images and iframes may not have loaded when Playwright first queries the DOM. These utilities scroll resources into view and wait for them to settle before assertions. They are especially important before visual comparisons.
import { expect, test } from '@playwright/test';
import { waitForAllImages, waitForFrames } from '@lullabot/playwright-testing';
test('hero renders with images and embedded video', async ({ page }) => {
await page.goto('/');
await waitForAllImages(page);
await waitForFrames(page);
await expect(page).toHaveScreenshot();
});
waitForImages()
waitForImages(page: Page, selector: string, options?: WaitForImagesOptions): Promise<void>
| Parameter | Default | Description |
|---|---|---|
page |
(required) | The Playwright page object. |
selector |
(required) | CSS selector for the <img> elements to wait for. |
Scrolls each matching image into view to trigger lazy loading, waits for requests to settle and visible images to decode, then returns the page to the top. Requests that fail also settle, so broken images do not hang the initial load wait.
WaitForImagesOptions accepts:
| Option | Default | Description |
|---|---|---|
decodeTimeoutMs |
15000 |
Maximum time to wait for visible images to decode. |
recoverErroredImages |
false |
Re-request broken images with a cache-busting URL. This rewrites src, removes srcset, and removes enclosing <picture> sources. |
afterScroll |
undefined |
Optional async callback receiving the page after it returns to the top. |
waitForAllImages()
waitForAllImages(page: Page, options?: WaitForImagesOptions): Promise<void>
Shorthand for waitForImages(page, 'img:visible', options).
waitForImagesToDecode()
waitForImagesToDecode(page: Page, timeoutMs?: number, recoverErroredImages?: boolean): Promise<string[]>
| Parameter | Default | Description |
|---|---|---|
page |
(required) | The Playwright page object. |
timeoutMs |
15000 |
How long to wait for images to decode before giving up. |
recoverErroredImages |
false |
Allow broken image sources to be rewritten and re-requested. |
Called by waitForImages(); call it directly only to use a different timeout or to assert on the result. Waits for every visible image to decode. When recoverErroredImages is enabled, it re-requests failed images with a cache-busting query parameter. Images that never decode are not an error. Instead, they are warned about on the console and returned, so a broken image in a screenshot has an explanation instead of just a pixel diff.
waitForFonts()
waitForFonts(page: Page): Promise<void>
| Parameter | Default | Description |
|---|---|---|
page |
(required) | The Playwright page object. |
Awaits document.fonts.ready so text is not captured with fallback-font metrics. Fallback glyphs have different widths, so text can wrap onto a different number of lines and render the page at a slightly different height until the real web fonts apply — a flake that typically only shows up on the first attempt.
waitForFrames()
waitForFrames(page: Page): Promise<void>
| Parameter | Default | Description |
|---|---|---|
page |
(required) | The Playwright page object. |
Scrolls each visible, connected <iframe> into view and waits for its frame URL and load state. Operates serially to avoid concurrency bugs; fast enough that parallelism is not worth the complexity.
blurActiveElement()
blurActiveElement(page: Page): Promise<boolean>
| Parameter | Default | Description |
|---|---|---|
page |
(required) | The Playwright page object. |
Removes keyboard focus from a genuinely focused control so its focus ring does not appear in only some screenshot runs. Returns true if an element was blurred, false if nothing was focused (the <body>/<html> fallback is left alone). takeAccessibleScreenshot() calls this for you unless blur: false is passed.
clearHover()
clearHover(page: Page): Promise<() => Promise<void>>
| Parameter | Default | Description |
|---|---|---|
page |
(required) | The Playwright page object. |
Moves the pointer onto a temporary transparent viewport shield so stale pointer activity cannot leave unrelated page content in its :hover state. Returns an idempotent cleanup function that removes the shield and restores normal pointer hit testing. takeAccessibleScreenshot() calls and cleans this up automatically unless clearHover: false is passed.
forcePseudoState()
forcePseudoState(page: Page, selector: string, pseudoClasses: ForcedPseudoClass[]): Promise<() => Promise<void>>
| Parameter | Default | Description |
|---|---|---|
page |
(required) | The Playwright page object. |
selector |
(required) | CSS selector whose first matching element receives the forced state. |
pseudoClasses |
(required) | One or more of active, focus, focus-visible, focus-within, hover, or target. |
Uses Chromium's CSS.forcePseudoState DevTools command to hold interaction CSS
in a deterministic state. It is Chromium-only and throws when the selector has
no match. The returned cleanup function clears the forced classes, detaches the
DevTools session, and is safe to call more than once.
For ordinary cross-browser hover and focus testing, prefer the
interactionStates option. It uses
real Playwright interactions and keeps them active through the screenshot and
accessibility scan.
Forced pseudo-states are independent of real pointer hover and DOM focus, so
takeAccessibleScreenshot()
can still clear those incidental states while preserving the requested styling
through both the screenshot and accessibility scan. See Force CSS pseudo-states for the
declarative visual-diff configuration and a custom-test example.
openAllDetails()
openAllDetails(page: Page): Promise<void>
Expands every collapsed native HTML <details> element on the page so nested
controls become interactable. Call after navigation and before filling fields
inside collapsible regions. This sets the open property directly.