Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

11 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Sightline

axe-core tests 1 of the 9 success criteria WCAG 2.2 added. Sightline tests 8.

PyPI License: MIT Python 3.9+ Playwright Tests Status: beta

πŸ‡©πŸ‡ͺ Deutsche Fassung

Sightline is an accessibility auditor for the WCAG 2.2 criteria that static DOM analysis structurally cannot answer: whether a sticky header covers the focused element, whether a focus indicator is actually visible, whether a control is drag-only, whether a password field silently blocks paste.

It drives a real browser through Playwright, measures pixels and geometry, and reports the numbers behind every verdict.

Sightline terminal output


The problem

Almost every automated accessibility check in the industry runs on axe-core. Lighthouse depends on it (axe-core: ^4.12.1), so does Pa11y, and so do jest-axe, @axe-core/playwright and axe DevTools by construction.

axe-core injects a script into the page and inspects the DOM and computed styles. That is the right tool for alt text, ARIA validity, heading order and text contrast β€” and it is excellent at them.

But WCAG 2.2 (W3C Recommendation, revised 12 December 2024) added nine success criteria, and most of them are not questions the DOM can answer. They are questions about layout geometry, rendered pixels, which event handlers a component registered, and how several pages compare to one another.

Check it yourself. Every WCAG tag axe-core emits is listed in doc/rule-descriptions.md:

curl -s https://raw.githubusercontent.com/dequelabs/axe-core/develop/doc/rule-descriptions.md \
  | grep -oE "wcag[0-9a-z]+" | sort -u

The complete set contains exactly one WCAG 2.2 tag: wcag258, on the single target-size rule, marked "needs review". There is no wcag2411, no wcag2413, no wcag257, no wcag326, no wcag337, no wcag338.

So a team can have a green accessibility pipeline and still fail eight of the nine criteria the standard added β€” including four at Level AA, which the European Accessibility Act regime and EN 301 549 both point at.

That gap is what Sightline fills. It is not a replacement for axe-core. Run both.


What it checks

SC Criterion Level In axe-core? How Sightline decides
2.4.11 Focus Not Obscured (Minimum) AA ❌ Tabs through the page for real; at each stop hit-tests a grid of points with elementFromPoint and reports the fraction covered
2.4.12 Focus Not Obscured (Enhanced) AAA ❌ Same measurement, fails on any real coverage
2.4.13 Focus Appearance AAA ❌ Screenshots each focus stop unfocused and focused, decodes and diffs the pixels, checks area against a 2px perimeter and contrast against 3:1
2.5.7 Dragging Movements AA ❌ Wraps addEventListener before navigation to see which elements registered a pointer-down/pointer-move pair with no keyboard path
2.5.8 Target Size (Minimum) AA ⚠️ one rule, "needs review" 24Γ—24 px, with the spacing exception implemented as real 24px-circle intersection and the inline exception requiring genuine non-target text
3.2.6 Consistent Help A ❌ Locates help mechanisms in twelve languages across several pages and compares relative order and containing landmark
3.3.8 Accessible Authentication (Min) AA ❌ Dispatches a real cancelable paste event to detect paste blocking; checks credential autocomplete; detects CAPTCHAs
3.3.9 Accessible Authentication (Enhanced) AAA ❌ Same, without the object-recognition exception
3.3.7 Redundant Entry A ❌ Deliberately not implemented β€” see Honest limitations

Install

pip install sightline-a11y
python -m playwright install chromium

Or from source:

git clone https://github.com/Ax1zz/sightline.git
cd sightline
pip install -e ".[dev]"
python -m playwright install chromium

Quick start

# Audit a page
sightline https://example.com

# Level AA only, with a standalone HTML report you can attach to an audit
sightline https://example.com --level AA --html report.html

# Several pages: this additionally enables 3.2.6 Consistent Help
sightline https://example.com/ https://example.com/pricing https://example.com/contact

# Machine-readable output for CI
sightline https://example.com --json - | jq '.counts'

Exit code is 1 when anything fails, 0 otherwise, so it drops straight into a pipeline. --exit-zero reports without failing the build; --fail-on-review is stricter and also fails on review items.

HTML report

Use it inside an existing Playwright test

The CLI navigates for you. If you already have a page β€” behind a login, mid-flow, three modals deep β€” hand it over directly:

from playwright.sync_api import sync_playwright
from sightline import audit, instrument

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()

    instrument(page)                 # before navigating, for 2.5.7
    page.goto("https://app.example.com/login")
    page.fill("#email", "demo@example.com")
    page.fill("#password", "hunter2")
    page.click("button[type=submit]")

    report = audit(page, levels=["A", "AA"])

    for finding in report.violations:
        print(finding.sc, finding.target, finding.message)

    assert not report.violations, report.to_json()

audit() returns a Report; every Finding carries sc, level, outcome, impact, target, message, help_url and a detail dict holding the raw measurements β€” pixel counts, contrast ratios, coverage fractions, dimensions β€” so a human can check the tool's arithmetic.

GitHub Actions

- uses: actions/setup-python@v5
  with: { python-version: '3.12' }
- run: pip install sightline-a11y && python -m playwright install --with-deps chromium
- run: sightline ${{ env.PREVIEW_URL }} --level AA --html a11y.html
- uses: actions/upload-artifact@v4
  if: always()
  with: { name: accessibility-report, path: a11y.html }

Alternatives

Sightline axe-core / Lighthouse / Pa11y Manual audit
Focus obscuring (2.4.11/2.4.12) measured not covered yes, slowly
Focus appearance (2.4.13) measured from pixels not covered yes, subjective
Dragging (2.5.7) detected, flagged for review not covered yes
Target size (2.5.8) full exception logic one rule, needs review yes
Consistent help (3.2.6) compared across pages not covered yes
Accessible auth (3.3.8) paste actively probed not covered yes
Alt text, ARIA, contrast, headings not covered β€” use axe-core excellent yes
Runs in CI yes yes no
Cost per run free free hours of a specialist

The honest summary: axe-core owns the DOM layer, Sightline owns the geometry-and-behaviour layer, and neither replaces a human for the criteria that need judgement.


Honest limitations

Automated testing cannot prove conformance, and a tool that pretends otherwise is worse than no tool. Sightline is explicit about where it stops:

  • 3.3.7 Redundant Entry is not implemented. It requires modelling a multi-step process and knowing what the user already supplied. Any automated verdict would be a guess, so there isn't one.
  • Heuristic checks report review, never fail. 2.5.7 can tell you a component is drag-operated; it cannot know whether an equivalent button exists elsewhere on the page.
  • The 2.5.8 "equivalent control" and "essential" exceptions are not detectable. Failures say so.
  • 3.2.6 approximates. Relative order is compared among help mechanisms and their landmarks, not against every piece of page content.
  • 2.4.13 reads the rendered result. A focus indicator drawn with a background image, a pseudo-element or an SVG is measured the same way as an outline β€” which is the point β€” but an animated indicator can be captured mid-transition. Screenshots are taken with animations disabled to limit this.
  • Only what is reachable by Tab is examined. Controls behind a menu you have to open first need you to drive the page and call audit(page).
  • The 3.3.8 paste probe dispatches a real event at credential fields, which fires the page's own handlers. Use --no-paste-probe against production.

How it works

CLI / Python API
      β”‚
   Auditor ──── instrument(page)   init script, must precede navigation
      β”‚
  Check registry ──── each check: run(page, ctx) -> [Finding]
      β”‚
  Primitives:  png.py     dependency-free PNG decoder (zlib only)
               color.py   sRGB luminance, WCAG contrast ratio
               dom.py     hit testing, geometry, listener capture
      β”‚
  Reporters:  text β”‚ json β”‚ standalone HTML

The only runtime dependency is Playwright. The PNG decoder is hand-written rather than pulling in Pillow for one narrow job; there are no network calls, no telemetry and no model inference anywhere in the audit.

Development

pip install -e ".[dev]"
python -m playwright install chromium
pytest -q

The test suite runs against two fixtures: tests/fixtures/failing.html plants a defect for each criterion, and passing.html is the same UI done accessibly. Every check is asserted in both directions β€” a checker that fires on everything is as useless as one that fires on nothing.

Roadmap

  • Async Playwright API alongside the sync one
  • pytest plugin: assert_wcag22(page)
  • Reusable GitHub Action
  • SARIF output for code scanning
  • Baseline files, so existing debt can be accepted and only regressions fail
  • Firefox and WebKit (the checks are engine-agnostic; only capture is tuned)
  • EN 301 549 clause mapping in the HTML report

Contributing

New checks, better exception logic and real-world false-positive reports are all welcome β€” a false positive is a bug here, not a rounding error. See CONTRIBUTING.md.

License

MIT

About

The WCAG 2.2 accessibility checks axe-core cant do: focus obscuring, focus appearance, target size, dragging, accessible authentication. Playwright-native, MIT.

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages