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
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Native mobile accessibility testing spans two complementary layers:
This skill covers both layers on iOS and Android. Full per-platform detail lives in references/ios-audit.md and references/android-checks.md.
Nearest neighbors and differentiation:
xcuitest-suite - general XCUITest UI automation; does not cover
performAccessibilityAudit, VoiceOver traits, or touch-target checks.espresso-suite - general Espresso UI automation; does not wire
AccessibilityChecks, Accessibility Scanner, or TalkBack workflows.performAccessibilityAudit() (iOS 17+) audits the current view and fails the test
automatically if any issue is found - no explicit assertion needed (wwdc23).
import XCTest
final class HomeAccessibilityTests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = true // report ALL issues per screen, not just the first
XCUIApplication().launch()
}
func testHomeScreenAudit() throws {
try XCUIApplication().performAccessibilityAudit()
}
}Scoped audits, false-positive suppression, audit-per-screen, VoiceOver label/trait/hint checks, 44pt touch targets, and Accessibility Inspector are in references/ios-audit.md.
AccessibilityChecks.enable() fires the Accessibility Test Framework on every
Espresso perform() call; setRunChecksFromRootView(true) checks the whole
hierarchy, not just the interacted view (atf). Requires the
espresso-accessibility dependency.
import androidx.test.espresso.accessibility.AccessibilityChecks
@RunWith(AndroidJUnit4::class)
class CheckoutAccessibilityTest {
init {
AccessibilityChecks.enable().setRunChecksFromRootView(true)
}
@Test
fun applyPromoCode() {
onView(withId(R.id.promo_field)).perform(typeText("WELCOME10"), closeSoftKeyboard())
onView(withId(R.id.apply_button)).perform(click())
// checks fire automatically on every perform()
}
}The dependency, suppression, 48dp touch targets, contrast thresholds,
contentDescription labelling, and the TalkBack manual workflow are in
references/android-checks.md.
jobs:
a11y-audit:
runs-on: macos-15
steps:
- uses: actions/checkout@v5
- run: |
xcodebuild test \
-project MyApp.xcodeproj \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 15,OS=latest' \
-only-testing MyAppUITests/HomeAccessibilityTests \
-resultBundlePath A11yResults.xcresult
- uses: actions/upload-artifact@v4
if: always()
with:
name: a11y-xcresult
path: A11yResults.xcresultjobs:
a11y-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
script: ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.CheckoutAccessibilityTest
- uses: actions/upload-artifact@v4
if: always()
with:
name: a11y-test-results
path: app/build/outputs/androidTest-results| Anti-pattern | Why it fails | Fix |
|---|---|---|
continueAfterFailure = false in audit tests | Stops after the first issue; misses the rest on the same screen | Set continueAfterFailure = true for audit tests (wwdc23) |
| No suppression closure; globally ignoring an audit type | Hides all failures of that type, not just the known one | Suppress by both auditType and element.label (references/ios-audit.md) |
Setting accessibilityLabel to the element type | "Submit button" - VoiceOver already announces "button" from traits | Set label to purpose only: "Submit order" (uia) |
contentDescription on every Text composable | Redundant; TalkBack reads text content automatically | Omit for plain text; set only for icon-only or image elements (cd) |
Running AccessibilityChecks without setRunChecksFromRootView(true) | Checks only the interacted view; off-screen violations pass | Enable root-view mode (Android core, above) (atf) |
| Manual TalkBack only, no automated checks | Inconsistent; regressions slip in on refactors | Pair TalkBack manual review with AccessibilityChecks in CI |
performAccessibilityAudit requires iOS 17+. For pre-17 targets write
explicit XCUIElement.label and frame assertions (see
references/ios-audit.md).AccessibilityChecks fires on perform() only. Views that are visible but
never interacted with in a given test are not checked unless
setRunChecksFromRootView(true) is set.performAccessibilityAudit(), audit types, per-screen guidance, continueAfterFailure = true recommendation, suppression closure.accessibilityLabel, accessibilityHint, accessibilityTraits.AccessibilityChecks.enable(), setRunChecksFromRootView(true), setSuppressingResultMatcher(), per-action firing.contentDescription: purpose not visual detail, null for decorative, unique labels in lists.xcuitest-suite, espresso-suite - functional UI automation; not accessibility-focused.