pour engine
The engine, documented.
pour engine is the audit engine inside every pour tool: a WCAG 2.2 engine written from the spec, with zero dependencies. It runs entirely in the browser and returns structured, per-rule results with the exact failing elements and the evidence for each verdict. It's open source on GitHub and published to npm, so you can run the same audit in your own tests and tools.
86 success criteria in WCAG 2.2
82 automated rules in pour engine
52 criteria that still need a human. We hand you the checklist
Install
npm install pour-engine
Source at github.com/pourdev/pour-engine, package at npmjs.com/package/pour-engine. The same engine ships inside the extension and the bookmarklet.
Quick start
The engine audits a live DOM: the top document, an iframe's document,
or anything with querySelectorAll. One call, one report.
import { run, name, version } from 'pour-engine';
const results = await run(document, {
// Tags are cumulative: WCAG 2.2 AA means every A and AA rule
// from 2.0 through 2.2. Omit tags to run everything.
tags: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22a', 'wcag22aa'],
});
for (const violation of results.violations) {
console.log(violation.id, violation.impact, violation.nodes.length);
}
Where it runs
Anywhere a real browser renders a DOM. The engine ships inside the extension and the bookmarklet, and the same package drives headless browsers: point Puppeteer or Playwright at any URL, import the engine into the page, and audit. pour's own release checks run it in both Chromium and Firefox this way, headless, which makes CI a two-line job. Or skip the wiring entirely: the command line does it in one.
// npm i pour-engine puppeteer
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'load' });
const report = await page.evaluate(async () => {
const { run } = await import('https://unpkg.com/pour-engine@1/engine/index.js');
const { violations } = await run(document, { tags: ['wcag2a', 'wcag2aa'] });
return violations.map((v) => ({ id: v.id, impact: v.impact, count: v.nodes.length }));
});
await browser.close();
console.table(report);
Playwright is the same shape: swap the import and
chromium.launch(). Both are complete, runnable files in
the package:
pour-engine/examples
(Puppeteer, Playwright, and a plain-browser page). One honest
constraint: results come from computed styles and real layout, and
contrast is measured rather than guessed, so the engine needs a real
rendering engine, headless included. A DOM emulator without layout
is not a supported target.
API
run(context, options, onProgress) returns a promise for the full report.
| Option | Type | What it does |
|---|---|---|
tags |
string[] |
Filter rules by WCAG version and level tags, for example wcag22aa. Empty or absent runs every rule. |
exclude |
string |
CSS selector for elements to leave out of the audit, including their subtrees. |
signal |
AbortSignal |
Stops the run at the next rule boundary and throws an AbortError. |
The optional onProgress callback fires before and after
each rule with the rule id, counts so far, and timing, which is what
drives the live progress UI in pour tools.
| Result field | What's in it |
|---|---|
violations |
Rules that failed. Each carries its WCAG criterion, severity, help text, and per-element nodes with a CSS path and HTML snippet. |
passes |
Rules that ran and found nothing wrong, with how many elements they checked. |
incomplete |
Findings the engine cannot judge conclusively. Returned for a human, never guessed at. |
inapplicable |
Rules with nothing to test on this page. |
manualReview |
The criteria in your chosen scope that no tool can verify, as a checklist. Nothing silently skipped. |
ruleTimings |
Per-rule wall time, so slow rules have nowhere to hide. |
How it's built
-
Spec-first
Every rule and helper was written from the W3C specifications (WCAG, ARIA, accname). When a judgment call comes up, it's settled by reading the spec.
-
Zero dependencies
The engine ships nothing but its own code, so it runs anywhere a DOM exists: content scripts, bookmarklets, test harnesses. No bundling baggage, no supply-chain surface.
-
No false authority
A result is a violation only when the spec says so. Everything uncertain lands in
incompleteor the manual-review checklist, never a guess dressed up as a finding.
~4× faster than axe-core® on the typical page
9.5× faster across the whole 138-page benchmark
2× the failing elements found
Benchmarked
Measured August 2026 across 138 live pages: three interleaved cold runs per engine on the same loaded DOM, medians reported, so neither engine benefits from a warm cache. The typical page audits in tens of milliseconds, around four times faster than axe-core®, and the gap widens with the page: more than nine times faster across the whole set, and twenty-one times on the heaviest page tested, at 190,000 DOM nodes.
In the same run the engine reported twice the failing elements (21,179 to 10,700) while sending just over a third as many findings to human review (7,282 to 20,870): a verdict it can prove is a verdict it gives. Every disagreement family between the two engines is adjudicated element-by-element against the spec before a number is quoted here.
axe® and axe-core® are registered trademarks of Deque Systems, Inc., which is not affiliated with and does not endorse pour. The figures are our own measurements.