KMPwright

Accessibility audits

Run the bundled axe-core 4.14.0 against the current page with accessibilityAudit and read violations, incomplete checks and frame coverage.

page.goto("https://example.com/signup")
val report = page.accessibilityAudit(tags = setOf("wcag2a", "wcag2aa"), selector = "main")

println("axe ${report.axeVersion}: ${report.counts.violations} violations")
for (issue in report.violations) {
    println("[${issue.impact}] ${issue.id}: ${issue.help} (${issue.nodeCount} nodes)")
    issue.nodes.forEach { println("  ${it.target} ${it.failureSummary}") }
}
check(report.violations.none { it.impact == "critical" })

page.accessibilityAudit(...) injects axe-core into the page, runs it, and returns a typed report. The engine ships inside KMPwright (unmodified axe-core 4.14.0, generated into kmpwright-core/.../generated/AxeBundle.kt), so nothing is downloaded at runtime and the page needs no network access.

This guide covers rule-based audits. For the accessibility tree that agents use to find elements (@e3 refs), see Snapshots.

Playwright has no built-in audit. Its docs suggest the @axe-core/playwright package, and accessibilityAudit covers the same use. The engine is injected privately: window.axe is left as the page had it.

Parameters

Prop

Type

Report

AccessibilityAuditReport:

FieldTypeMeaning
urlStringDocument URL as axe reported it.
axeVersionStringEngine version (4.14.0).
countsAccessibilityAuditCountsRule counts by outcome: violations, incomplete, passes, inapplicable.
violationsList<AccessibilityAuditIssue>Rules that failed.
incompleteList<AccessibilityAuditIssue>Rules axe could not decide and that need manual review.
coverageAccessibilityAuditCoverageauditedFrames: Int and skippedFrames: List<String>.

Only violations and incomplete are listed in detail. Passing and inapplicable rules appear only in counts.

AccessibilityAuditIssue:

FieldTypeMeaning
idStringaxe rule id, such as color-contrast or label.
impactStringminor, moderate, serious or critical; unknown when axe gives none.
helpStringShort description of the rule.
helpUrlStringDeque University page for the rule.
tagsList<String>Rule tags (wcag2aa, cat.color, ...).
nodeCountIntTotal number of affected elements.
nodesList<AccessibilityAuditNode>Up to maxNodesPerRule elements.

AccessibilityAuditNode:

FieldTypeMeaning
targetJsonArrayaxe's selector path for the element. Nested arrays step into shadow roots or frames.
htmlStringElement HTML, cut to 300 characters.
failureSummaryStringaxe's explanation of what to fix, cut to 16 KiB.

The report classes are @Serializable, so you can write them out with kotlinx.serialization for CI artifacts.

Backends and frames

BackendaccessibilityAudit capabilityFrames
JVM (CDP)SupportedAudits the top document and its iframes, each in its own isolated world, then merges the results with axe's runPartial/finishRun. selector applies to the top document only. Frames that can't be reached are listed in coverage.skippedFrames with the reason. Throws when there are more than 128 frames or more than 32 MiB of partial results.
Android WebViewPartialAudits the main document only. Every iframe/frame is listed in coverage.skippedFrames by id, src or (unnamed frame).
iOS WKWebViewPartialSame as Android.

Errors

  • KmpwrightTimeoutException when the audit exceeds timeoutMs.
  • KmpwrightException when KmpwrightConfig.allowDomExtraction or allowEval is false, when the report is larger than 4 MiB, when the selector is invalid or matches nothing, or when the page has a locked (non-configurable) window.axe property.
  • IllegalArgumentException for a non-positive timeout or maxNodesPerRule outside 1..100.

Audits are checked against ActionType.Evaluate in the page's action policy. Concurrent audits on the same page run one at a time.

On this page