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 | Attribute | WebdriverIO selector |
|---|
| Android | content-desc | driver.$('~your-label') |
| iOS | accessibilityIdentifier / accessible name | driver.$('~your-id') |
Quick reference
| Goal | How to do it |
|---|
| Prove an element is on the accessibility layer | Locate it with driver.$('~accessibility-id') |
| Assert a screen is navigable via accessibility semantics | Use ~ selectors throughout the full flow |
| Assert an icon button has a meaningful label | getAttribute('content-desc') and check length |
| Assert an error or toast is readable | await 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");
}
});
},
);