CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/mobile-a11y-test-author

Authors native mobile accessibility tests covering iOS (Accessibility Inspector, XCUITest `performAccessibilityAudit()` introduced in iOS 17, VoiceOver label/trait/hint verification) and Android (Espresso `AccessibilityChecks.enable()`, Accessibility Scanner, TalkBack traversal, `contentDescription` labelling) with WCAG-aligned checks for element labels, 44pt/48dp touch targets, contrast ratios, and focus order. Use when an iOS or Android app needs automated and manual accessibility test coverage beyond what `xcuitest-suite` or `espresso-suite` provide.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

ios-audit.mdreferences/

iOS accessibility test detail

Extends the core performAccessibilityAudit() example in ../SKILL.md. The audit is available from iOS 17 and fails the test automatically when it finds an issue, so no explicit assertion is needed (wwdc23).

Scope the audit to specific categories

try app.performAccessibilityAudit(for: [.dynamicType, .contrast])

Pass an XCUIAccessibilityAuditType option set. Documented audit types include .dynamicType and .contrast; passing no argument runs all available checks (wwdc23).

Suppress known false positives

try app.performAccessibilityAudit(for: [.contrast]) { issue in
    // Ignore the decorative watermark label (no contrast fix planned)
    if let element = issue.element,
       element.label == "WatermarkLabel",
       issue.auditType == .contrast {
        return true   // suppress this issue
    }
    return false
}

The closure receives an XCUIAccessibilityAuditIssue; return true to suppress. Narrow suppressions by both auditType and element.label so real regressions still fail (wwdc23).

Cover each screen

Each call inspects only the currently visible elements. Navigate to every distinct screen and re-run the audit:

func testCheckoutFlowAudit() throws {
    let app = XCUIApplication()
    app.launch()
    try app.performAccessibilityAudit()          // Screen 1
    app.buttons["place-order-button"].tap()
    try app.performAccessibilityAudit()          // Screen 2
}

VoiceOver label, trait, and hint checks

accessibilityLabel is the localized string VoiceOver reads to identify an element, accessibilityHint describes the action result, and accessibilityTraits communicates purpose - common values are .button, .link, .header, .image, .staticText, .adjustable (uia). Production code sets them; XCUITest verifies via XCUIElement.label:

// Production (UIKit)
let submitButton = UIButton()
submitButton.accessibilityLabel = "Submit order"
submitButton.accessibilityHint  = "Places your order and charges the saved card"
submitButton.accessibilityTraits = [.button]
// Test
func testSubmitButtonLabel() {
    let btn = XCUIApplication().buttons["Submit order"]
    XCTAssertTrue(btn.exists, "VoiceOver cannot find the Submit button")
    XCTAssertEqual(btn.label, "Submit order")
}

Touch target size (44pt minimum)

The minimum control size on iOS and iPadOS is 44x44 pt (hig). Assert via XCUIElement.frame:

func testSubmitButtonTouchTarget() {
    let frame = XCUIApplication().buttons["Submit order"].frame
    XCTAssertGreaterThanOrEqual(frame.width,  44, "Touch target width below 44pt")
    XCTAssertGreaterThanOrEqual(frame.height, 44, "Touch target height below 44pt")
}

Accessibility Inspector (manual complement)

Open it from Xcode menu > Open Developer Tool > Accessibility Inspector. It runs the same checks as performAccessibilityAudit() interactively on real devices and simulators - use it to diagnose an audit failure before writing a suppression (wwdc23).

References

SKILL.md

tile.json