mcp-maestro-mobile-ai 1.3.1 → 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.
- package/CHANGELOG.md +344 -152
- package/ROADMAP.md +21 -8
- package/package.json +9 -3
- package/src/mcp-server/index.js +1394 -826
- package/src/mcp-server/schemas/toolSchemas.js +820 -0
- package/src/mcp-server/tools/contextTools.js +309 -2
- package/src/mcp-server/tools/runTools.js +409 -31
- package/src/mcp-server/utils/knownIssues.js +564 -0
- package/src/mcp-server/utils/maestro.js +265 -29
- package/src/mcp-server/utils/promptAnalyzer.js +701 -0
- package/src/mcp-server/utils/security.js +1200 -0
- package/src/mcp-server/utils/yamlCache.js +381 -0
- package/src/mcp-server/utils/yamlGenerator.js +426 -0
- package/src/mcp-server/utils/yamlTemplate.js +303 -0
|
@@ -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
|
+
};
|