mcp-maestro-mobile-ai 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/CONTRIBUTING.md +417 -0
- package/LICENSE +22 -0
- package/README.md +719 -0
- package/ROADMAP.md +239 -0
- package/docs/ENTERPRISE_READINESS.md +545 -0
- package/docs/MCP_SETUP.md +180 -0
- package/docs/PRIVACY.md +198 -0
- package/docs/REACT_NATIVE_AUTOMATION_GUIDELINES.md +584 -0
- package/docs/SECURITY.md +573 -0
- package/package.json +69 -0
- package/prompts/example-login-tests.txt +9 -0
- package/prompts/example-youtube-tests.txt +8 -0
- package/src/mcp-server/index.js +625 -0
- package/src/mcp-server/tools/contextTools.js +194 -0
- package/src/mcp-server/tools/promptTools.js +191 -0
- package/src/mcp-server/tools/runTools.js +357 -0
- package/src/mcp-server/tools/utilityTools.js +721 -0
- package/src/mcp-server/tools/validateTools.js +220 -0
- package/src/mcp-server/utils/appContext.js +295 -0
- package/src/mcp-server/utils/logger.js +52 -0
- package/src/mcp-server/utils/maestro.js +508 -0
- package/templates/mcp-config-claude-desktop.json +15 -0
- package/templates/mcp-config-cursor.json +15 -0
- package/templates/mcp-config-vscode.json +13 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# React Native Automation Guidelines
|
|
2
|
+
|
|
3
|
+
## For Maestro-Based Mobile Testing
|
|
4
|
+
|
|
5
|
+
This document provides comprehensive guidelines for preparing React Native applications for automated testing with Maestro.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [Overview](#overview)
|
|
12
|
+
2. [Accessibility Identifiers](#accessibility-identifiers)
|
|
13
|
+
3. [Naming Conventions](#naming-conventions)
|
|
14
|
+
4. [Component Guidelines](#component-guidelines)
|
|
15
|
+
5. [Screen Guidelines](#screen-guidelines)
|
|
16
|
+
6. [Form Guidelines](#form-guidelines)
|
|
17
|
+
7. [Navigation Guidelines](#navigation-guidelines)
|
|
18
|
+
8. [Common Patterns](#common-patterns)
|
|
19
|
+
9. [Anti-Patterns to Avoid](#anti-patterns-to-avoid)
|
|
20
|
+
10. [Testing Checklist](#testing-checklist)
|
|
21
|
+
11. [Troubleshooting](#troubleshooting)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
|
|
27
|
+
Maestro automation relies on Android Accessibility services to interact with UI elements. For reliable and maintainable automated tests, every interactive element must have proper accessibility identifiers.
|
|
28
|
+
|
|
29
|
+
### Key Principles
|
|
30
|
+
|
|
31
|
+
- **Every interactive element needs a unique identifier**
|
|
32
|
+
- **Identifiers must be static (not dynamic)**
|
|
33
|
+
- **Use semantic naming that reflects purpose**
|
|
34
|
+
- **Maintain identifiers in a central constants file**
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Accessibility Identifiers
|
|
39
|
+
|
|
40
|
+
### Required Props for Every Interactive Element
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
<TouchableOpacity
|
|
44
|
+
testID="login_button"
|
|
45
|
+
accessibilityLabel="login_button"
|
|
46
|
+
accessible={true}
|
|
47
|
+
>
|
|
48
|
+
<Text>Login</Text>
|
|
49
|
+
</TouchableOpacity>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Why Both `testID` and `accessibilityLabel`?
|
|
53
|
+
|
|
54
|
+
| Prop | Purpose | Platform |
|
|
55
|
+
|------|---------|----------|
|
|
56
|
+
| `testID` | Primary identifier for testing | iOS & Android (partial) |
|
|
57
|
+
| `accessibilityLabel` | Accessibility services & Maestro | iOS & Android |
|
|
58
|
+
| `accessible` | Marks element as accessible | iOS & Android |
|
|
59
|
+
|
|
60
|
+
**Important:** Maestro primarily uses `accessibilityLabel` on Android, so always include it.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Naming Conventions
|
|
65
|
+
|
|
66
|
+
### Standard Suffixes
|
|
67
|
+
|
|
68
|
+
| Element Type | Suffix | Example |
|
|
69
|
+
|--------------|--------|---------|
|
|
70
|
+
| Screen/View | `_screen` | `login_screen`, `dashboard_screen` |
|
|
71
|
+
| Button | `_button` | `submit_button`, `cancel_button` |
|
|
72
|
+
| Input/TextField | `_input` | `email_input`, `password_input` |
|
|
73
|
+
| Toggle/Switch | `_toggle` | `notifications_toggle` |
|
|
74
|
+
| Checkbox | `_checkbox` | `terms_checkbox` |
|
|
75
|
+
| Tab | `tab_` (prefix) | `tab_home`, `tab_settings` |
|
|
76
|
+
| Link | `_link` | `forgot_password_link` |
|
|
77
|
+
| Icon | `_icon` | `search_icon`, `menu_icon` |
|
|
78
|
+
| Card | `_card` | `user_profile_card` |
|
|
79
|
+
| List Item | `_item` | `contact_item` |
|
|
80
|
+
| Modal/Dialog | `_dialog` or `_modal` | `confirm_dialog` |
|
|
81
|
+
| Section | `_section` | `profile_section` |
|
|
82
|
+
|
|
83
|
+
### Naming Rules
|
|
84
|
+
|
|
85
|
+
1. **Use lowercase with underscores** (snake_case)
|
|
86
|
+
2. **Be descriptive but concise**
|
|
87
|
+
3. **Include context when needed** (e.g., `login_email_input` vs just `email_input`)
|
|
88
|
+
4. **Avoid generic names** like `button1`, `input2`
|
|
89
|
+
|
|
90
|
+
### Good Examples
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// ✅ Good - Clear, descriptive, follows convention
|
|
94
|
+
testID="login_email_input"
|
|
95
|
+
testID="forgot_password_link"
|
|
96
|
+
testID="tab_settings"
|
|
97
|
+
testID="save_profile_button"
|
|
98
|
+
testID="dark_mode_toggle"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Bad Examples
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
// ❌ Bad - Generic, unclear, or wrong format
|
|
105
|
+
testID="btn1"
|
|
106
|
+
testID="TextField"
|
|
107
|
+
testID="loginButton" // Should be login_button
|
|
108
|
+
testID={`item_${id}`} // Dynamic - don't do this
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Component Guidelines
|
|
114
|
+
|
|
115
|
+
### Buttons
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
// Standard button
|
|
119
|
+
<TouchableOpacity
|
|
120
|
+
testID="submit_form_button"
|
|
121
|
+
accessibilityLabel="submit_form_button"
|
|
122
|
+
accessibilityRole="button"
|
|
123
|
+
accessible={true}
|
|
124
|
+
onPress={handleSubmit}
|
|
125
|
+
>
|
|
126
|
+
<Text>Submit</Text>
|
|
127
|
+
</TouchableOpacity>
|
|
128
|
+
|
|
129
|
+
// Icon button
|
|
130
|
+
<TouchableOpacity
|
|
131
|
+
testID="search_icon_button"
|
|
132
|
+
accessibilityLabel="search_icon_button"
|
|
133
|
+
accessibilityRole="button"
|
|
134
|
+
accessibilityHint="Opens search screen"
|
|
135
|
+
accessible={true}
|
|
136
|
+
onPress={openSearch}
|
|
137
|
+
>
|
|
138
|
+
<Icon name="search" />
|
|
139
|
+
</TouchableOpacity>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Text Inputs
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<TextInput
|
|
146
|
+
testID="email_input"
|
|
147
|
+
accessibilityLabel="email_input"
|
|
148
|
+
accessible={true}
|
|
149
|
+
placeholder="Enter your email"
|
|
150
|
+
value={email}
|
|
151
|
+
onChangeText={setEmail}
|
|
152
|
+
keyboardType="email-address"
|
|
153
|
+
autoCapitalize="none"
|
|
154
|
+
/>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Toggles/Switches
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
<Switch
|
|
161
|
+
testID="notifications_toggle"
|
|
162
|
+
accessibilityLabel="notifications_toggle"
|
|
163
|
+
accessible={true}
|
|
164
|
+
value={notificationsEnabled}
|
|
165
|
+
onValueChange={setNotificationsEnabled}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Lists
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
// List container
|
|
173
|
+
<FlatList
|
|
174
|
+
testID="contacts_list"
|
|
175
|
+
accessibilityLabel="contacts_list"
|
|
176
|
+
data={contacts}
|
|
177
|
+
renderItem={({ item, index }) => (
|
|
178
|
+
// For list items, use static prefix + position if needed for specific item testing
|
|
179
|
+
// But prefer testing by visible text content instead
|
|
180
|
+
<View
|
|
181
|
+
testID="contact_item"
|
|
182
|
+
accessibilityLabel={item.name}
|
|
183
|
+
accessible={true}
|
|
184
|
+
>
|
|
185
|
+
<Text>{item.name}</Text>
|
|
186
|
+
</View>
|
|
187
|
+
)}
|
|
188
|
+
/>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Screen Guidelines
|
|
194
|
+
|
|
195
|
+
### Screen Container
|
|
196
|
+
|
|
197
|
+
Every screen must have a wrapper with a unique screen identifier:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
const LoginScreen = () => {
|
|
201
|
+
return (
|
|
202
|
+
<SafeAreaView
|
|
203
|
+
testID="login_screen"
|
|
204
|
+
accessibilityLabel="login_screen"
|
|
205
|
+
accessible={false} // Container not directly accessible
|
|
206
|
+
style={styles.container}
|
|
207
|
+
>
|
|
208
|
+
{/* Screen content */}
|
|
209
|
+
</SafeAreaView>
|
|
210
|
+
);
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Screen Wrapper Component (Recommended)
|
|
215
|
+
|
|
216
|
+
Create a reusable screen wrapper:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// components/ScreenWrapper.tsx
|
|
220
|
+
interface ScreenWrapperProps {
|
|
221
|
+
screenId: string;
|
|
222
|
+
children: React.ReactNode;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const ScreenWrapper: React.FC<ScreenWrapperProps> = ({
|
|
226
|
+
screenId,
|
|
227
|
+
children
|
|
228
|
+
}) => {
|
|
229
|
+
return (
|
|
230
|
+
<SafeAreaView
|
|
231
|
+
testID={screenId}
|
|
232
|
+
accessibilityLabel={screenId}
|
|
233
|
+
accessible={false}
|
|
234
|
+
style={styles.container}
|
|
235
|
+
>
|
|
236
|
+
{children}
|
|
237
|
+
</SafeAreaView>
|
|
238
|
+
);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Usage
|
|
242
|
+
const DashboardScreen = () => (
|
|
243
|
+
<ScreenWrapper screenId="dashboard_screen">
|
|
244
|
+
{/* Screen content */}
|
|
245
|
+
</ScreenWrapper>
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Form Guidelines
|
|
252
|
+
|
|
253
|
+
### Complete Form Example
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
const RegistrationForm = () => {
|
|
257
|
+
return (
|
|
258
|
+
<View testID="registration_form" accessibilityLabel="registration_form">
|
|
259
|
+
<TextInput
|
|
260
|
+
testID="first_name_input"
|
|
261
|
+
accessibilityLabel="first_name_input"
|
|
262
|
+
placeholder="First Name"
|
|
263
|
+
/>
|
|
264
|
+
|
|
265
|
+
<TextInput
|
|
266
|
+
testID="last_name_input"
|
|
267
|
+
accessibilityLabel="last_name_input"
|
|
268
|
+
placeholder="Last Name"
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
<TextInput
|
|
272
|
+
testID="email_input"
|
|
273
|
+
accessibilityLabel="email_input"
|
|
274
|
+
placeholder="Email"
|
|
275
|
+
keyboardType="email-address"
|
|
276
|
+
/>
|
|
277
|
+
|
|
278
|
+
<TextInput
|
|
279
|
+
testID="password_input"
|
|
280
|
+
accessibilityLabel="password_input"
|
|
281
|
+
placeholder="Password"
|
|
282
|
+
secureTextEntry
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
<TouchableOpacity
|
|
286
|
+
testID="terms_checkbox"
|
|
287
|
+
accessibilityLabel="terms_checkbox"
|
|
288
|
+
accessibilityRole="checkbox"
|
|
289
|
+
>
|
|
290
|
+
<Text>I agree to terms</Text>
|
|
291
|
+
</TouchableOpacity>
|
|
292
|
+
|
|
293
|
+
<TouchableOpacity
|
|
294
|
+
testID="register_button"
|
|
295
|
+
accessibilityLabel="register_button"
|
|
296
|
+
accessibilityRole="button"
|
|
297
|
+
>
|
|
298
|
+
<Text>Register</Text>
|
|
299
|
+
</TouchableOpacity>
|
|
300
|
+
</View>
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Navigation Guidelines
|
|
308
|
+
|
|
309
|
+
### Bottom Tab Navigator
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
<Tab.Navigator>
|
|
313
|
+
<Tab.Screen
|
|
314
|
+
name="Home"
|
|
315
|
+
component={HomeScreen}
|
|
316
|
+
options={{
|
|
317
|
+
tabBarTestID: 'tab_home',
|
|
318
|
+
tabBarAccessibilityLabel: 'tab_home',
|
|
319
|
+
}}
|
|
320
|
+
/>
|
|
321
|
+
<Tab.Screen
|
|
322
|
+
name="Safety"
|
|
323
|
+
component={SafetyScreen}
|
|
324
|
+
options={{
|
|
325
|
+
tabBarTestID: 'tab_safety',
|
|
326
|
+
tabBarAccessibilityLabel: 'tab_safety',
|
|
327
|
+
}}
|
|
328
|
+
/>
|
|
329
|
+
<Tab.Screen
|
|
330
|
+
name="Settings"
|
|
331
|
+
component={SettingsScreen}
|
|
332
|
+
options={{
|
|
333
|
+
tabBarTestID: 'tab_settings',
|
|
334
|
+
tabBarAccessibilityLabel: 'tab_settings',
|
|
335
|
+
}}
|
|
336
|
+
/>
|
|
337
|
+
</Tab.Navigator>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Header Buttons
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
<Stack.Screen
|
|
344
|
+
name="Profile"
|
|
345
|
+
component={ProfileScreen}
|
|
346
|
+
options={{
|
|
347
|
+
headerRight: () => (
|
|
348
|
+
<TouchableOpacity
|
|
349
|
+
testID="edit_profile_header_button"
|
|
350
|
+
accessibilityLabel="edit_profile_header_button"
|
|
351
|
+
>
|
|
352
|
+
<Text>Edit</Text>
|
|
353
|
+
</TouchableOpacity>
|
|
354
|
+
),
|
|
355
|
+
}}
|
|
356
|
+
/>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Common Patterns
|
|
362
|
+
|
|
363
|
+
### Modal/Dialog
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
<Modal
|
|
367
|
+
testID="confirm_dialog"
|
|
368
|
+
accessibilityLabel="confirm_dialog"
|
|
369
|
+
visible={visible}
|
|
370
|
+
>
|
|
371
|
+
<View testID="confirm_dialog_content">
|
|
372
|
+
<Text>Are you sure?</Text>
|
|
373
|
+
|
|
374
|
+
<TouchableOpacity
|
|
375
|
+
testID="confirm_yes_button"
|
|
376
|
+
accessibilityLabel="confirm_yes_button"
|
|
377
|
+
>
|
|
378
|
+
<Text>Yes</Text>
|
|
379
|
+
</TouchableOpacity>
|
|
380
|
+
|
|
381
|
+
<TouchableOpacity
|
|
382
|
+
testID="confirm_no_button"
|
|
383
|
+
accessibilityLabel="confirm_no_button"
|
|
384
|
+
>
|
|
385
|
+
<Text>No</Text>
|
|
386
|
+
</TouchableOpacity>
|
|
387
|
+
</View>
|
|
388
|
+
</Modal>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Loading States
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
{isLoading ? (
|
|
395
|
+
<View testID="loading_indicator" accessibilityLabel="loading_indicator">
|
|
396
|
+
<ActivityIndicator />
|
|
397
|
+
</View>
|
|
398
|
+
) : (
|
|
399
|
+
<View testID="content_loaded" accessibilityLabel="content_loaded">
|
|
400
|
+
{/* Content */}
|
|
401
|
+
</View>
|
|
402
|
+
)}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Error States
|
|
406
|
+
|
|
407
|
+
```tsx
|
|
408
|
+
{error && (
|
|
409
|
+
<View testID="error_message" accessibilityLabel="error_message">
|
|
410
|
+
<Text>{error.message}</Text>
|
|
411
|
+
<TouchableOpacity
|
|
412
|
+
testID="retry_button"
|
|
413
|
+
accessibilityLabel="retry_button"
|
|
414
|
+
>
|
|
415
|
+
<Text>Retry</Text>
|
|
416
|
+
</TouchableOpacity>
|
|
417
|
+
</View>
|
|
418
|
+
)}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Anti-Patterns to Avoid
|
|
424
|
+
|
|
425
|
+
### ❌ Dynamic IDs
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
// DON'T - Dynamic IDs break automation
|
|
429
|
+
testID={`item_${item.id}`}
|
|
430
|
+
testID={`button_${index}`}
|
|
431
|
+
testID={`${screenName}_input`}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### ❌ Text-Based Selectors
|
|
435
|
+
|
|
436
|
+
```tsx
|
|
437
|
+
// DON'T - Text can change with translations
|
|
438
|
+
// Maestro: tapOn: "Login" <- Fragile!
|
|
439
|
+
|
|
440
|
+
// DO - Use IDs
|
|
441
|
+
testID="login_button"
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### ❌ Missing Identifiers on Interactive Elements
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
// DON'T - No testID
|
|
448
|
+
<TouchableOpacity onPress={handlePress}>
|
|
449
|
+
<Text>Click Me</Text>
|
|
450
|
+
</TouchableOpacity>
|
|
451
|
+
|
|
452
|
+
// DO - Always add testID and accessibilityLabel
|
|
453
|
+
<TouchableOpacity
|
|
454
|
+
testID="action_button"
|
|
455
|
+
accessibilityLabel="action_button"
|
|
456
|
+
onPress={handlePress}
|
|
457
|
+
>
|
|
458
|
+
<Text>Click Me</Text>
|
|
459
|
+
</TouchableOpacity>
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### ❌ Inconsistent Naming
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
// DON'T - Mixed conventions
|
|
466
|
+
testID="loginBtn"
|
|
467
|
+
testID="SUBMIT_BUTTON"
|
|
468
|
+
testID="userEmail"
|
|
469
|
+
|
|
470
|
+
// DO - Consistent snake_case with proper suffixes
|
|
471
|
+
testID="login_button"
|
|
472
|
+
testID="submit_button"
|
|
473
|
+
testID="user_email_input"
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Testing Checklist
|
|
479
|
+
|
|
480
|
+
### Pre-PR Checklist
|
|
481
|
+
|
|
482
|
+
- [ ] All interactive elements have `testID`
|
|
483
|
+
- [ ] All interactive elements have `accessibilityLabel`
|
|
484
|
+
- [ ] Screen containers have unique `_screen` identifiers
|
|
485
|
+
- [ ] IDs follow naming conventions
|
|
486
|
+
- [ ] No dynamic IDs used
|
|
487
|
+
- [ ] Constants file updated (if using centralized constants)
|
|
488
|
+
- [ ] New screens documented
|
|
489
|
+
|
|
490
|
+
### Centralized Constants File
|
|
491
|
+
|
|
492
|
+
Maintain all testIDs in a central file:
|
|
493
|
+
|
|
494
|
+
```tsx
|
|
495
|
+
// constants/testIds.ts
|
|
496
|
+
export const TEST_IDS = {
|
|
497
|
+
// Screens
|
|
498
|
+
LOGIN_SCREEN: 'login_screen',
|
|
499
|
+
DASHBOARD_SCREEN: 'dashboard_screen',
|
|
500
|
+
SETTINGS_SCREEN: 'settings_screen',
|
|
501
|
+
|
|
502
|
+
// Login Screen
|
|
503
|
+
EMAIL_INPUT: 'email_input',
|
|
504
|
+
PASSWORD_INPUT: 'password_input',
|
|
505
|
+
LOGIN_BUTTON: 'login_button',
|
|
506
|
+
FORGOT_PASSWORD_LINK: 'forgot_password_link',
|
|
507
|
+
|
|
508
|
+
// Navigation
|
|
509
|
+
TAB_HOME: 'tab_home',
|
|
510
|
+
TAB_SAFETY: 'tab_safety',
|
|
511
|
+
TAB_SECURITY: 'tab_security',
|
|
512
|
+
TAB_CONTACTS: 'tab_contacts',
|
|
513
|
+
TAB_SETTINGS: 'tab_settings',
|
|
514
|
+
|
|
515
|
+
// ... more IDs
|
|
516
|
+
} as const;
|
|
517
|
+
|
|
518
|
+
// Usage
|
|
519
|
+
import { TEST_IDS } from '@/constants/testIds';
|
|
520
|
+
|
|
521
|
+
<TouchableOpacity testID={TEST_IDS.LOGIN_BUTTON}>
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Troubleshooting
|
|
527
|
+
|
|
528
|
+
### Element Not Found
|
|
529
|
+
|
|
530
|
+
1. **Check if `accessibilityLabel` is set** (Maestro uses this on Android)
|
|
531
|
+
2. **Verify element is visible** (not hidden by scroll or z-index)
|
|
532
|
+
3. **Check for typos** in the ID
|
|
533
|
+
4. **Use Maestro Studio** to inspect the element tree
|
|
534
|
+
|
|
535
|
+
### Flaky Tests
|
|
536
|
+
|
|
537
|
+
1. **Add explicit waits** using `extendedWaitUntil`
|
|
538
|
+
2. **Check for animations** that might delay element visibility
|
|
539
|
+
3. **Verify network calls complete** before assertions
|
|
540
|
+
|
|
541
|
+
### Maestro Studio Inspection
|
|
542
|
+
|
|
543
|
+
```bash
|
|
544
|
+
# Launch Maestro Studio to inspect element tree
|
|
545
|
+
maestro studio
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Debug Mode
|
|
549
|
+
|
|
550
|
+
```yaml
|
|
551
|
+
# Add to flow for debugging
|
|
552
|
+
- takeScreenshot: "debug-screenshot"
|
|
553
|
+
- evalScript: |
|
|
554
|
+
console.log(JSON.stringify(maestro.tree(), null, 2))
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## Quick Reference Card
|
|
560
|
+
|
|
561
|
+
| Element | testID Format | Example |
|
|
562
|
+
|---------|---------------|---------|
|
|
563
|
+
| Screen | `{name}_screen` | `login_screen` |
|
|
564
|
+
| Button | `{action}_button` | `submit_button` |
|
|
565
|
+
| Input | `{field}_input` | `email_input` |
|
|
566
|
+
| Tab | `tab_{name}` | `tab_home` |
|
|
567
|
+
| Toggle | `{feature}_toggle` | `dark_mode_toggle` |
|
|
568
|
+
| Link | `{action}_link` | `forgot_password_link` |
|
|
569
|
+
| Modal | `{name}_dialog` | `confirm_dialog` |
|
|
570
|
+
| Icon | `{action}_icon` | `search_icon` |
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Resources
|
|
575
|
+
|
|
576
|
+
- [Maestro Documentation](https://maestro.mobile.dev/)
|
|
577
|
+
- [React Native Accessibility](https://reactnative.dev/docs/accessibility)
|
|
578
|
+
- [Android Accessibility](https://developer.android.com/guide/topics/ui/accessibility)
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
**Document Version:** 1.0
|
|
583
|
+
**Last Updated:** December 2024
|
|
584
|
+
|