github / github/accessibility-scanner
Scanner does not wait for client-side rendering before running axe scan
- Dominant language
- TypeScript
- Stars
- 369
- Forks
- 40
- Avg merge
- 1d 9h
- Merged PRs (30d)
- 10
Description
## Problem
When scanning single-page applications (React, Vue, Angular, etc.), the scanner runs the axe scan immediately after `page.goto()` resolves (which waits for the `load` event). At that point, the JavaScript bundles are loaded but the framework hasn't finished rendering the DOM yet. This means axe scans a nearly-empty `
This leads to:
- **False positives**: Document-level violations like `landmark-one-main` and `page-has-heading-one` are reported because the landmarks and headings haven't been rendered yet.
- **False negatives**: Element-level violations like `button-name` are missed because the elements don't exist in the DOM yet.
- **Misleading screenshots**: Screenshots are taken *after* axe runs (inside `addFinding`), by which time React has finished rendering. So the screenshots show the correct, fully-rendered page — even though axe scanned a different DOM state.
## Steps to reproduce
1. Set up the scanner against any React/SPA application
2. Run a scan on a page that has proper `` landmarks and `
` headings rendered by the framework
3. Observe that `landmark-one-main` and `page-has-heading-one` violations are reported
4. Run axe dev tools manually in the browser on the same page — these violations are not found
5. Observe that element-level violations found by axe dev tools (e.g. `button-name`) are not reported by the scanner
## Root cause
In [`findForUrl.ts`](https://github.com/github/accessibility-scanner/blob/main/.github/actions/find/src/findForUrl.ts):
```ts
await page.goto(url)
// axe runs immediately — no wait for client-side rendering
const rawFindings = await new AxeBuilder({page}).analyze()
```
`page.goto()` resolves on the `load` event, which fires when HTML/CSS/JS resources are loaded — but before the JS framework has executed and rendered the DOM.
## Suggested fix
Add a wait for the page to be idle before running the axe scan. For example:
```ts
await page.goto(url)
await page.waitForLoadState('networkidle')
// or: await page.waitForTimeout(2000)
// or: await page.waitForFunction(() => document.querySelector('[data-testid]') !== null)
const rawFindings = await new AxeBuilder({page}).analyze()
```
`waitForLoadState('networkidle')` waits until there are no network connections for at least 500ms, which is a reasonable heuristic for "the SPA has finished its initial API calls and rendered."
## Environment
- `github/accessibility-scanner@v2` (SHA: `7866232dda98e447fed8ec0d7798b322d888fd27`)
- React 19 application with Mantine UI, served from Docker containers via Caddy
- Authenticated via `auth_context` input with session cookies
Contributor guide
Assessment
This issue has not been assessed yet.