fix: wait for SPA rendering (networkidle) before running axe scan#223
fix: wait for SPA rendering (networkidle) before running axe scan#223mvanhorn wants to merge 3 commits into
Conversation
The scanner ran axe immediately after page.goto, so single-page apps that render after load were scanned against a near-empty DOM and reported no violations. Wait for networkidle (30s cap) before scanning; if a page never reaches idle, warn and proceed so long-polling/websocket sites still scan. Closes github#201 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
are we sure this is the correct fix? according to the playwright docs
which is the recommended event. Also,
it specifically says: "rely on web assertions to assess readiness instead" - which would be something like waiting for a specific element to be rendered. Maybe we need to add a new input to the scanner (like an ID or class) for an element that we wait on to ensure the page is rendered fully. ** EDIT ** more info regarding page navigation in playwright (specifically the hydration section): https://playwright.dev/docs/navigations#hydration |
Per review: Playwright discourages networkidle and recommends web assertions for readiness. Replaces the networkidle wait with a locator assertion on rendered visible content under body, with a warn-and-proceed fallback so minimal static pages still scan. The timeout is exposed as a configurable action input.
|
You're right, and thanks for the docs pointer - networkidle was the lazy version of this fix. Reworked in 450fddb: goto keeps its default load event, and readiness is now a web assertion (waiting for visible rendered content under body) with a warn-and-proceed fallback so intentionally minimal pages still get scanned. The timeout is exposed as an action input rather than hardcoded. Tests updated for both the late-rendering SPA path and the fallback path. |
|
You're right that Playwright discourages networkidle, and the docs' advice (rely on web assertions) is the correct pattern when you know the app under test. The catch here is that the scanner runs against arbitrary URLs - there are no app-specific selectors to assert on, so "wait for something meaningful" has no generic definition. |
…euristic Replace the generic visible-content wait (and its rendered_content_timeout input) with an optional waitForSelectors field on url_configs entries, per review. Each listed selector must become visible before the scan runs.
|
Good call - reworked it in 59f8e50 to your url_configs suggestion. Each entry can now carry an optional waitForSelectors array; the scanner waits for each selector to be visible before running Axe, which is the web-assertion readiness the Playwright docs recommend. The generic rendered-content heuristic and its rendered_content_timeout input are gone. Input validation and tests cover the new field, and a selector that never appears fails that URL's scan the same way any other page error does, so a not-ready page can't produce false findings silently. |
Summary
The scanner navigated with
page.goto(url)and ran axe immediately, so single-page apps that render their content after initial load were scanned against a near-empty DOM, producing misleading "no violations" results (#201). This waits for the page to reachnetworkidlebefore scanning.Changes
page.goto(url), awaitpage.waitForLoadState('networkidle', { timeout: 30000 })so client-rendered content is present before axe runs.core.warningand the scan proceeds rather than failing the run, preserving current behavior for those sites while fixing the common SPA case.Testing
vitest run tests/findForUrl.test.ts— 7 passed, including a new case asserting the scan still runs (with a warning) whenwaitForLoadStatetimes out, and that the wait is awaited before findings are collected.Closes #201
AI was used for assistance.