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
|
@@ -3,8 +3,18 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides comprehensive, GENERIC instructions for AI to generate valid Maestro YAML.
|
|
5
5
|
* Works for ANY test scenario - login, forms, navigation, search, etc.
|
|
6
|
+
*
|
|
7
|
+
* Includes Known Issues & Solutions Registry for handling edge cases
|
|
8
|
+
* discovered during test execution across Flutter, React Native, and Native apps.
|
|
6
9
|
*/
|
|
7
10
|
|
|
11
|
+
import {
|
|
12
|
+
getInteractionPatternHints,
|
|
13
|
+
getYamlGenerationRules,
|
|
14
|
+
getBestPractices,
|
|
15
|
+
PatternCategory,
|
|
16
|
+
} from "./knownIssues.js";
|
|
17
|
+
|
|
8
18
|
/**
|
|
9
19
|
* COMPREHENSIVE YAML generation instructions for AI
|
|
10
20
|
* These are GENERIC rules that apply to ALL test scenarios
|
|
@@ -210,6 +220,195 @@ appId: {APP_ID}
|
|
|
210
220
|
|
|
211
221
|
---
|
|
212
222
|
|
|
223
|
+
### 7.1. FLUTTER DROPDOWN PATTERNS (⚠️ CRITICAL FOR FLUTTER APPS)
|
|
224
|
+
|
|
225
|
+
Flutter dropdowns are rendered as overlays and require special handling:
|
|
226
|
+
|
|
227
|
+
#### Method 1: Tap and Select by Visible Text (Preferred)
|
|
228
|
+
\`\`\`yaml
|
|
229
|
+
# Step 1: Tap on the dropdown to open it
|
|
230
|
+
- tapOn: "Current Selected Value" # e.g., "Australia"
|
|
231
|
+
|
|
232
|
+
# Step 2: Wait for dropdown overlay to appear
|
|
233
|
+
- waitForAnimationToEnd
|
|
234
|
+
|
|
235
|
+
# Step 3: Tap on the desired option by its exact text
|
|
236
|
+
- tapOn: "Desired Option Text" # e.g., "United Kingdom"
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
239
|
+
#### Method 2: Using text: selector for partial matching
|
|
240
|
+
\`\`\`yaml
|
|
241
|
+
# Open dropdown
|
|
242
|
+
- tapOn:
|
|
243
|
+
text: "Australia"
|
|
244
|
+
- waitForAnimationToEnd
|
|
245
|
+
|
|
246
|
+
# Select option using partial text match
|
|
247
|
+
- tapOn:
|
|
248
|
+
text: "United Kingdom"
|
|
249
|
+
\`\`\`
|
|
250
|
+
|
|
251
|
+
#### Method 3: Scroll within dropdown to find option
|
|
252
|
+
\`\`\`yaml
|
|
253
|
+
# Open dropdown
|
|
254
|
+
- tapOn: "Australia"
|
|
255
|
+
- waitForAnimationToEnd
|
|
256
|
+
|
|
257
|
+
# If option is not visible, scroll within the dropdown
|
|
258
|
+
- scroll:
|
|
259
|
+
direction: DOWN
|
|
260
|
+
|
|
261
|
+
# Then tap on the option
|
|
262
|
+
- tapOn: "United Kingdom"
|
|
263
|
+
\`\`\`
|
|
264
|
+
|
|
265
|
+
#### Method 4: Using index for dropdown items (when text fails)
|
|
266
|
+
\`\`\`yaml
|
|
267
|
+
# Open dropdown
|
|
268
|
+
- tapOn: "Australia"
|
|
269
|
+
- waitForAnimationToEnd
|
|
270
|
+
|
|
271
|
+
# Select by index (0-based) - e.g., index 1 for second item
|
|
272
|
+
- tapOn:
|
|
273
|
+
index: 1
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
#### Method 5: Using containsText for flexible matching
|
|
277
|
+
\`\`\`yaml
|
|
278
|
+
# Open dropdown
|
|
279
|
+
- tapOn:
|
|
280
|
+
containsText: "Austral"
|
|
281
|
+
- waitForAnimationToEnd
|
|
282
|
+
|
|
283
|
+
# Select using contains
|
|
284
|
+
- tapOn:
|
|
285
|
+
containsText: "United King"
|
|
286
|
+
\`\`\`
|
|
287
|
+
|
|
288
|
+
#### Method 6: Using optional property for robust selection
|
|
289
|
+
\`\`\`yaml
|
|
290
|
+
# Open dropdown and handle if already open
|
|
291
|
+
- tapOn:
|
|
292
|
+
text: "Australia"
|
|
293
|
+
optional: true
|
|
294
|
+
- waitForAnimationToEnd
|
|
295
|
+
|
|
296
|
+
# Try multiple selectors for the option
|
|
297
|
+
- runFlow:
|
|
298
|
+
when:
|
|
299
|
+
visible: "United Kingdom"
|
|
300
|
+
commands:
|
|
301
|
+
- tapOn: "United Kingdom"
|
|
302
|
+
|
|
303
|
+
# Fallback: try by index if text fails
|
|
304
|
+
- runFlow:
|
|
305
|
+
when:
|
|
306
|
+
notVisible: "United Kingdom"
|
|
307
|
+
commands:
|
|
308
|
+
- tapOn:
|
|
309
|
+
index: 1
|
|
310
|
+
\`\`\`
|
|
311
|
+
|
|
312
|
+
#### Method 7: Using assertVisible before tap (for debugging)
|
|
313
|
+
\`\`\`yaml
|
|
314
|
+
# Open dropdown
|
|
315
|
+
- tapOn: "Australia"
|
|
316
|
+
- waitForAnimationToEnd
|
|
317
|
+
|
|
318
|
+
# Verify the dropdown opened and options are visible
|
|
319
|
+
- assertVisible: "United Kingdom"
|
|
320
|
+
|
|
321
|
+
# Now tap
|
|
322
|
+
- tapOn: "United Kingdom"
|
|
323
|
+
\`\`\`
|
|
324
|
+
|
|
325
|
+
#### Method 8: Complete Login with Dropdown Example
|
|
326
|
+
\`\`\`yaml
|
|
327
|
+
appId: com.example.app
|
|
328
|
+
---
|
|
329
|
+
# Login with region selection
|
|
330
|
+
- clearState
|
|
331
|
+
- launchApp
|
|
332
|
+
|
|
333
|
+
# Enter credentials (always tap before input!)
|
|
334
|
+
- tapOn: "Username"
|
|
335
|
+
- inputText: "myuser"
|
|
336
|
+
- tapOn: "Password"
|
|
337
|
+
- inputText: "mypassword"
|
|
338
|
+
|
|
339
|
+
# Hide keyboard before interacting with dropdown
|
|
340
|
+
- hideKeyboard
|
|
341
|
+
|
|
342
|
+
# Select region from dropdown
|
|
343
|
+
- tapOn: "Australia" # Current selection - opens dropdown
|
|
344
|
+
- waitForAnimationToEnd # Wait for overlay
|
|
345
|
+
- tapOn: "United Kingdom" # Desired option
|
|
346
|
+
|
|
347
|
+
# Submit form
|
|
348
|
+
- tapOn: "SIGN IN"
|
|
349
|
+
|
|
350
|
+
# Verify success
|
|
351
|
+
- assertVisible: "Welcome"
|
|
352
|
+
\`\`\`
|
|
353
|
+
|
|
354
|
+
#### Flutter Dropdown Tips:
|
|
355
|
+
1. ⚠️ **ALWAYS hideKeyboard** before tapping a dropdown - keyboard may cover it!
|
|
356
|
+
2. ⚠️ **ALWAYS waitForAnimationToEnd** after opening dropdown
|
|
357
|
+
3. ⚠️ Use **exact text** of the dropdown options when possible
|
|
358
|
+
4. ⚠️ If options are scrollable, add **scroll** command before selecting
|
|
359
|
+
5. ⚠️ Some Flutter dropdowns need a **pressKey: escape** to close if tapped outside
|
|
360
|
+
6. ⚠️ Use **assertVisible** before tapOn to verify dropdown is open
|
|
361
|
+
|
|
362
|
+
#### Flutter Accessibility Issue - When Text Matching Fails:
|
|
363
|
+
⚠️ **CRITICAL**: Flutter apps often expose elements only via \`accessibilityText\`, not \`text\`.
|
|
364
|
+
When Maestro cannot find elements by text, use **point coordinates**:
|
|
365
|
+
|
|
366
|
+
\`\`\`yaml
|
|
367
|
+
# Step 1: Use maestro hierarchy command to get element bounds
|
|
368
|
+
# Example: bounds = "[53,1238][1028,1386]"
|
|
369
|
+
# Calculate center: X = (53+1028)/2 = 540, Y = (1238+1386)/2 = 1312
|
|
370
|
+
|
|
371
|
+
# Step 2: Tap by coordinates
|
|
372
|
+
- tapOn:
|
|
373
|
+
point: 540,1312
|
|
374
|
+
|
|
375
|
+
# For dropdown options, get bounds from hierarchy and tap centers:
|
|
376
|
+
- tapOn:
|
|
377
|
+
point: 540,1438 # Center of "United Kingdom" option
|
|
378
|
+
\`\`\`
|
|
379
|
+
|
|
380
|
+
**How to get coordinates:**
|
|
381
|
+
1. Run \`maestro hierarchy\` to dump UI tree
|
|
382
|
+
2. Find element by \`accessibilityText\` in the JSON
|
|
383
|
+
3. Extract \`bounds\`: "[left,top][right,bottom]"
|
|
384
|
+
4. Calculate center: X = (left+right)/2, Y = (top+bottom)/2
|
|
385
|
+
5. Use \`tapOn: point: X,Y\`
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
### 7.2. PICKER AND DATE PICKER PATTERNS (Mobile)
|
|
390
|
+
|
|
391
|
+
\`\`\`yaml
|
|
392
|
+
# Date Picker
|
|
393
|
+
- tapOn: "Date Field"
|
|
394
|
+
- waitForAnimationToEnd
|
|
395
|
+
- scroll:
|
|
396
|
+
direction: DOWN
|
|
397
|
+
element: "year picker"
|
|
398
|
+
- tapOn: "2024"
|
|
399
|
+
- tapOn: "OK"
|
|
400
|
+
|
|
401
|
+
# Time Picker
|
|
402
|
+
- tapOn: "Time Field"
|
|
403
|
+
- waitForAnimationToEnd
|
|
404
|
+
- tapOn: "10" # Hour
|
|
405
|
+
- tapOn: "30" # Minutes
|
|
406
|
+
- tapOn: "AM" # Period
|
|
407
|
+
- tapOn: "OK"
|
|
408
|
+
\`\`\`
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
213
412
|
### 8. ❌ NEVER DO THESE
|
|
214
413
|
|
|
215
414
|
1. ❌ **NEVER** use \`inputText\` without \`tapOn\` first
|
|
@@ -218,6 +417,8 @@ appId: {APP_ID}
|
|
|
218
417
|
4. ❌ **NEVER** use coordinates (x,y) unless absolutely necessary
|
|
219
418
|
5. ❌ **NEVER** use hardcoded waits (\`sleep\`) - use \`extendedWaitUntil\`
|
|
220
419
|
6. ❌ **NEVER** forget to hide keyboard after text input if it blocks elements
|
|
420
|
+
7. ❌ **NEVER** tap dropdown options without \`waitForAnimationToEnd\` after opening dropdown
|
|
421
|
+
8. ❌ **NEVER** try to select dropdown option while keyboard is visible
|
|
221
422
|
|
|
222
423
|
---
|
|
223
424
|
|
|
@@ -229,6 +430,9 @@ appId: {APP_ID}
|
|
|
229
430
|
4. ✅ **ALWAYS** add \`assertVisible\` to verify expected results
|
|
230
431
|
5. ✅ **ALWAYS** use waits for elements that may take time to load
|
|
231
432
|
6. ✅ **ALWAYS** hide keyboard if it might block buttons: \`- hideKeyboard\`
|
|
433
|
+
7. ✅ **ALWAYS** use \`hideKeyboard\` BEFORE interacting with dropdowns
|
|
434
|
+
8. ✅ **ALWAYS** use \`waitForAnimationToEnd\` AFTER opening a dropdown/picker
|
|
435
|
+
9. ✅ **ALWAYS** tap the CURRENT VALUE to open dropdown, then tap the DESIRED VALUE
|
|
232
436
|
|
|
233
437
|
---
|
|
234
438
|
|
|
@@ -304,6 +508,12 @@ export function getYamlGenerationContext(appId, appContext = null) {
|
|
|
304
508
|
}
|
|
305
509
|
}
|
|
306
510
|
|
|
511
|
+
// Add generic interaction patterns for reliable tests
|
|
512
|
+
context += getInteractionPatternHints();
|
|
513
|
+
|
|
514
|
+
// Add best practices
|
|
515
|
+
context += getBestPractices();
|
|
516
|
+
|
|
307
517
|
return context;
|
|
308
518
|
}
|
|
309
519
|
|
|
@@ -402,6 +612,86 @@ appId: {APP_ID}
|
|
|
402
612
|
- scroll # If logout is below fold
|
|
403
613
|
- tapOn: "{logout_button}"
|
|
404
614
|
- assertVisible: "{login_screen_element}"
|
|
615
|
+
`,
|
|
616
|
+
|
|
617
|
+
dropdown: `# Dropdown/Picker Selection Pattern
|
|
618
|
+
appId: {APP_ID}
|
|
619
|
+
---
|
|
620
|
+
- clearState
|
|
621
|
+
- launchApp
|
|
622
|
+
|
|
623
|
+
# IMPORTANT: Hide keyboard first if any text field was focused
|
|
624
|
+
- hideKeyboard
|
|
625
|
+
|
|
626
|
+
# Step 1: Tap on dropdown (tap the CURRENT selected value)
|
|
627
|
+
- tapOn: "{current_selected_value}"
|
|
628
|
+
|
|
629
|
+
# Step 2: ALWAYS wait for dropdown overlay to appear
|
|
630
|
+
- waitForAnimationToEnd
|
|
631
|
+
|
|
632
|
+
# Step 3: If options might be scrollable, scroll to find the option
|
|
633
|
+
# - scroll:
|
|
634
|
+
# direction: DOWN
|
|
635
|
+
|
|
636
|
+
# Step 4: Tap on the desired option
|
|
637
|
+
- tapOn: "{desired_option_value}"
|
|
638
|
+
|
|
639
|
+
# Step 5: Verify selection (optional)
|
|
640
|
+
- assertVisible: "{desired_option_value}"
|
|
641
|
+
`,
|
|
642
|
+
|
|
643
|
+
flutter_login_with_dropdown: `# Flutter Login with Dropdown Pattern (e.g., Region Selection)
|
|
644
|
+
appId: {APP_ID}
|
|
645
|
+
---
|
|
646
|
+
# Login with dropdown selection (e.g., region/country picker)
|
|
647
|
+
- clearState
|
|
648
|
+
- launchApp
|
|
649
|
+
|
|
650
|
+
# Enter username
|
|
651
|
+
- tapOn: "{username_field}"
|
|
652
|
+
- inputText: "{username}"
|
|
653
|
+
|
|
654
|
+
# Enter password
|
|
655
|
+
- tapOn: "{password_field}"
|
|
656
|
+
- inputText: "{password}"
|
|
657
|
+
|
|
658
|
+
# CRITICAL: Hide keyboard before dropdown interaction!
|
|
659
|
+
- hideKeyboard
|
|
660
|
+
|
|
661
|
+
# Select from dropdown
|
|
662
|
+
- tapOn: "{current_dropdown_value}" # e.g., "Australia"
|
|
663
|
+
- waitForAnimationToEnd # Wait for dropdown overlay
|
|
664
|
+
- tapOn: "{desired_dropdown_value}" # e.g., "United Kingdom"
|
|
665
|
+
|
|
666
|
+
# Submit
|
|
667
|
+
- tapOn: "{submit_button}"
|
|
668
|
+
|
|
669
|
+
# Verify success
|
|
670
|
+
- assertVisible: "{success_indicator}"
|
|
671
|
+
`,
|
|
672
|
+
|
|
673
|
+
flutter_multi_dropdown: `# Flutter Multiple Dropdowns Pattern
|
|
674
|
+
appId: {APP_ID}
|
|
675
|
+
---
|
|
676
|
+
- clearState
|
|
677
|
+
- launchApp
|
|
678
|
+
|
|
679
|
+
# First dropdown
|
|
680
|
+
- hideKeyboard
|
|
681
|
+
- tapOn: "{dropdown_1_current_value}"
|
|
682
|
+
- waitForAnimationToEnd
|
|
683
|
+
- tapOn: "{dropdown_1_desired_value}"
|
|
684
|
+
|
|
685
|
+
# Wait for first dropdown to close
|
|
686
|
+
- waitForAnimationToEnd
|
|
687
|
+
|
|
688
|
+
# Second dropdown
|
|
689
|
+
- tapOn: "{dropdown_2_current_value}"
|
|
690
|
+
- waitForAnimationToEnd
|
|
691
|
+
- tapOn: "{dropdown_2_desired_value}"
|
|
692
|
+
|
|
693
|
+
# Continue with form...
|
|
694
|
+
- tapOn: "{submit_button}"
|
|
405
695
|
`,
|
|
406
696
|
};
|
|
407
697
|
|
|
@@ -550,10 +840,23 @@ This ensures consistent YAML generation next time!
|
|
|
550
840
|
`;
|
|
551
841
|
}
|
|
552
842
|
|
|
843
|
+
// Re-export interaction pattern utilities for direct access
|
|
844
|
+
export {
|
|
845
|
+
getInteractionPatternHints,
|
|
846
|
+
getYamlGenerationRules,
|
|
847
|
+
getBestPractices,
|
|
848
|
+
PatternCategory,
|
|
849
|
+
} from "./knownIssues.js";
|
|
850
|
+
|
|
553
851
|
export default {
|
|
554
852
|
YAML_GENERATION_INSTRUCTIONS,
|
|
555
853
|
getYamlGenerationContext,
|
|
556
854
|
TEST_PATTERNS,
|
|
557
855
|
validateYamlStructure,
|
|
558
856
|
getScreenAnalysisInstructions,
|
|
857
|
+
// Interaction pattern utilities
|
|
858
|
+
getInteractionPatternHints,
|
|
859
|
+
getYamlGenerationRules,
|
|
860
|
+
getBestPractices,
|
|
861
|
+
PatternCategory,
|
|
559
862
|
};
|