mcp-maestro-mobile-ai 1.1.1 → 1.3.1

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,559 @@
1
+ /**
2
+ * YAML Generation Template System
3
+ *
4
+ * Provides comprehensive, GENERIC instructions for AI to generate valid Maestro YAML.
5
+ * Works for ANY test scenario - login, forms, navigation, search, etc.
6
+ */
7
+
8
+ /**
9
+ * COMPREHENSIVE YAML generation instructions for AI
10
+ * These are GENERIC rules that apply to ALL test scenarios
11
+ */
12
+ export const YAML_GENERATION_INSTRUCTIONS = `
13
+ ## MAESTRO YAML GENERATION RULES (GENERIC - FOR ALL SCENARIOS)
14
+
15
+ ⚠️ CRITICAL: Follow these rules EXACTLY for ALL test types.
16
+
17
+ ---
18
+
19
+ ### 1. YAML STRUCTURE (ALWAYS REQUIRED)
20
+
21
+ Every YAML file MUST have this structure:
22
+
23
+ \`\`\`yaml
24
+ appId: {APP_ID}
25
+ ---
26
+ # Description of what this test does
27
+ - clearState # ALWAYS first - clears app data
28
+ - launchApp # ALWAYS second - opens the app
29
+ # ... your test steps here ...
30
+ \`\`\`
31
+
32
+ ---
33
+
34
+ ### 2. TEXT INPUT PATTERN (⚠️ MOST CRITICAL RULE)
35
+
36
+ **RULE: You MUST ALWAYS tap on a field BEFORE entering text.**
37
+
38
+ ✅ CORRECT - Always use this pattern:
39
+ \`\`\`yaml
40
+ - tapOn: "Field Name" # Step 1: Tap to focus the field
41
+ - inputText: "your value" # Step 2: Then type the text
42
+ \`\`\`
43
+
44
+ ❌ WRONG - This will put text in the WRONG field:
45
+ \`\`\`yaml
46
+ - inputText: "your value" # NO! Missing tapOn - text goes to currently focused field!
47
+ \`\`\`
48
+
49
+ **Example for ANY form with multiple fields:**
50
+ \`\`\`yaml
51
+ # First field
52
+ - tapOn: "First Field Label"
53
+ - inputText: "first value"
54
+
55
+ # Second field
56
+ - tapOn: "Second Field Label"
57
+ - inputText: "second value"
58
+
59
+ # Third field
60
+ - tapOn: "Third Field Label"
61
+ - inputText: "third value"
62
+
63
+ # Submit
64
+ - tapOn: "Submit Button"
65
+ \`\`\`
66
+
67
+ ---
68
+
69
+ ### 3. ELEMENT SELECTION (How to identify UI elements)
70
+
71
+ Use these selectors IN ORDER of preference:
72
+
73
+ | Priority | Selector Type | YAML Syntax | When to Use |
74
+ |----------|--------------|-------------|-------------|
75
+ | 1 (Best) | testID | \`- tapOn: id: "element-id"\` | When you know the testID |
76
+ | 2 | accessibilityLabel | \`- tapOn: "Label Text"\` | For accessibility labels |
77
+ | 3 | Visible Text | \`- tapOn: "Button Text"\` | For visible button/link text |
78
+ | 4 | Contains Text | \`- tapOn: text: "partial"\` | When text partially matches |
79
+ | 5 (Last) | Index | \`- tapOn: index: 0\` | Only if nothing else works |
80
+
81
+ **NEVER use coordinates unless absolutely necessary.**
82
+
83
+ ---
84
+
85
+ ### 4. COMMON ACTIONS REFERENCE
86
+
87
+ | What You Want | YAML Command | Example |
88
+ |---------------|--------------|---------|
89
+ | Tap a button/element | \`- tapOn:\` | \`- tapOn: "Submit"\` |
90
+ | Tap by testID | \`- tapOn: id:\` | \`- tapOn: id: "submit-btn"\` |
91
+ | Enter text (after tapOn) | \`- inputText:\` | \`- inputText: "hello"\` |
92
+ | Clear text field | \`- eraseText: 50\` | Erases 50 characters |
93
+ | Assert element visible | \`- assertVisible:\` | \`- assertVisible: "Success"\` |
94
+ | Assert NOT visible | \`- assertNotVisible:\` | \`- assertNotVisible: "Error"\` |
95
+ | Wait for element | \`- extendedWaitUntil:\` | See below |
96
+ | Scroll down | \`- scroll\` | \`- scroll\` |
97
+ | Scroll in direction | \`- scroll:\` | \`- scroll: DOWN\` |
98
+ | Swipe | \`- swipe:\` | \`- swipe: LEFT\` |
99
+ | Press back button | \`- pressKey: back\` | \`- pressKey: back\` |
100
+ | Press enter/done | \`- pressKey: enter\` | \`- pressKey: enter\` |
101
+ | Hide keyboard | \`- hideKeyboard\` | \`- hideKeyboard\` |
102
+ | Wait (seconds) | \`- waitForAnimationToEnd\` | \`- waitForAnimationToEnd\` |
103
+ | Long press | \`- longPressOn:\` | \`- longPressOn: "Element"\` |
104
+ | Double tap | \`- doubleTapOn:\` | \`- doubleTapOn: "Element"\` |
105
+
106
+ ---
107
+
108
+ ### 5. WAITING FOR ELEMENTS
109
+
110
+ When an element might take time to appear:
111
+
112
+ \`\`\`yaml
113
+ # Wait up to 10 seconds for element
114
+ - extendedWaitUntil:
115
+ visible: "Element Text or ID"
116
+ timeout: 10000
117
+
118
+ # Or use assertVisible with implicit wait
119
+ - assertVisible: "Element"
120
+ \`\`\`
121
+
122
+ ---
123
+
124
+ ### 6. CONDITIONAL CHECKS
125
+
126
+ \`\`\`yaml
127
+ # Run steps only if element is visible
128
+ - runFlow:
129
+ when:
130
+ visible: "Optional Element"
131
+ commands:
132
+ - tapOn: "Optional Element"
133
+ \`\`\`
134
+
135
+ ---
136
+
137
+ ### 7. GENERIC TEST PATTERNS
138
+
139
+ #### Pattern A: Any Form Submission
140
+ \`\`\`yaml
141
+ appId: {APP_ID}
142
+ ---
143
+ # Fill out form
144
+ - clearState
145
+ - launchApp
146
+
147
+ # Navigate to form if needed
148
+ - tapOn: "Menu or Tab Name"
149
+
150
+ # Fill each field (ALWAYS tapOn first!)
151
+ - tapOn: "Field 1 Label"
152
+ - inputText: "Value 1"
153
+
154
+ - tapOn: "Field 2 Label"
155
+ - inputText: "Value 2"
156
+
157
+ - tapOn: "Field 3 Label"
158
+ - inputText: "Value 3"
159
+
160
+ # Submit
161
+ - tapOn: "Submit Button Text"
162
+
163
+ # Verify success
164
+ - assertVisible: "Success Indicator"
165
+ \`\`\`
166
+
167
+ #### Pattern B: Navigation Test
168
+ \`\`\`yaml
169
+ appId: {APP_ID}
170
+ ---
171
+ - clearState
172
+ - launchApp
173
+ - tapOn: "Menu Item or Tab"
174
+ - assertVisible: "Expected Screen Element"
175
+ \`\`\`
176
+
177
+ #### Pattern C: Search Test
178
+ \`\`\`yaml
179
+ appId: {APP_ID}
180
+ ---
181
+ - clearState
182
+ - launchApp
183
+ - tapOn: "Search Field or Icon"
184
+ - inputText: "search query"
185
+ - pressKey: enter
186
+ - assertVisible: "Expected Result"
187
+ \`\`\`
188
+
189
+ #### Pattern D: List Interaction
190
+ \`\`\`yaml
191
+ appId: {APP_ID}
192
+ ---
193
+ - clearState
194
+ - launchApp
195
+ - scroll # Scroll to find item
196
+ - tapOn: "List Item Text"
197
+ - assertVisible: "Detail View Element"
198
+ \`\`\`
199
+
200
+ #### Pattern E: Settings/Toggle Test
201
+ \`\`\`yaml
202
+ appId: {APP_ID}
203
+ ---
204
+ - clearState
205
+ - launchApp
206
+ - tapOn: "Settings"
207
+ - tapOn: "Toggle or Switch Label"
208
+ - assertVisible: "Changed State Indicator"
209
+ \`\`\`
210
+
211
+ ---
212
+
213
+ ### 8. ❌ NEVER DO THESE
214
+
215
+ 1. ❌ **NEVER** use \`inputText\` without \`tapOn\` first
216
+ 2. ❌ **NEVER** skip \`clearState\` and \`launchApp\`
217
+ 3. ❌ **NEVER** assume testIDs - use visible text if unsure
218
+ 4. ❌ **NEVER** use coordinates (x,y) unless absolutely necessary
219
+ 5. ❌ **NEVER** use hardcoded waits (\`sleep\`) - use \`extendedWaitUntil\`
220
+ 6. ❌ **NEVER** forget to hide keyboard after text input if it blocks elements
221
+
222
+ ---
223
+
224
+ ### 9. ✅ ALWAYS DO THESE
225
+
226
+ 1. ✅ **ALWAYS** start with \`clearState\` then \`launchApp\`
227
+ 2. ✅ **ALWAYS** use \`tapOn\` before \`inputText\`
228
+ 3. ✅ **ALWAYS** use visible text/labels when testIDs are unknown
229
+ 4. ✅ **ALWAYS** add \`assertVisible\` to verify expected results
230
+ 5. ✅ **ALWAYS** use waits for elements that may take time to load
231
+ 6. ✅ **ALWAYS** hide keyboard if it might block buttons: \`- hideKeyboard\`
232
+
233
+ ---
234
+
235
+ ### 10. DEBUGGING TIPS
236
+
237
+ If a test fails:
238
+ 1. Add \`- assertVisible: "Expected Element"\` before the failing step
239
+ 2. Add waits with \`extendedWaitUntil\` for slow-loading elements
240
+ 3. Add \`- hideKeyboard\` before tapping buttons that might be covered
241
+ 4. Check if the element text matches exactly (case-sensitive)
242
+ `;
243
+
244
+ /**
245
+ * Get YAML generation context for a specific app
246
+ */
247
+ export function getYamlGenerationContext(appId, appContext = null) {
248
+ let context = YAML_GENERATION_INSTRUCTIONS.replace(/{APP_ID}/g, appId);
249
+
250
+ // Add app-specific context if available
251
+ if (
252
+ appContext &&
253
+ appContext.elements &&
254
+ Object.keys(appContext.elements).length > 0
255
+ ) {
256
+ context += `\n---\n\n## APP-SPECIFIC ELEMENTS FOR ${appId}\n\n`;
257
+ context += `Use these EXACT element identifiers:\n\n`;
258
+ context += `| Element Name | Selector | Type |\n`;
259
+ context += `|--------------|----------|------|\n`;
260
+
261
+ for (const [name, element] of Object.entries(appContext.elements)) {
262
+ let selector = "";
263
+ if (element.testId) {
264
+ selector = `id: "${element.testId}"`;
265
+ } else if (element.accessibilityLabel) {
266
+ selector = `"${element.accessibilityLabel}"`;
267
+ } else if (element.text) {
268
+ selector = `"${element.text}"`;
269
+ }
270
+ const type = element.type || "unknown";
271
+ context += `| ${name} | \`${selector}\` | ${type} |\n`;
272
+ }
273
+ context += `\n`;
274
+ }
275
+
276
+ // Add screen information if available
277
+ if (
278
+ appContext &&
279
+ appContext.screens &&
280
+ Object.keys(appContext.screens).length > 0
281
+ ) {
282
+ context += `\n## SCREEN STRUCTURE\n\n`;
283
+ for (const [screenName, screen] of Object.entries(appContext.screens)) {
284
+ context += `### ${screenName}\n`;
285
+ if (screen.description) context += `${screen.description}\n\n`;
286
+ if (screen.elements && screen.elements.length > 0) {
287
+ context += `**Elements on this screen:** ${screen.elements.join(
288
+ ", "
289
+ )}\n\n`;
290
+ }
291
+ }
292
+ }
293
+
294
+ // Add example flows if available
295
+ if (appContext && appContext.flows && appContext.flows.length > 0) {
296
+ context += `\n## REFERENCE FLOWS (Copy these patterns)\n\n`;
297
+
298
+ // Show last 5 successful flows
299
+ const recentFlows = appContext.flows.slice(-5);
300
+ for (const flow of recentFlows) {
301
+ context += `### ${flow.name}\n`;
302
+ if (flow.description) context += `${flow.description}\n`;
303
+ context += `\`\`\`yaml\n${flow.yaml}\n\`\`\`\n\n`;
304
+ }
305
+ }
306
+
307
+ return context;
308
+ }
309
+
310
+ /**
311
+ * Common test patterns for standard scenarios
312
+ */
313
+ export const TEST_PATTERNS = {
314
+ login: `# Login Test Pattern
315
+ appId: {APP_ID}
316
+ ---
317
+ # Login with credentials
318
+ - clearState
319
+ - launchApp
320
+ - tapOn: "{username_field}" # Username field - MUST tap first!
321
+ - inputText: "{username}"
322
+ - tapOn: "{password_field}" # Password field - MUST tap first!
323
+ - inputText: "{password}"
324
+ - hideKeyboard # Hide keyboard before tapping button
325
+ - tapOn: "{login_button}"
326
+ - assertVisible: "{success_indicator}"
327
+ `,
328
+
329
+ form: `# Generic Form Pattern
330
+ appId: {APP_ID}
331
+ ---
332
+ # Fill and submit any form
333
+ - clearState
334
+ - launchApp
335
+
336
+ # For EACH field - always tapOn then inputText:
337
+ - tapOn: "{field_1_label}"
338
+ - inputText: "{field_1_value}"
339
+
340
+ - tapOn: "{field_2_label}"
341
+ - inputText: "{field_2_value}"
342
+
343
+ - tapOn: "{field_3_label}"
344
+ - inputText: "{field_3_value}"
345
+
346
+ # Hide keyboard if needed
347
+ - hideKeyboard
348
+
349
+ # Submit
350
+ - tapOn: "{submit_button}"
351
+ - assertVisible: "{success_message}"
352
+ `,
353
+
354
+ search: `# Search Pattern
355
+ appId: {APP_ID}
356
+ ---
357
+ - clearState
358
+ - launchApp
359
+ - tapOn: "{search_field}"
360
+ - inputText: "{search_term}"
361
+ - pressKey: enter
362
+ - assertVisible: "{result_indicator}"
363
+ `,
364
+
365
+ navigation: `# Navigation Pattern
366
+ appId: {APP_ID}
367
+ ---
368
+ - clearState
369
+ - launchApp
370
+ - tapOn: "{menu_or_tab}"
371
+ - assertVisible: "{expected_screen_element}"
372
+ `,
373
+
374
+ list: `# List Interaction Pattern
375
+ appId: {APP_ID}
376
+ ---
377
+ - clearState
378
+ - launchApp
379
+ - scroll # Scroll to find item if needed
380
+ - tapOn: "{list_item}"
381
+ - assertVisible: "{detail_element}"
382
+ `,
383
+
384
+ settings: `# Settings/Toggle Pattern
385
+ appId: {APP_ID}
386
+ ---
387
+ - clearState
388
+ - launchApp
389
+ - tapOn: "Settings" # or settings icon
390
+ - scroll # Scroll to find setting if needed
391
+ - tapOn: "{setting_name}"
392
+ - assertVisible: "{changed_state}"
393
+ `,
394
+
395
+ logout: `# Logout Pattern
396
+ appId: {APP_ID}
397
+ ---
398
+ - clearState
399
+ - launchApp
400
+ # Assuming already logged in
401
+ - tapOn: "{menu_or_profile}"
402
+ - scroll # If logout is below fold
403
+ - tapOn: "{logout_button}"
404
+ - assertVisible: "{login_screen_element}"
405
+ `,
406
+ };
407
+
408
+ /**
409
+ * Validate YAML structure for common issues
410
+ */
411
+ export function validateYamlStructure(yamlContent) {
412
+ const issues = [];
413
+ const lines = yamlContent.split("\n");
414
+
415
+ let hasAppId = false;
416
+ let hasClearState = false;
417
+ let hasLaunchApp = false;
418
+ let lastWasTapOn = false;
419
+ let lastWasEraseText = false;
420
+ let lineNum = 0;
421
+
422
+ for (const line of lines) {
423
+ lineNum++;
424
+ const trimmed = line.trim();
425
+
426
+ // Skip comments and empty lines
427
+ if (trimmed.startsWith("#") || trimmed === "" || trimmed === "---") {
428
+ continue;
429
+ }
430
+
431
+ // Check for appId
432
+ if (trimmed.startsWith("appId:")) {
433
+ hasAppId = true;
434
+ }
435
+
436
+ // Check for clearState
437
+ if (trimmed === "- clearState") {
438
+ hasClearState = true;
439
+ }
440
+
441
+ // Check for launchApp
442
+ if (trimmed === "- launchApp") {
443
+ hasLaunchApp = true;
444
+ }
445
+
446
+ // Check for inputText without preceding tapOn or eraseText
447
+ if (
448
+ trimmed.startsWith("- inputText:") &&
449
+ !lastWasTapOn &&
450
+ !lastWasEraseText
451
+ ) {
452
+ issues.push({
453
+ line: lineNum,
454
+ severity: "error",
455
+ issue:
456
+ "inputText without preceding tapOn - TEXT WILL GO TO WRONG FIELD!",
457
+ suggestion: 'Add "- tapOn: \\"FieldName\\"" BEFORE this inputText',
458
+ code: trimmed,
459
+ });
460
+ }
461
+
462
+ // Track last action
463
+ lastWasTapOn = trimmed.startsWith("- tapOn:");
464
+ lastWasEraseText = trimmed.startsWith("- eraseText");
465
+ }
466
+
467
+ // Structure checks
468
+ if (!hasAppId) {
469
+ issues.push({
470
+ line: 1,
471
+ severity: "error",
472
+ issue: "Missing appId at the beginning",
473
+ suggestion: 'Add "appId: com.your.app" at the top of the YAML',
474
+ });
475
+ }
476
+
477
+ if (!hasClearState) {
478
+ issues.push({
479
+ line: 1,
480
+ severity: "warning",
481
+ issue: "Missing clearState - app may have stale data from previous runs",
482
+ suggestion: 'Add "- clearState" as the first command after ---',
483
+ });
484
+ }
485
+
486
+ if (!hasLaunchApp) {
487
+ issues.push({
488
+ line: 1,
489
+ severity: "error",
490
+ issue: "Missing launchApp - app will not start!",
491
+ suggestion: 'Add "- launchApp" after clearState',
492
+ });
493
+ }
494
+
495
+ const errors = issues.filter((i) => i.severity === "error");
496
+ const warnings = issues.filter((i) => i.severity === "warning");
497
+
498
+ return {
499
+ valid: errors.length === 0,
500
+ hasWarnings: warnings.length > 0,
501
+ errors,
502
+ warnings,
503
+ issues,
504
+ summary:
505
+ errors.length > 0
506
+ ? `❌ ${errors.length} error(s) found - YAML will likely fail!`
507
+ : warnings.length > 0
508
+ ? `⚠️ ${warnings.length} warning(s) - YAML may work but has issues`
509
+ : "✅ YAML structure looks valid",
510
+ };
511
+ }
512
+
513
+ /**
514
+ * Get UI hierarchy from device (for understanding current screen)
515
+ */
516
+ export function getScreenAnalysisInstructions() {
517
+ return `
518
+ ## How to Analyze the Current Screen
519
+
520
+ Before generating YAML, you should understand what's on the screen:
521
+
522
+ 1. **Ask the user for element names/labels visible on screen**
523
+ 2. **Use the take_screenshot tool** to capture current state
524
+ 3. **Check if app context is registered** with get_ai_context
525
+
526
+ ### Questions to Ask User:
527
+
528
+ For a LOGIN screen:
529
+ - "What is the label/placeholder of the username field?"
530
+ - "What is the label/placeholder of the password field?"
531
+ - "What is the text on the login button?"
532
+
533
+ For ANY form:
534
+ - "What are the labels of each input field?"
535
+ - "What is the text on the submit/save button?"
536
+ - "What should appear after successful submission?"
537
+
538
+ ### If User Provides Element Info, Register It:
539
+
540
+ Use register_elements to save for future use:
541
+ \`\`\`
542
+ {
543
+ "usernameField": { "text": "Username", "type": "textfield" },
544
+ "passwordField": { "text": "Password", "type": "textfield" },
545
+ "loginButton": { "text": "SIGN IN", "type": "button" }
546
+ }
547
+ \`\`\`
548
+
549
+ This ensures consistent YAML generation next time!
550
+ `;
551
+ }
552
+
553
+ export default {
554
+ YAML_GENERATION_INSTRUCTIONS,
555
+ getYamlGenerationContext,
556
+ TEST_PATTERNS,
557
+ validateYamlStructure,
558
+ getScreenAnalysisInstructions,
559
+ };