Skip to content

Contrast sweep

tools/contrast-sweep crawls the live gevell.com, pdf.gevell.com and help.gevell.com, public and signed-in, in light and dark mode. It screenshots every screen and measures text contrast (1.4.3), form-control boundaries (1.4.11), focus visibility (2.4.7), focus-indicator contrast (1.4.11) and focus not obscured (2.4.11). Usage, auth modes and flags: tools/contrast-sweep/README.md.

How it relates to the a11y CI gate

Accessibility CI runs axe on each app’s routes in light and dark mode against a local build, per PR. The sweep is the complement:

a11y CI gateContrast sweep
Runs againstlocal build, per PRproduction, nightly
Text axe can’t decide (gradients, overlays, pseudo-elements)not measuredmeasured by pixel comparison
Form-control borders, focus rings, focus obscurednoyes
apps/home (gevell.com)not coveredcovered
Signed-in screensmocked fixture dataa real account holding seeded documents (or yours via JWT)
Record of decisionsallowlist per apptools/contrast-sweep/decisions.json

Nightly run

.github/workflows/contrast-sweep.yml, 07:30 UTC, also manually dispatchable (site, signed-in on/off). It runs on the self-hosted theaccessible-test runner: Cloudflare answers GitHub-hosted runner IPs with 403 on every page, which is why the first nightly run measured nothing (#3793).

It signs in as [email protected] (CONTRAST_SWEEP_* secrets), a dedicated account holding five seeded documents (#3794; see β€œSeeded documents” in the tool README). --require-auth --require-fixtures means missing secrets or a missing fixture break the run instead of silently sweeping empty or signed-out screens. If the fixtures are gone (the account was cleaned up, say), re-run npm run seed. Outputs:

  • job summary: the verdict, coverage totals (screens measured / errored / skipped, elements measured and not measurable) and the undecided failures;
  • artifact contrast-report-<run id> (90 days): index.html with evidence crops, full-page screenshots of every screen in both modes, report.json;
  • on a scheduled run with undecided failures (exit 1), an untrustworthy run (exit 2: rejected or expired session, a site with no measured screen, over 25% of captures errored), or a job that died before the sweep finished, a contrast-regression issue is opened, or commented on if one is open.

Handling a finding

  1. Open the artifact’s index.html and look at the evidence crop. It is the exact region measured, with the element outlined in red.
  2. If it is real, fix it. Once merged but not yet deployed, add a fix-pending decision with an expires date so the nightly doesn’t keep paging.
  3. If the tool is wrong, add a false-positive decision. The reason must say how you checked (computed style, manual measurement).
  4. If we are knowingly shipping it, add an accepted decision with the reason and an issue link.
  5. Remove decisions the report lists as unused. They matched nothing, so the issue is gone.

git log -p tools/contrast-sweep/decisions.json is the audit trail of who decided what and when.

Running it as a specific person

To see what a real customer account sees (their files, their teams), run it locally with that person’s access token. See the README section β€œAs yourself”. Only the access token is used, never the refresh token, so the person’s own session is not disturbed. Their report contains screenshots of their account, so keep it out of public issues.

AAA tier

--level aaa adds 1.4.6 Contrast (Enhanced) and 2.4.13 Focus Appearance, plus axe’s other AAA rules as advisories (#3764). It is report only: AAA findings appear in their own report section, grouped by colour pair, and go through decisions.json, but never change the exit code. AA stays the gate because it is what Title II requires and what our public claims say. The nightly job runs AA only. Promoting an AAA criterion to the gate is a deliberate decision, made per criterion. Run it by hand to size the gap:

Terminal window
npm run sweep -w @accessible-pdf/contrast-sweep -- --no-auth --level aaa

The AAA criteria that can’t be automated are listed as a manual checklist in the README and in every AAA report.

Known limits

Hover states, click-to-open menus and dialogs, text in images, icons and charts (1.4.11 is only checked for form controls and focus indicators), and anything past --max-pages. A clean run is evidence, not a conformance claim.