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.
- package/CHANGELOG.md +32 -0
- package/package.json +1 -1
- package/src/mcp-server/index.js +200 -11
- package/src/mcp-server/tools/contextTools.js +123 -0
- package/src/mcp-server/tools/runTools.js +227 -4
- package/src/mcp-server/utils/reportGenerator.js +455 -0
- package/src/mcp-server/utils/yamlTemplate.js +559 -0
|
@@ -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
|
+
};
|