mcp-maestro-mobile-ai 1.4.0 → 1.6.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,564 @@
1
+ /**
2
+ * Generic Mobile Interaction Patterns
3
+ *
4
+ * Framework-agnostic patterns for reliable mobile test automation.
5
+ * These patterns are designed to reduce test failures across ANY mobile application
6
+ * regardless of whether it's built with Flutter, React Native, or native technologies.
7
+ *
8
+ * NO app-specific values or hard-coded coordinates.
9
+ * NO framework-specific identifiers.
10
+ * Focus on STRATEGIES and PATTERNS that improve test resilience.
11
+ */
12
+
13
+ /**
14
+ * Interaction pattern categories
15
+ */
16
+ export const PatternCategory = {
17
+ KEYBOARD_HANDLING: 'keyboard_handling',
18
+ ELEMENT_INTERACTION: 'element_interaction',
19
+ TIMING_SYNC: 'timing_synchronization',
20
+ INPUT_HANDLING: 'input_handling',
21
+ DROPDOWN_PICKER: 'dropdown_picker',
22
+ NAVIGATION: 'navigation',
23
+ SCROLL_VISIBILITY: 'scroll_visibility',
24
+ FALLBACK_STRATEGIES: 'fallback_strategies',
25
+ };
26
+
27
+ /**
28
+ * Generic Interaction Patterns
29
+ *
30
+ * Each pattern describes:
31
+ * - situation: When to apply this pattern
32
+ * - strategy: The generic approach
33
+ * - yamlPattern: Template YAML (with placeholders)
34
+ * - rationale: Why this pattern improves reliability
35
+ */
36
+ export const INTERACTION_PATTERNS = {
37
+ // ============================================
38
+ // KEYBOARD HANDLING PATTERNS
39
+ // ============================================
40
+
41
+ HIDE_KEYBOARD_BEFORE_TAP: {
42
+ category: PatternCategory.KEYBOARD_HANDLING,
43
+ situation: 'Before tapping any element that might be covered by the keyboard',
44
+ strategy: `After entering text in any input field, ALWAYS hide the keyboard before
45
+ interacting with other elements (buttons, dropdowns, links) that may be positioned
46
+ below or near the input fields.`,
47
+ yamlPattern: `# After text input, before tapping other elements
48
+ - tapOn: "{input_field}"
49
+ - inputText: "{value}"
50
+
51
+ # ALWAYS hide keyboard before next interaction
52
+ - hideKeyboard
53
+ - waitForAnimationToEnd
54
+
55
+ # Now safe to tap elements that may have been covered
56
+ - tapOn: "{next_element}"`,
57
+ rationale: 'On-screen keyboards can cover 40-50% of the screen. Elements below text fields become untappable until keyboard is dismissed.',
58
+ },
59
+
60
+ DOUBLE_WAIT_AFTER_KEYBOARD: {
61
+ category: PatternCategory.KEYBOARD_HANDLING,
62
+ situation: 'When keyboard dismissal animation interferes with next action',
63
+ strategy: `Some apps have longer keyboard dismissal animations. Use double wait
64
+ when single wait is insufficient.`,
65
+ yamlPattern: `- hideKeyboard
66
+ - waitForAnimationToEnd
67
+ - waitForAnimationToEnd # Extra wait for complete dismissal
68
+
69
+ - tapOn: "{element}"`,
70
+ rationale: 'Keyboard dismissal can trigger layout reflows. Double wait ensures UI has fully settled.',
71
+ },
72
+
73
+ // ============================================
74
+ // INPUT HANDLING PATTERNS
75
+ // ============================================
76
+
77
+ TAP_BEFORE_INPUT: {
78
+ category: PatternCategory.INPUT_HANDLING,
79
+ situation: 'ALWAYS, before every inputText command',
80
+ strategy: `NEVER use inputText without a preceding tapOn. The inputText command
81
+ sends keystrokes to the currently focused element. Without tapOn, text may go to
82
+ the wrong field.`,
83
+ yamlPattern: `# CORRECT PATTERN
84
+ - tapOn: "{field_name}"
85
+ - inputText: "{value}"
86
+
87
+ # WRONG - text may go to previously focused field
88
+ # - inputText: "{value}" # NO!`,
89
+ rationale: 'inputText sends keystrokes to whatever element has focus. Only tapOn guarantees the correct field is focused.',
90
+ },
91
+
92
+ CLEAR_BEFORE_RETRY: {
93
+ category: PatternCategory.INPUT_HANDLING,
94
+ situation: 'When re-entering data into a field that may contain previous input',
95
+ strategy: `Use eraseText before inputText when:
96
+ - Retrying after a failed attempt
97
+ - Editing existing values
98
+ - Looping through multiple test iterations`,
99
+ yamlPattern: `# Clear existing content before new input
100
+ - tapOn: "{field}"
101
+ - eraseText: 50 # Erase up to 50 characters
102
+ - inputText: "{new_value}"`,
103
+ rationale: 'Fields may retain previous values. eraseText ensures clean state before new input.',
104
+ },
105
+
106
+ WAIT_BETWEEN_INPUTS: {
107
+ category: PatternCategory.INPUT_HANDLING,
108
+ situation: 'When filling multiple fields in sequence',
109
+ strategy: `Add waitForAnimationToEnd between field inputs when tests fail due to
110
+ focus not shifting properly.`,
111
+ yamlPattern: `- tapOn: "{field_1}"
112
+ - inputText: "{value_1}"
113
+
114
+ - waitForAnimationToEnd # Let focus settle
115
+
116
+ - tapOn: "{field_2}"
117
+ - inputText: "{value_2}"`,
118
+ rationale: 'Some apps have focus transition animations. Waiting prevents input going to wrong field.',
119
+ },
120
+
121
+ // ============================================
122
+ // ELEMENT INTERACTION PATTERNS
123
+ // ============================================
124
+
125
+ VERIFY_BEFORE_INTERACT: {
126
+ category: PatternCategory.ELEMENT_INTERACTION,
127
+ situation: 'Before interacting with critical elements',
128
+ strategy: `Use extendedWaitUntil to verify element visibility and readiness
129
+ before attempting interaction. This prevents failures due to loading states.`,
130
+ yamlPattern: `# Wait for element to be ready
131
+ - extendedWaitUntil:
132
+ visible: "{element_identifier}"
133
+ timeout: 10000
134
+
135
+ # Now interact with confidence
136
+ - tapOn: "{element_identifier}"`,
137
+ rationale: 'Elements may exist in DOM but not be interactive yet. Explicit wait ensures readiness.',
138
+ },
139
+
140
+ MULTIPLE_SELECTOR_FALLBACK: {
141
+ category: PatternCategory.ELEMENT_INTERACTION,
142
+ situation: 'When primary selector may not work in all scenarios',
143
+ strategy: `Use runFlow with conditional checks to try multiple selectors:
144
+ 1. Try primary selector
145
+ 2. If not visible, try alternative selector
146
+ 3. Use index-based selection as last resort`,
147
+ yamlPattern: `# Try primary selector
148
+ - runFlow:
149
+ when:
150
+ visible: "{primary_selector}"
151
+ commands:
152
+ - tapOn: "{primary_selector}"
153
+
154
+ # Fallback to alternative
155
+ - runFlow:
156
+ when:
157
+ notVisible: "{primary_selector}"
158
+ commands:
159
+ - tapOn: "{alternative_selector}"`,
160
+ rationale: 'Element identifiers may vary across app versions or device configurations.',
161
+ },
162
+
163
+ // ============================================
164
+ // DROPDOWN/PICKER PATTERNS
165
+ // ============================================
166
+
167
+ DROPDOWN_INTERACTION: {
168
+ category: PatternCategory.DROPDOWN_PICKER,
169
+ situation: 'When interacting with dropdown menus, pickers, or select elements',
170
+ strategy: `Standard dropdown interaction flow:
171
+ 1. Hide keyboard (if any text field was previously focused)
172
+ 2. Wait for any pending animations
173
+ 3. Tap the dropdown trigger element (current value or dropdown button)
174
+ 4. Wait for dropdown/overlay to appear
175
+ 5. Tap the desired option
176
+ 6. Wait for dropdown to close`,
177
+ yamlPattern: `# Step 1: Ensure keyboard is hidden
178
+ - hideKeyboard
179
+ - waitForAnimationToEnd
180
+
181
+ # Step 2: Open dropdown (tap current value or trigger)
182
+ - tapOn: "{dropdown_trigger}"
183
+ - waitForAnimationToEnd
184
+
185
+ # Step 3: Select option
186
+ - tapOn: "{desired_option}"
187
+ - waitForAnimationToEnd`,
188
+ rationale: 'Dropdowns render as overlays. Proper timing between steps prevents missed taps.',
189
+ },
190
+
191
+ SCROLLABLE_DROPDOWN: {
192
+ category: PatternCategory.DROPDOWN_PICKER,
193
+ situation: 'When dropdown options may require scrolling',
194
+ strategy: `If the desired option is not immediately visible in the dropdown:
195
+ 1. Open dropdown
196
+ 2. Scroll within dropdown to find option
197
+ 3. Then tap the option`,
198
+ yamlPattern: `- tapOn: "{dropdown_trigger}"
199
+ - waitForAnimationToEnd
200
+
201
+ # Scroll if option might be below visible area
202
+ - scroll
203
+
204
+ - tapOn: "{desired_option}"
205
+ - waitForAnimationToEnd`,
206
+ rationale: 'Long option lists may require scrolling. Option must be visible before tapping.',
207
+ },
208
+
209
+ // ============================================
210
+ // TIMING & SYNCHRONIZATION PATTERNS
211
+ // ============================================
212
+
213
+ WAIT_FOR_SCREEN_TRANSITION: {
214
+ category: PatternCategory.TIMING_SYNC,
215
+ situation: 'After actions that trigger navigation (login, submit, next)',
216
+ strategy: `After triggering navigation:
217
+ 1. Wait for a unique element on the DESTINATION screen
218
+ 2. Use longer timeout for network-dependent transitions
219
+ 3. Assert destination reached before proceeding`,
220
+ yamlPattern: `# Trigger navigation
221
+ - tapOn: "{submit_button}"
222
+
223
+ # Wait for destination screen element
224
+ - extendedWaitUntil:
225
+ visible: "{destination_screen_element}"
226
+ timeout: 30000
227
+
228
+ # Verify we're on correct screen
229
+ - assertVisible: "{destination_screen_element}"`,
230
+ rationale: 'Navigation may involve network calls, animations, or complex transitions.',
231
+ },
232
+
233
+ WAIT_FOR_LOADING_COMPLETE: {
234
+ category: PatternCategory.TIMING_SYNC,
235
+ situation: 'When screen has loading indicators',
236
+ strategy: `Wait for loading to complete by:
237
+ 1. Waiting for loading indicator to disappear, OR
238
+ 2. Waiting for content elements to appear`,
239
+ yamlPattern: `# Option 1: Wait for loading to disappear
240
+ - extendedWaitUntil:
241
+ notVisible: "{loading_indicator}"
242
+ timeout: 15000
243
+
244
+ # Option 2: Wait for content to appear
245
+ - extendedWaitUntil:
246
+ visible: "{content_element}"
247
+ timeout: 15000`,
248
+ rationale: 'Interacting during loading state causes unpredictable failures.',
249
+ },
250
+
251
+ ANIMATION_BUFFER: {
252
+ category: PatternCategory.TIMING_SYNC,
253
+ situation: 'After any action that triggers UI animation',
254
+ strategy: `Use waitForAnimationToEnd after:
255
+ - Opening/closing modals
256
+ - Expanding/collapsing sections
257
+ - Tab switches
258
+ - Any visual transition`,
259
+ yamlPattern: `- tapOn: "{element_that_triggers_animation}"
260
+ - waitForAnimationToEnd
261
+
262
+ # Proceed after animation completes
263
+ - tapOn: "{next_element}"`,
264
+ rationale: 'Tapping during animation often fails or taps wrong element.',
265
+ },
266
+
267
+ // ============================================
268
+ // SCROLL & VISIBILITY PATTERNS
269
+ // ============================================
270
+
271
+ SCROLL_TO_ELEMENT: {
272
+ category: PatternCategory.SCROLL_VISIBILITY,
273
+ situation: 'When element may be below the visible fold',
274
+ strategy: `Scroll before attempting to tap elements that might not be visible:
275
+ - Form fields at bottom of long forms
276
+ - List items below the fold
277
+ - Buttons at page bottom`,
278
+ yamlPattern: `# Scroll to bring element into view
279
+ - scroll
280
+
281
+ # Then interact
282
+ - tapOn: "{element_below_fold}"`,
283
+ rationale: 'Elements must be visible on screen to be tappable.',
284
+ },
285
+
286
+ SCROLL_AND_VERIFY: {
287
+ category: PatternCategory.SCROLL_VISIBILITY,
288
+ situation: 'When you need to confirm element is visible after scroll',
289
+ strategy: `Combine scroll with visibility assertion for reliability.`,
290
+ yamlPattern: `- scroll
291
+ - waitForAnimationToEnd
292
+
293
+ - assertVisible: "{target_element}"
294
+ - tapOn: "{target_element}"`,
295
+ rationale: 'Verifying visibility after scroll ensures element is in tappable area.',
296
+ },
297
+
298
+ // ============================================
299
+ // NAVIGATION PATTERNS
300
+ // ============================================
301
+
302
+ BACK_NAVIGATION: {
303
+ category: PatternCategory.NAVIGATION,
304
+ situation: 'When navigating back to previous screen',
305
+ strategy: `Use pressKey: back for system back navigation. Wait for
306
+ previous screen element to confirm navigation succeeded.`,
307
+ yamlPattern: `- pressKey: back
308
+ - waitForAnimationToEnd
309
+
310
+ - assertVisible: "{previous_screen_element}"`,
311
+ rationale: 'Back navigation varies by platform. pressKey: back is most reliable.',
312
+ },
313
+
314
+ // ============================================
315
+ // FALLBACK STRATEGIES
316
+ // ============================================
317
+
318
+ ELEMENT_NOT_FOUND_STRATEGY: {
319
+ category: PatternCategory.FALLBACK_STRATEGIES,
320
+ situation: 'When element selector is not finding the element',
321
+ strategy: `Progressive fallback approach:
322
+ 1. Verify correct screen (add assertVisible for screen identifier)
323
+ 2. Add scroll in case element is below fold
324
+ 3. Try alternative text/label that might match
325
+ 4. Check if keyboard is covering element
326
+ 5. As last resort, capture hierarchy for analysis`,
327
+ yamlPattern: `# Step 1: Confirm we're on right screen
328
+ - assertVisible: "{screen_identifier}"
329
+
330
+ # Step 2: Hide keyboard if it might be covering
331
+ - hideKeyboard
332
+ - waitForAnimationToEnd
333
+
334
+ # Step 3: Scroll in case element is below fold
335
+ - scroll
336
+ - waitForAnimationToEnd
337
+
338
+ # Step 4: Try to interact
339
+ - tapOn: "{element}"`,
340
+ rationale: 'Systematic fallback identifies root cause without blind retries.',
341
+ },
342
+
343
+ TAP_NOT_WORKING_STRATEGY: {
344
+ category: PatternCategory.FALLBACK_STRATEGIES,
345
+ situation: 'When tap is found but action does not trigger',
346
+ strategy: `Element may not be interactive yet:
347
+ 1. Add explicit wait before tap
348
+ 2. Hide keyboard if near text fields
349
+ 3. Wait for any loading to complete
350
+ 4. Ensure element is fully visible (not partially off-screen)`,
351
+ yamlPattern: `# Ensure element is ready
352
+ - extendedWaitUntil:
353
+ visible: "{element}"
354
+ timeout: 10000
355
+
356
+ # Clear any overlays
357
+ - hideKeyboard
358
+ - waitForAnimationToEnd
359
+
360
+ # Now tap
361
+ - tapOn: "{element}"`,
362
+ rationale: 'Element visibility does not guarantee interactivity. Explicit waits help.',
363
+ },
364
+ };
365
+
366
+ /**
367
+ * Get interaction patterns by category
368
+ */
369
+ export function getPatternsByCategory(category) {
370
+ return Object.entries(INTERACTION_PATTERNS)
371
+ .filter(([_, pattern]) => pattern.category === category)
372
+ .reduce((acc, [key, pattern]) => ({ ...acc, [key]: pattern }), {});
373
+ }
374
+
375
+ /**
376
+ * Get all pattern categories
377
+ */
378
+ export function getPatternCategories() {
379
+ return Object.values(PatternCategory);
380
+ }
381
+
382
+ /**
383
+ * Generate YAML generation hints from patterns
384
+ * This is embedded into YAML generation context
385
+ */
386
+ export function getInteractionPatternHints() {
387
+ return `
388
+ ## GENERIC INTERACTION PATTERNS FOR RELIABLE TESTS
389
+
390
+ Apply these patterns to reduce test failures across any mobile application.
391
+
392
+ ### 🔴 CRITICAL: Keyboard Handling
393
+
394
+ **ALWAYS hide keyboard before interacting with elements that might be covered:**
395
+ \`\`\`yaml
396
+ # After ANY text input
397
+ - tapOn: "{field}"
398
+ - inputText: "{value}"
399
+
400
+ # Before ANY button, dropdown, or link below text fields
401
+ - hideKeyboard
402
+ - waitForAnimationToEnd
403
+
404
+ - tapOn: "{button_or_dropdown}"
405
+ \`\`\`
406
+
407
+ ### 🔴 CRITICAL: Input Field Pattern
408
+
409
+ **ALWAYS tap before inputText:**
410
+ \`\`\`yaml
411
+ # CORRECT - ensures correct field is focused
412
+ - tapOn: "{field_name}"
413
+ - inputText: "{value}"
414
+
415
+ # WRONG - may input to previously focused field
416
+ - inputText: "{value}"
417
+ \`\`\`
418
+
419
+ ### 🟡 IMPORTANT: Dropdown/Picker Interaction
420
+
421
+ \`\`\`yaml
422
+ # Standard dropdown pattern
423
+ - hideKeyboard
424
+ - waitForAnimationToEnd
425
+
426
+ - tapOn: "{dropdown_trigger}" # Current value or dropdown button
427
+ - waitForAnimationToEnd # Wait for options to appear
428
+
429
+ - tapOn: "{option_to_select}" # Select desired option
430
+ - waitForAnimationToEnd # Wait for dropdown to close
431
+ \`\`\`
432
+
433
+ ### 🟡 IMPORTANT: Screen Transitions
434
+
435
+ **After navigation actions, wait for destination screen:**
436
+ \`\`\`yaml
437
+ - tapOn: "{submit_or_navigate_button}"
438
+
439
+ - extendedWaitUntil:
440
+ visible: "{element_on_next_screen}"
441
+ timeout: 30000
442
+
443
+ - assertVisible: "{element_on_next_screen}"
444
+ \`\`\`
445
+
446
+ ### 🟢 RECOMMENDED: Before Critical Interactions
447
+
448
+ **Verify element is ready before interaction:**
449
+ \`\`\`yaml
450
+ - extendedWaitUntil:
451
+ visible: "{important_element}"
452
+ timeout: 10000
453
+
454
+ - tapOn: "{important_element}"
455
+ \`\`\`
456
+
457
+ ### 🟢 RECOMMENDED: After Animations
458
+
459
+ **Wait after any action that triggers visual change:**
460
+ \`\`\`yaml
461
+ - tapOn: "{element_causing_animation}"
462
+ - waitForAnimationToEnd
463
+
464
+ - tapOn: "{next_element}"
465
+ \`\`\`
466
+
467
+ ### 🟢 RECOMMENDED: Elements Below Fold
468
+
469
+ **Scroll before interacting with elements that may not be visible:**
470
+ \`\`\`yaml
471
+ - scroll
472
+ - waitForAnimationToEnd
473
+
474
+ - tapOn: "{element_below_fold}"
475
+ \`\`\`
476
+
477
+ ### 🟢 RECOMMENDED: Retry Scenarios
478
+
479
+ **Clear field before re-entering data:**
480
+ \`\`\`yaml
481
+ - tapOn: "{field}"
482
+ - eraseText: 50
483
+ - inputText: "{new_value}"
484
+ \`\`\`
485
+ `;
486
+ }
487
+
488
+ /**
489
+ * Get YAML generation rules derived from patterns
490
+ * These are the core rules that should be applied during generation
491
+ */
492
+ export function getYamlGenerationRules() {
493
+ return {
494
+ // Mandatory rules (apply always)
495
+ mandatory: {
496
+ tapBeforeInput: true,
497
+ hideKeyboardBeforeDropdown: true,
498
+ hideKeyboardBeforeButtonsBelowFields: true,
499
+ waitAfterNavigation: true,
500
+ startWithClearStateAndLaunch: true,
501
+ },
502
+
503
+ // Recommended rules (apply for better reliability)
504
+ recommended: {
505
+ waitAfterAnimations: true,
506
+ verifyBeforeCriticalInteractions: true,
507
+ scrollBeforeElementsBelowFold: true,
508
+ eraseBeforeRetryInput: true,
509
+ doubleWaitAfterKeyboardHide: false, // Only when needed
510
+ },
511
+
512
+ // Timing defaults
513
+ timing: {
514
+ defaultTimeout: 10000,
515
+ navigationTimeout: 30000,
516
+ loadingTimeout: 15000,
517
+ },
518
+ };
519
+ }
520
+
521
+ /**
522
+ * Get platform-agnostic best practices
523
+ * These apply regardless of the app's framework
524
+ */
525
+ export function getBestPractices() {
526
+ return `
527
+ ## BEST PRACTICES FOR RELIABLE MOBILE TESTS
528
+
529
+ ### Test Structure
530
+ 1. ALWAYS start with \`clearState\` then \`launchApp\`
531
+ 2. Wait for initial screen to fully load before first interaction
532
+ 3. End with assertions that verify expected outcome
533
+
534
+ ### Element Interaction
535
+ 1. Use visible text/labels as primary selectors
536
+ 2. Add explicit waits for elements that may load asynchronously
537
+ 3. Hide keyboard before interacting with elements below text fields
538
+
539
+ ### Form Filling
540
+ 1. Tap each field before entering text
541
+ 2. Hide keyboard before tapping submit buttons
542
+ 3. Wait for form submission response
543
+
544
+ ### Navigation
545
+ 1. Wait for destination screen elements after navigation
546
+ 2. Use longer timeouts for network-dependent screens
547
+ 3. Verify you're on correct screen before proceeding
548
+
549
+ ### Error Handling
550
+ 1. Add waits at failure-prone steps
551
+ 2. Scroll if elements might be below visible area
552
+ 3. Hide keyboard if elements might be covered
553
+ `;
554
+ }
555
+
556
+ export default {
557
+ PatternCategory,
558
+ INTERACTION_PATTERNS,
559
+ getPatternsByCategory,
560
+ getPatternCategories,
561
+ getInteractionPatternHints,
562
+ getYamlGenerationRules,
563
+ getBestPractices,
564
+ };