# WCAG Accessibility Scanner (axe-core) (`wolfgang_ai/wcag-accessibility-scanner`) Actor

Crawls a website with headless Chromium, runs axe-core on every page and returns one result per WCAG 2.x A/AA violation with selector, HTML, fix guidance and screenshots. Includes an A–F accessibility grade.

- **URL**: https://apify.com/wolfgang\_ai/wcag-accessibility-scanner.md
- **Developed by:** [Hesling Reidinga](https://apify.com/wolfgang_ai) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 accessibility violation founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## WCAG Accessibility Scanner (axe-core)

Find out in minutes whether a website breaks accessibility rules — and exactly
where. This Actor crawls a site with a real Chromium browser, runs
[axe-core](https://github.com/dequelabs/axe-core) (the same engine behind
Lighthouse and most enterprise accessibility tools) on every page, and returns
**one result per offending element** with the CSS selector, the HTML snippet, a
plain-language explanation, a link to the fix, and a screenshot of the element.
You also get an **A–F accessibility grade** for the site.

### What it does

1. Opens your start URL in headless Chromium (1280×900) and follows same-origin
   links — product, cart, checkout, collection, contact and content pages first —
   up to the number of pages you choose.
2. Runs axe-core against **WCAG 2.0, 2.1 and 2.2 at levels A and AA** on each
   page. Best-practice-only rules are not included, so every finding maps to a
   real WCAG success criterion.
3. Takes a full-page screenshot of every page and an element screenshot for the
   first three offending elements of each rule.
4. Writes one dataset row per violation and a `SUMMARY` record with totals,
   the grade and the top failing rules.

It respects `robots.txt`, scans one page at a time with a polite delay, and
identifies itself as `AccessibilityGradeBot`.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrl` | string (required) | — | The page to start from, e.g. `https://example.com`. |
| `maxPages` | integer | `5` | Maximum pages to scan, including the start URL (1–50). |
| `includeScreenshots` | boolean | `true` | Store page and element screenshots and link them from each result. |

```json
{
  "startUrl": "https://example.com",
  "maxPages": 5,
  "includeScreenshots": true
}
```

### Output

#### Dataset — one item per violation

Each row is one element that fails one rule. Sort or filter by `impact` to
prioritise, group by `ruleId` to plan fixes, or by `pageUrl` to assign work.

```json
{
  "pageUrl": "https://www.w3.org/WAI/demos/bad/before/home.html",
  "pageTitle": "Welcome to CityLights! [Inaccessible Home Page]",
  "ruleId": "image-alt",
  "impact": "critical",
  "wcag": ["wcag2a", "wcag111"],
  "tags": ["cat.text-alternatives", "wcag2a", "wcag111", "section508", "section508.22.a", "EN-301-549", "EN-9.1.1.1", "ACT"],
  "help": "Images must have alternative text",
  "helpUrl": "https://dequeuniversity.com/rules/axe/4.13/image-alt",
  "description": "Ensure <img> elements have alternative text or a role of none or presentation",
  "selector": "#nav > a[href$=\"tickets.html\"] > img",
  "html": "<img src=\"./img/tickets.gif\" width=\"85\" height=\"20\">",
  "failureSummary": "Fix any of the following:\n  Element does not have an alt attribute\n  ...",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/.../records/screenshot-p1-image-alt-1.jpg"
}
```

`impact` is one of `minor`, `moderate`, `serious`, `critical`. `wcag` lists the
WCAG tags (`wcag2a` = level A, `wcag2aa` = level AA, `wcag111` = success
criterion 1.1.1); `tags` keeps every axe tag, including EN 301 549 and
Section 508 references. `screenshotUrl` is present only when a screenshot was
taken for that element (first three elements per rule, visible elements only).

#### Key-value store

- **`SUMMARY`** — the site-level result:

  ```json
  {
    "grade": "F",
    "pagesScanned": 2,
    "violations": 6,
    "nodes": 46,
    "byImpact": { "minor": 0, "moderate": 0, "serious": 12, "critical": 34 },
    "seriousNodes": 46,
    "topRules": [
      { "ruleId": "image-alt", "nodes": 33 },
      { "ruleId": "link-name", "nodes": 7 },
      { "ruleId": "color-contrast", "nodes": 2 }
    ],
    "targetUrl": "https://www.w3.org/WAI/demos/bad/before/home.html",
    "platform": "custom",
    "pages": [
      { "url": "https://www.w3.org/WAI/demos/bad/before/home.html", "title": "...", "httpStatus": 200, "loadMs": 283, "violations": 46, "screenshotUrl": "..." }
    ],
    "errors": [],
    "startedAt": "2026-09-19T21:34:10.000Z",
    "finishedAt": "2026-09-19T21:34:18.000Z"
  }
  ```

  `violations` counts distinct rule failures per page; `nodes` counts offending
  elements (the number of dataset rows). `platform` is a best-effort guess
  (`shopify`, `woocommerce`, `wix`, `squarespace`, `bigcommerce`, `custom`).

- **`RESULT`** — the complete raw scan result (all pages, all rules, all nodes).

- **`page-<n>.jpg`** — full-page screenshot of each scanned page.

- **`screenshot-p<n>-<rule>-<k>.jpg`** — element screenshots.

#### Grade

The grade is based on the number of **serious + critical** offending elements
across the scanned pages:

| Grade | Serious + critical elements |
|---|---|
| A | 0 |
| B | 1–3 |
| C | 4–10 |
| D | 11–25 |
| F | more than 25 |

### Use cases

- **Agencies and freelancers** — run it on a prospect's site before the first
  call and open with concrete, screenshot-backed findings; re-run after the fix
  to prove the improvement.
- **Compliance checks (EAA, ADA, Section 508, AODA)** — the European
  Accessibility Act applies to most consumer-facing e-commerce and services
  since June 2025, and ADA Title III lawsuits over websites keep rising. A
  monthly scan gives you a dated, repeatable record of where you stand.
- **E-commerce and SaaS teams** — schedule the Actor and pipe the dataset into
  Slack, a spreadsheet or your issue tracker; the `selector` and `html` fields
  point developers to the exact element.
- **Procurement and audits** — get a quick, engine-based baseline before
  commissioning a manual audit, so the auditor's time goes to what tools can't
  catch.

### Limits and what this is not

- **Automated testing finds roughly 30–40 % of WCAG issues.** Tools can check
  alt attributes, contrast, form labels, ARIA usage, language attributes and
  similar rules reliably, but they cannot judge whether alt text is meaningful,
  whether keyboard focus order makes sense, or whether video has accurate
  captions. A clean scan is a good sign, not a certificate of conformance.
- One viewport (desktop, 1280×900), no logged-in areas, no interaction with
  cookie walls or modals beyond what loads by default.
- Pages that need JavaScript to render are fine (real Chromium), but pages
  blocked by `robots.txt`, returning 4xx/5xx, or behind bot protection are
  skipped and listed in `SUMMARY.errors`. If no page at all can be scanned the
  run fails with an explanation.
- Up to 50 pages per run; screenshots are capped at 60 per run (page
  screenshots are always kept, element screenshots fill the rest).

### Pricing

This Actor is offered as **pay per result**: you pay a small fixed amount for
each violation row written to the dataset, plus nothing else — a clean site
costs you (almost) nothing to check. The exact per-result price is shown on the
Actor's Apify Store page; with the Apify free plan you can run it without a
card. If the Actor is switched to pay-per-event pricing, each dataset row is
charged as one `violation` event; the amount is set in the Apify Console, not
in the code.

### Support

Something missing — a mobile viewport, authenticated scans, a PDF report? Open
an issue on the Actor's page and describe the site type you are testing.

***

### For developers

This Actor lives in the FullAutoBiz pnpm monorepo as `apify/wcag-scanner` and
wraps the workspace package `@fab/scanner` (Playwright + `@axe-core/playwright`).

**Build strategy: a single esbuild bundle.** The Apify platform builds the
Actor from this directory alone, without the pnpm workspace, so
`scripts/build.mjs` bundles `src/main.ts` together with `@fab/scanner`,
`@fab/contracts`, `axe-core` and `zod` into `dist/main.js`
(`esbuild --bundle --platform=node --format=esm --external:playwright --external:apify`).
Only `apify` and `playwright` stay external: `playwright` ships with the
`apify/actor-node-playwright-chrome:22` base image (with matching browsers),
and `apify` is installed in the Docker build from the generated
`dist/package.json`. The bundle is not minified because axe-core serialises
itself with `Function#toString()` at runtime.

```sh
pnpm install                                        # repo root
pnpm --filter @fab/scanner run setup                # once: playwright install chromium
pnpm --filter @fab/apify-wcag-scanner build         # -> dist/main.js + dist/package.json
cd apify/wcag-scanner
echo '{"startUrl":"https://www.w3.org/WAI/demos/bad/before/home.html","maxPages":2}' \
  > storage/key_value_stores/default/INPUT.json
pnpm exec apify run --purge                         # local run; results in ./storage
```

`dist/main.js` and `dist/package.json` are deliberately not git-ignored in this
directory (see `.gitignore`) because `apify push` uploads the directory as-is
and `.actor/Dockerfile` only copies those two files. Always run `build` before
`apify push`. `storage/` and `apify_storage/` (local run output) are ignored.

# Actor input Schema

## `startUrl` (type: `string`):

The page to start from. The scanner follows same-origin links from here (products, cart, checkout, contact and content pages are prioritised).

## `maxPages` (type: `integer`):

Maximum number of pages to scan, including the start URL.

## `includeScreenshots` (type: `boolean`):

Store a full-page screenshot per page and an element screenshot for the first few offending elements of each rule in the key-value store, and link them from each result.

## Actor input object example

```json
{
  "startUrl": "https://example.com",
  "maxPages": 5,
  "includeScreenshots": true
}
```

# Actor output Schema

## `violations` (type: `string`):

One item per WCAG violation node: page URL, rule, impact, WCAG tags, selector, failing HTML, fix guidance link and element screenshot URL.

## `summary` (type: `string`):

Key-value store record SUMMARY: grade, pages scanned, violation and node counts by impact, top rules.

## `rawResult` (type: `string`):

Key-value store record RESULT: the complete per-page scan result.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrl": "https://www.w3.org/WAI/demos/bad/before/home.html"
};

// Run the Actor and wait for it to finish
const run = await client.actor("wolfgang_ai/wcag-accessibility-scanner").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "startUrl": "https://www.w3.org/WAI/demos/bad/before/home.html" }

# Run the Actor and wait for it to finish
run = client.actor("wolfgang_ai/wcag-accessibility-scanner").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrl": "https://www.w3.org/WAI/demos/bad/before/home.html"
}' |
apify call wolfgang_ai/wcag-accessibility-scanner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wolfgang_ai/wcag-accessibility-scanner"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/FiIdfHLN8wSaBNdC3/builds/1W1f4iToQRHpwHqgD/openapi.json
