Documentation
¶
Overview ¶
Package axetest is the axe-core accessibility-testing harness for GoFastr apps — host applications and the framework's own chromedp suites alike.
It vendors the axe-core engine (testdata/axe.min.js, embedded) and exposes the primitives every a11y gate needs:
- NewBrowser — one chromedp browser context reused across all scans;
- NewTab — a fresh tab per page so the previous page's SSE socket tears down;
- Prepare — the load-bearing color-scheme freeze + force step;
- Scan — injects axe and runs it against the CURRENT DOM state (so a caller can open a modal first and then scan).
Each app keeps its own page list, allowlist, and gate Test function — only the reusable machinery lives here. Derive the page list from the same source your screens register from (a catalog, app.Routes()) so new screens are scanned automatically instead of drifting out of the gate.
Every successful Scan also records the scanned path into the axe-coverage manifest (framework/axecov, .gofastr/axe-coverage.json), which uihost strict mode reads in dev to enforce that every page route has an axe test. GOFASTR_AXE_COVERAGE=0 disables recording.
Index ¶
- Variables
- func NewBrowser(t *testing.T) context.Context
- func NewBrowserContext(parent context.Context) (context.Context, context.CancelFunc, error)
- func NewTab(t *testing.T, browser context.Context) (context.Context, context.CancelFunc)
- func NewTabContext(browser context.Context, timeout time.Duration) (context.Context, context.CancelFunc)
- func Prepare(scheme string) chromedp.Action
- type ScanOption
- type ViolatedNode
- type Violation
Constants ¶
This section is empty.
Variables ¶
var Schemes = []string{"dark", "light"}
Schemes lists the color schemes every page is scanned under, forced via the same <html data-color-scheme> attribute that ui.ThemeToggle flips. Without forcing, the scheme bootstrap follows the host's prefers-color-scheme — a dev machine in Dark appearance only ever audits the dark palette while CI runners (light by default) only audit light, so contrast regressions in the unseen scheme stay invisible locally and surface as CI-only failures.
Functions ¶
func NewBrowser ¶
NewBrowser returns one chromedp browser context shared across all axe runs in a single test. Per-page browsers blow the websocket dial deadline when auditing many pages in a row, so reuse one browser and open fresh tabs with NewTab. The returned context is long-lived (no per-scan timeout child) — a timeout child would kill the browser when it expired and cancel later pages.
func NewBrowserContext ¶
NewBrowserContext is the non-test variant of NewBrowser — the same tuned headless browser for callers without a *testing.T (the `gofastr audit a11y --url` command). The browser is started eagerly; the caller MUST call the returned cancel to tear it down.
func NewTab ¶
NewTab opens a FRESH tab derived from browser (so the previous page's SSE socket is torn down) with a per-tab scan timeout. It returns the tab context and a cancel func the caller MUST defer — cancelling tears down both the timeout and the tab target so sockets don't leak across pages.
func NewTabContext ¶
func NewTabContext(browser context.Context, timeout time.Duration) (context.Context, context.CancelFunc)
NewTabContext is the non-test variant of NewTab. The caller MUST call the returned cancel — it tears down both the timeout and the tab target so sockets don't leak across pages.
func Prepare ¶
Prepare is a chromedp action that freezes CSS transitions/animations and forces the given color scheme on the current document and its UA controls. Run it AFTER navigating (and a brief settle), BEFORE any widget interaction and Scan.
The freeze is load-bearing AND must be a constructed stylesheet: the scheme flip starts 120–160ms color transitions (header links, search pill), and in throttled headless tabs animation frames may never tick, pinning computed colors at the PREVIOUS scheme's values indefinitely — axe then reports phantom mixed-scheme contrast failures on every page. An injected <style> element cannot fix this when the host ships a `default-src 'self'` CSP, which silently blocks inline styles; adoptedStyleSheets is script-created and not subject to style-src, so it bypasses the CSP.
Types ¶
type ScanOption ¶
type ScanOption func(*scanConfig)
ScanOption modifies how Scan configures axe.run(). The nil option is a no-op; construct one with WithDisabledRules or WithEnabledRules.
func WithDisabledRules ¶
func WithDisabledRules(rules ...string) ScanOption
WithDisabledRules passes the given axe rule IDs to axe.run() as disabled (not evaluated at all). Use only when a rule can structurally never apply to the host's surfaces; a ruleAllowlist entry is preferred so the skip stays visible in each app's test source.
func WithEnabledRules ¶
func WithEnabledRules(rules ...string) ScanOption
WithEnabledRules turns ON axe rule IDs that ship disabled-by-default — notably the WCAG 2.2 `target-size` rule (24px minimum tap target, tagged wcag22aa/wcag258), which axe-core evaluates only when explicitly enabled. A caller that passes no options keeps axe's defaults verbatim (target-size stays off), so existing gates are unaffected.
type ViolatedNode ¶
ViolatedNode is one element that tripped a rule.
type Violation ¶
type Violation struct {
ID string `json:"id"`
Impact string `json:"impact"`
Description string `json:"description"`
Help string `json:"help"`
HelpURL string `json:"helpUrl"`
Tags []string `json:"tags"`
Nodes []ViolatedNode `json:"nodes"`
// Scheme is the forced color scheme that produced the violation.
// Set by Scan, not part of the axe JSON payload.
Scheme string `json:"-"`
}
Violation is one axe-core rule violation. Scheme records which forced color scheme produced it; it is set by Scan, not part of the axe JSON.
func Scan ¶
func Scan(ctx context.Context, scheme string, ruleAllowlist map[string]string, opts ...ScanOption) ([]Violation, error)
Scan injects axe-core and runs it once against the CURRENT DOM state, returning allowlist-filtered [Violation]s tagged with scheme. The caller controls navigation, Prepare(scheme), and any widget opening before calling — Scan only measures whatever the page looks like right now, which is what lets a gate open a modal first and then scan the open-widget DOM.
ruleAllowlist maps axe rule IDs to skip (ID → justification) — a Violation for an allowlisted ID is dropped from the result. Behavior modifiers are passed as ScanOption values: WithDisabledRules (a rule that can structurally never apply, e.g. `region` on a fragment demo) and WithEnabledRules (a default-off rule to turn on, e.g. WCAG 2.2 `target-size`). A caller that passes neither gets axe's stock behavior.