github / github/accessibility-scanner

Scanner does not wait for client-side rendering before running axe scan

Open
#201 2 comments 2 reactions 0 assignees View on GitHub
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 `

` instead of the actual page content.

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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.