0
# Accessibility Testing
1
2
iOS accessibility system integration with VoiceOver support, accessibility tree validation, and comprehensive testing utilities for ensuring app accessibility compliance.
3
4
## Capabilities
5
6
### Accessibility Tree Construction
7
8
Functions for building and analyzing the accessibility tree from the UI hierarchy.
9
10
```kotlin { .api }
11
/**
12
* Constructs accessibility tree from current UI hierarchy
13
* @return Root AccessibilityTestNode representing the complete accessibility tree
14
*/
15
internal fun UIKitInstrumentedTest.getAccessibilityTree(): AccessibilityTestNode
16
17
/**
18
* Normalizes accessibility tree by removing non-essential elements
19
* @return Normalized accessibility tree with only meaningful nodes, or null if empty
20
*/
21
internal fun AccessibilityTestNode.normalized(): AccessibilityTestNode?
22
```
23
24
**Usage Example:**
25
26
```kotlin
27
runUIKitInstrumentedTest {
28
setContentWithAccessibilityEnabled {
29
Column {
30
Text("Welcome")
31
Button(onClick = {}) {
32
Text("Submit")
33
}
34
}
35
}
36
37
val tree = getAccessibilityTree()
38
val normalizedTree = tree.normalized()
39
40
println("Accessibility tree: $normalizedTree")
41
}
42
```
43
44
### Accessibility Node Search
45
46
Functions for finding specific nodes within the accessibility tree based on various criteria.
47
48
```kotlin { .api }
49
/**
50
* Finds accessibility node by identifier tag
51
* @param tag - Identifier tag to search for
52
* @return AccessibilityTestNode if found
53
* @throws AssertionError if node with tag is not found
54
*/
55
internal fun UIKitInstrumentedTest.findNodeWithTag(tag: String): AccessibilityTestNode
56
57
/**
58
* Finds accessibility node by accessibility label
59
* @param label - Accessibility label to search for
60
* @return AccessibilityTestNode if found
61
* @throws AssertionError if node with label is not found
62
*/
63
internal fun UIKitInstrumentedTest.findNodeWithLabel(label: String): AccessibilityTestNode
64
65
/**
66
* Finds first accessibility element in the tree
67
* @return AccessibilityTestNode of first accessible element
68
* @throws AssertionError if no accessibility element is found
69
*/
70
internal fun UIKitInstrumentedTest.firstAccessibleNode(): AccessibilityTestNode
71
72
/**
73
* Finds accessibility node matching a condition
74
* @param isValid - Predicate function to test nodes
75
* @return AccessibilityTestNode if found, null otherwise
76
*/
77
internal fun UIKitInstrumentedTest.findNodeOrNull(
78
isValid: (AccessibilityTestNode) -> Boolean
79
): AccessibilityTestNode?
80
```
81
82
**Usage Examples:**
83
84
```kotlin
85
runUIKitInstrumentedTest {
86
setContentWithAccessibilityEnabled {
87
Button(
88
onClick = {},
89
modifier = Modifier.testTag("submit_button")
90
) {
91
Text("Submit Form")
92
}
93
}
94
95
// Find by test tag
96
val submitButton = findNodeWithTag("submit_button")
97
requireNotNull(submitButton) { "Submit button not found" }
98
99
// Find by accessibility label
100
val labeledNode = findNodeWithLabel("Submit Form")
101
requireNotNull(labeledNode) { "Node with label not found" }
102
}
103
```
104
105
### Accessibility Tree Validation
106
107
Comprehensive validation functions for asserting accessibility tree structure and content.
108
109
```kotlin { .api }
110
/**
111
* Asserts that accessibility tree matches expected structure
112
* @param block - DSL block defining expected tree structure
113
* @throws AssertionError if tree structure doesn't match expectations
114
*/
115
fun UIKitInstrumentedTest.assertAccessibilityTree(
116
block: AccessibilityTestNode.() -> Unit
117
)
118
```
119
120
**Usage Example:**
121
122
```kotlin
123
runUIKitInstrumentedTest {
124
setContentWithAccessibilityEnabled {
125
Column {
126
Text("Header Text")
127
Row {
128
Text("Label:")
129
Text("Value")
130
}
131
Button(onClick = {}) {
132
Text("Action")
133
}
134
}
135
}
136
137
assertAccessibilityTree {
138
node {
139
label = "Header Text"
140
children = listOf(
141
node {
142
label = "Label:"
143
children = listOf(
144
node { label = "Value" }
145
)
146
},
147
node {
148
label = "Action"
149
role = "button"
150
}
151
)
152
}
153
}
154
}
155
```
156
157
### Accessibility Node Interactions
158
159
Functions for interacting with accessibility nodes through accessibility actions.
160
161
```kotlin { .api }
162
/**
163
* Simulates tap on accessibility element using accessibility actions
164
* @throws IllegalStateException if node is not accessible or tappable
165
*/
166
fun AccessibilityTestNode.tap()
167
168
/**
169
* Simulates double tap on accessibility element
170
* @throws IllegalStateException if node doesn't support double tap action
171
*/
172
fun AccessibilityTestNode.doubleTap()
173
```
174
175
**Usage Examples:**
176
177
```kotlin
178
runUIKitInstrumentedTest {
179
setContentWithAccessibilityEnabled {
180
Button(onClick = { println("Button clicked!") }) {
181
Text("Click Me")
182
}
183
}
184
185
val button = findNodeWithLabel("Click Me")
186
requireNotNull(button) { "Button not found" }
187
188
// Tap using accessibility action
189
button.tap()
190
191
// Double tap if supported
192
button.doubleTap()
193
}
194
```
195
196
## AccessibilityTestNode Data Class
197
198
Represents a node in the accessibility tree with comprehensive accessibility information.
199
200
```kotlin { .api }
201
/**
202
* Represents a node in the accessibility tree for testing purposes
203
* @param isAccessibilityElement - Whether element is exposed to accessibility services
204
* @param identifier - Unique identifier for the accessibility element
205
* @param label - Accessibility label for VoiceOver
206
* @param value - Current value of the accessibility element
207
* @param frame - Bounding rectangle in screen coordinates
208
* @param children - Child accessibility nodes
209
* @param traits - Accessibility traits as UIAccessibilityTraits values
210
* @param element - Reference to the underlying NSObject
211
* @param parent - Parent accessibility node
212
*/
213
internal data class AccessibilityTestNode(
214
var isAccessibilityElement: Boolean? = null,
215
var identifier: String? = null,
216
var label: String? = null,
217
var value: String? = null,
218
var frame: DpRect? = null,
219
var children: List<AccessibilityTestNode>? = null,
220
var traits: List<UIAccessibilityTraits>? = null,
221
var element: NSObject? = null,
222
var parent: AccessibilityTestNode? = null
223
) {
224
fun validate(actualNode: AccessibilityTestNode?)
225
fun node(builder: AccessibilityTestNode.() -> Unit)
226
fun traits(vararg trait: UIAccessibilityTraits)
227
fun printTree(): String
228
val hasAccessibilityComponents: Boolean
229
}
230
```
231
232
**Usage Example:**
233
234
```kotlin
235
val node = AccessibilityTestNode(
236
identifier = "user_profile_button",
237
label = "User Profile",
238
hint = "Double tap to open profile",
239
role = "button",
240
traits = setOf("button", "enabled"),
241
frame = DpRect(
242
offset = DpOffset(10.dp, 10.dp),
243
size = DpSize(100.dp, 44.dp)
244
)
245
)
246
```
247
248
## Advanced Accessibility Testing Patterns
249
250
### Testing Complex UI Hierarchies
251
252
```kotlin
253
runUIKitInstrumentedTest {
254
setContentWithAccessibilityEnabled {
255
NavigationView {
256
List {
257
ForEach(items) { item ->
258
NavigationLink(destination = DetailView(item)) {
259
VStack {
260
Text(item.title)
261
Text(item.subtitle)
262
}
263
}
264
}
265
}
266
}
267
}
268
269
assertAccessibilityTree {
270
node {
271
role = "list"
272
children = items.map { item ->
273
node {
274
label = item.title
275
role = "button"
276
traits = setOf("button", "link")
277
children = listOf(
278
node { label = item.subtitle }
279
)
280
}
281
}
282
}
283
}
284
}
285
```
286
287
### Testing Dynamic Content Updates
288
289
```kotlin
290
runUIKitInstrumentedTest {
291
var counter by remember { mutableStateOf(0) }
292
293
setContentWithAccessibilityEnabled {
294
Column {
295
Text("Count: $counter")
296
Button(onClick = { counter++ }) {
297
Text("Increment")
298
}
299
}
300
}
301
302
// Test initial state
303
val initialCount = findNodeWithLabel("Count: 0")
304
requireNotNull(initialCount)
305
306
// Interact and test updated state
307
val button = findNodeWithLabel("Increment")
308
button?.tap()
309
310
waitUntil {
311
findNodeWithLabel("Count: 1") != null
312
}
313
}
314
```
315
316
## VoiceOver Integration
317
318
- **Full VoiceOver Support**: Complete integration with iOS VoiceOver screen reader
319
- **Focus Management**: Proper accessibility focus handling and navigation
320
- **Announcements**: Support for dynamic accessibility announcements
321
- **Custom Actions**: Integration with custom accessibility actions
322
- **Grouping**: Proper accessibility element grouping and container support
323
324
## Error Handling
325
326
- **AssertionError**: Thrown when accessibility tree assertions fail
327
- **IllegalStateException**: Thrown when attempting to interact with inaccessible elements
328
- **TimeoutException**: Thrown when waiting for accessibility tree changes times out
329
- **Graceful Degradation**: Fallback behaviors when accessibility services are disabled