61 / 66 · 09 Mobile & Cross-Platform Testing · Mobile Accessibility Testing← prev⊞ allnext →☰ Read as one page
11.2VoiceOver (iOS) and TalkBack (Android)
Screen readers on mobile work differently from desktop screen readers. Users navigate by swiping (linear navigation) or by exploring (touching the screen to hear what is under their finger). Both patterns must work.
Testing Screen Reader Navigation Order
# Testing screen reader accessibility
def test_voiceover_navigation_order(driver):
"""Verify that VoiceOver reads elements in logical order."""
# Enable accessibility inspection
elements = driver.find_elements(AppiumBy.CLASS_NAME, "XCUIElementTypeAny")
# Filter to accessible elements
accessible = [
{
"label": el.get_attribute("label"),
"trait": el.get_attribute("trait"),
"value": el.get_attribute("value"),
"frame": el.rect,
}
for el in elements
if el.get_attribute("accessible") == "true"
]
# Verify reading order follows visual layout (top-to-bottom, left-to-right)
for i in range(len(accessible) - 1):
current = accessible[i]
next_el = accessible[i + 1]
# Next element should be below or to the right
assert (
next_el["frame"]["y"] > current["frame"]["y"] or
(next_el["frame"]["y"] == current["frame"]["y"] and
next_el["frame"]["x"] >= current["frame"]["x"])
), f"Reading order violation: '{current['label']}' before '{next_el['label']}'"
Testing Accessibility Labels
def test_all_interactive_elements_have_labels(driver):
"""Every interactive element must have an accessibility label."""
buttons = driver.find_elements(AppiumBy.CLASS_NAME, "XCUIElementTypeButton")
text_fields = driver.find_elements(AppiumBy.CLASS_NAME, "XCUIElementTypeTextField")
switches = driver.find_elements(AppiumBy.CLASS_NAME, "XCUIElementTypeSwitch")
for element in buttons + text_fields + switches:
label = element.get_attribute("label") or element.get_attribute("name")
assert label and len(label) > 0, \
f"Interactive element at ({element.rect}) has no accessibility label"
# Labels should not be technical identifiers
assert not label.startswith("btn_"), \
f"Label '{label}' looks like a technical ID, not a human description"
assert not label.startswith("ic_"), \
f"Label '{label}' looks like an icon identifier, not a description"
def test_images_have_content_descriptions(driver):
"""All meaningful images must have content descriptions (Android)."""
images = driver.find_elements(AppiumBy.CLASS_NAME, "android.widget.ImageView")
for img in images:
content_desc = img.get_attribute("contentDescription")
# Decorative images can have empty descriptions
# but must explicitly set importantForAccessibility="no"
important = img.get_attribute("importantForAccessibility")
if important != "no":
assert content_desc and len(content_desc) > 0, \
f"Image at ({img.rect}) has no content description"