Skip to main content
Use accessibility IDs to verify that key screens are reachable and usable by screen readers on iOS and Android. The ~ selector prefix is WebdriverIO shorthand for locating an element by its accessibility ID — this proves the element is correctly exposed to the accessibility layer and keeps your tests stable across visual refactors.
This recipe covers accessibility spot-checking for native mobile. For operationalized a11y monitoring — scheduled runs, trend tracking, aggregated reports, and stakeholder dashboards — talk to your QA Wolf team about full-service accessibility testing.

Examples

Verify a screen’s key elements are reachable via accessibility semantics
const email    = await driver.$("~Email");
const password = await driver.$("~Password");
const loginBtn = await driver.$("~Log in");

await expect(email).toBeDisplayed();
await expect(password).toBeDisplayed();
await expect(loginBtn).toBeEnabled();
If you can’t locate an element using ~, that element is likely missing an accessibility label entirely — an accessibility bug worth raising with your dev team before writing the test.
Assert that icon buttons have meaningful accessible names
const menuIcon = await driver.$("~Main menu");
await expect(menuIcon).toBeDisplayed();

const label = await menuIcon.getAttribute("content-desc");
if (!label || label.trim().length < 3) {
  throw new Error("A11y: menu icon accessible label is missing or too short");
}

When to use

  • Your app has key interactive elements that must be exposed to VoiceOver (iOS) or TalkBack (Android).
  • Your team wants to catch regressions where a developer removed or renamed an accessibility label.
  • Your team wants a lightweight proxy for accessibility hygiene without a full WCAG audit.
  • Your app has icon-only buttons that must have meaningful accessible names.

Platform reference

PlatformAttributeWebdriverIO selector
Androidcontent-descdriver.$('~your-label')
iOSaccessibilityIdentifier / accessible namedriver.$('~your-id')

Quick reference

GoalHow to do it
Prove an element is on the accessibility layerLocate it with driver.$('~accessibility-id')
Assert a screen is navigable via accessibility semanticsUse ~ selectors throughout the full flow
Assert an icon button has a meaningful labelgetAttribute('content-desc') and check length
Assert an error or toast is readableawait expect($('~error-id')).toHaveText('...')
QA Wolf does not run a WCAG rules engine against native app views. Native apps don’t have a DOM, so axe-core style scanning isn’t available for native iOS or Android. For deeper audits — color contrast, touch target size, reading order — supplement these tests with manual testing using VoiceOver on iOS, TalkBack on Android, or the Xcode Accessibility Inspector and Android Studio Layout Inspector.

Full sample test

import { flow } from "@qawolf/flows/android";

export default flow(
  "Accessibility — login screen",
  { target: "Android - Pixel", launch: true },
  async ({ driver, test }) => {
    await test("verify login screen elements are accessible", async () => {
      // Arrange
      await driver.$("~Email").waitForDisplayed({ timeout: 10_000 });

      // Act
      const email    = driver.$("~Email");
      const password = driver.$("~Password");
      const loginBtn = driver.$("~Log in");

      await email.setValue(process.env.USER_EMAIL);
      await password.setValue(process.env.USER_PASSWORD);
      await loginBtn.click();

      // Assert
      await expect(driver.$("~Home")).toBeDisplayed();

      const menuIcon = driver.$("~Main menu");
      await expect(menuIcon).toBeDisplayed();

      const label = await menuIcon.getAttribute("content-desc");
      if (!label || label.trim().length < 3) {
        throw new Error("A11y: menu icon accessible label is missing or too short");
      }
    });
  },
);
Last modified on June 4, 2026