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.
@@ -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
+