axetest

package
v0.41.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 12 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func NewBrowser(t *testing.T) context.Context

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

func NewBrowserContext(parent context.Context) (context.Context, context.CancelFunc, error)

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

func NewTab(t *testing.T, browser context.Context) (context.Context, context.CancelFunc)

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

func Prepare(scheme string) chromedp.Action

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

type ViolatedNode struct {
	HTML   string   `json:"html"`
	Target []string `json:"target"`
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL