Back to projects

Case Study

bfsg-scanner

An accessibility scanner for WCAG 2.1 AA / EN 301 549 / BFSG

Open-source side project: concept, architecture, implementation, tests, CI/CD, release, and documentation — single developer.

Role
Solo developer & maintainer
Timeframe
2026 — v0.1.0 published to npm on September 1, 2026; v0.2.0 on September 10, 2026; v0.2.2 on September 29, 2026 published and working: npx bfsg-scanner <url>

Context / problem

Since June 28, 2025 the BFSG (Barrierefreiheitsstärkungsgesetz) has made digital accessibility a legal requirement in Germany for many B2C businesses: e-commerce, banking, bookings, ticketing. Anyone who builds or operates these sites has to be able to demonstrate conformance.

The existing tools (axe, Lighthouse) return rule IDs, not legal citations, and analyze one page at a time. bfsg-scanner scans a whole site and maps every finding to the EN 301 549 / BFSG clause it breaks, producing a citable compliance report in JSON, HTML, or PDF.

What it does

  1. Discovers the site's pages — sitemap.xml first, then a breadth-first crawl that respects robots.txt, with a configurable page limit.
  2. Analyzes every page in a real Chromium via Playwright + axe-core, with a concurrency pool, per-host rate limiting, an identifiable User-Agent, and an optional settle delay so client-rendered pages are scanned once hydrated.
  3. Maps each violation to its WCAG 2.1 success criterion and the corresponding EN 301 549 clause — and, via § 4 BFSG, to the statutory presumption of conformity.
  4. Produces the report as report.json (versioned JSON Schema), report.html (DE/EN, self-contained file), and report.pdf (A4, to be archived as a record).
  5. Exits with a non-zero code once violations reach a configurable severity threshold, so a CI pipeline can block a merge on accessibility.

In numbers

  • 153automated tests (Vitest)
  • 48merged pull requests, each with its own task checklist
  • 3operating systems in CI — Linux, Windows, macOS
  • 0·1·2·3·4documented semantic exit codes, with a reasoned hierarchy so CI knows why it stopped
  • JSON · HTML · PDFreport formats, from the same run
  • SLSAprovenance on npm via OIDC trusted publishing — no long-lived token in the release workflow
  • 24violations found on the W3C's deliberately inaccessible demo siteacross 5 pages — 11 critical, 13 serious — each with its WCAG criterion and EN 301 549 clause, in a single PDF
  • WCAG → EN 301 549every finding mapped from its WCAG 2.1 criterion to the EN 301 549 / BFSG clause

Technical highlights

  1. Published to npm with SLSA provenance.

    The release workflow publishes via OIDC trusted publishing — no long-lived token anywhere in it — with npm 2FA on the account.

  2. Small pull requests, each reviewed on its own.

    Every one carries a task checklist; the eleven decisions that shaped the architecture also have an Architecture Decision Record.

  3. 153 tests, CI on three operating systems.

    The Vitest suite runs on Linux, Windows, and macOS on every push.

  4. The output is a stable contract, not an ad-hoc format.

    A versioned JSON Schema describes report.json, guarded by anti-drift tests with ajv so the shape can't silently change.

  5. Semantic exit codes (0/1/2/3/4).

    Documented with a reasoned hierarchy, so a pipeline can tell an accessibility failure apart from a crawl error, a usage mistake, or a broken runner.

  6. Scanning ethics written down.

    docs/SCANNING-ETHICS.md — scan only sites you own or are authorized to test; rate limiting, robots.txt, and an identifiable User-Agent are on by default.

Tech stack

  • TypeScript
  • Node.js 22+
  • Playwright
  • axe-core
  • Zod
  • Vitest
  • Biome
  • GitHub Actions
  • npm (SLSA provenance)
  • ajv
  • JSON Schema

Demonstrated result

Run against the W3C's deliberately inaccessible demo site → 24 violations across 5 pages (11 critical, 13 serious), each with its WCAG criterion and EN 301 549 clause, in a single PDF.

What I learned

  • Real release engineering: provenance, OIDC trusted publishing, npm 2FA, publish-on-tag.
  • Designing the output as a stable contract (versioned JSON Schema), not an ad-hoc format.
  • Controlled concurrency and scanning "netiquette" — rate limiting, robots.txt, an identifiable UA.
  • Translating legal requirements (BFSG → EN 301 549 → WCAG) into verifiable, tested logic.

Screenshots

  • The self-contained HTML report: target, tool, the "Failed" verdict above the severity threshold, and the scan summary
  • Breakdown by impact, and the breached clauses — every WCAG 2.1 criterion beside its EN 301 549 clause, with the § 4 BFSG note
  • A single finding in detail: rule, impact, WCAG SC, EN 301 549 clause, a how-to-fix link, and the affected elements
  • The repository's pull requests on GitHub — each merged with its own task checklist