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 gate | Contrast sweep | |
|---|---|---|
| Runs against | local build, per PR | production, nightly |
| Text axe canβt decide (gradients, overlays, pseudo-elements) | not measured | measured by pixel comparison |
| Form-control borders, focus rings, focus obscured | no | yes |
| apps/home (gevell.com) | not covered | covered |
| Signed-in screens | mocked fixture data | a real account holding seeded documents (or yours via JWT) |
| Record of decisions | allowlist per app | tools/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.htmlwith 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-regressionissue is opened, or commented on if one is open.
Handling a finding
- Open the artifactβs
index.htmland look at the evidence crop. It is the exact region measured, with the element outlined in red. - If it is real, fix it. Once merged but not yet deployed, add a
fix-pendingdecision with anexpiresdate so the nightly doesnβt keep paging. - If the tool is wrong, add a
false-positivedecision. The reason must say how you checked (computed style, manual measurement). - If we are knowingly shipping it, add an
accepteddecision with the reason and an issue link. - 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:
npm run sweep -w @accessible-pdf/contrast-sweep -- --no-auth --level aaaThe 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.