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.
@@ -20,7 +20,26 @@ import {
20
20
  TEST_PATTERNS,
21
21
  validateYamlStructure,
22
22
  getScreenAnalysisInstructions,
23
+ getInteractionPatternHints,
24
+ getYamlGenerationRules,
25
+ getBestPractices,
26
+ PatternCategory,
23
27
  } from "../utils/yamlTemplate.js";
28
+ import {
29
+ INTERACTION_PATTERNS,
30
+ getPatternsByCategory,
31
+ getPatternCategories,
32
+ } from "../utils/knownIssues.js";
33
+ import {
34
+ analyzePrompt,
35
+ generateClarificationQuestions,
36
+ generateWarnings,
37
+ CompletenessLevel,
38
+ } from "../utils/promptAnalyzer.js";
39
+ import {
40
+ generateYamlFromAnalysis,
41
+ formatGenerationResponse,
42
+ } from "../utils/yamlGenerator.js";
24
43
 
25
44
  /**
26
45
  * Format result as MCP response
@@ -259,6 +278,9 @@ export async function getTestPattern(patternName) {
259
278
  list: TEST_PATTERNS.list,
260
279
  settings: TEST_PATTERNS.settings,
261
280
  logout: TEST_PATTERNS.logout,
281
+ dropdown: TEST_PATTERNS.dropdown,
282
+ flutter_login_with_dropdown: TEST_PATTERNS.flutter_login_with_dropdown,
283
+ flutter_multi_dropdown: TEST_PATTERNS.flutter_multi_dropdown,
262
284
  };
263
285
 
264
286
  const pattern = patterns[patternName?.toLowerCase()];
@@ -268,7 +290,7 @@ export async function getTestPattern(patternName) {
268
290
  success: false,
269
291
  error: `Unknown pattern: ${patternName}`,
270
292
  availablePatterns: Object.keys(patterns),
271
- hint: "Use: login, form, search, navigation, list, settings, or logout",
293
+ hint: "Use: login, form, search, navigation, list, settings, logout, dropdown, flutter_login_with_dropdown, or flutter_multi_dropdown",
272
294
  });
273
295
  }
274
296
 
@@ -276,7 +298,7 @@ export async function getTestPattern(patternName) {
276
298
  success: true,
277
299
  pattern: patternName,
278
300
  template: pattern,
279
- message: "Replace placeholders in {} with actual values. REMEMBER: Always use tapOn before inputText!",
301
+ message: "Replace placeholders in {} with actual values. REMEMBER: Always use tapOn before inputText! For dropdowns: hideKeyboard -> tapCurrentValue -> waitForAnimationToEnd -> tapDesiredValue",
280
302
  });
281
303
  }
282
304
 
@@ -299,6 +321,284 @@ export async function getScreenAnalysis() {
299
321
  });
300
322
  }
301
323
 
324
+ /**
325
+ * Get interaction patterns for reliable YAML generation
326
+ * These patterns are framework-agnostic and work across all mobile apps.
327
+ */
328
+ export async function getKnownIssues(category = null) {
329
+ let patterns = INTERACTION_PATTERNS;
330
+
331
+ if (category) {
332
+ patterns = getPatternsByCategory(category);
333
+ }
334
+
335
+ // Format patterns for display
336
+ const formattedPatterns = Object.entries(patterns).map(([key, pattern]) => ({
337
+ name: key,
338
+ category: pattern.category,
339
+ situation: pattern.situation,
340
+ strategy: pattern.strategy,
341
+ rationale: pattern.rationale,
342
+ }));
343
+
344
+ return formatResponse({
345
+ success: true,
346
+ count: formattedPatterns.length,
347
+ patterns: formattedPatterns,
348
+ availableCategories: getPatternCategories(),
349
+ message: "These patterns are automatically included in YAML generation. Apply them to create reliable tests.",
350
+ });
351
+ }
352
+
353
+ /**
354
+ * Get interaction patterns by category
355
+ */
356
+ export async function getPlatformRules(category) {
357
+ const validCategories = getPatternCategories();
358
+
359
+ if (!category || !validCategories.includes(category)) {
360
+ return formatResponse({
361
+ success: false,
362
+ error: `Invalid category: ${category}`,
363
+ validCategories,
364
+ hint: "Use: keyboard_handling, element_interaction, timing_synchronization, input_handling, dropdown_picker, navigation, scroll_visibility, or fallback_strategies",
365
+ });
366
+ }
367
+
368
+ const patterns = getPatternsByCategory(category);
369
+
370
+ return formatResponse({
371
+ success: true,
372
+ category,
373
+ patterns: Object.entries(patterns).map(([key, p]) => ({
374
+ name: key,
375
+ situation: p.situation,
376
+ strategy: p.strategy,
377
+ yamlPattern: p.yamlPattern,
378
+ })),
379
+ message: "Apply these patterns for the specified interaction type.",
380
+ });
381
+ }
382
+
383
+ /**
384
+ * Get all YAML generation rules and best practices
385
+ */
386
+ export async function getGenerationRules() {
387
+ const rules = getYamlGenerationRules();
388
+ const hints = getInteractionPatternHints();
389
+ const bestPractices = getBestPractices();
390
+
391
+ return formatResponse({
392
+ success: true,
393
+ rules,
394
+ hints,
395
+ bestPractices,
396
+ message: "These rules are automatically applied during YAML generation. They are framework-agnostic and improve test reliability.",
397
+ });
398
+ }
399
+
400
+ /**
401
+ * Validate and analyze user prompt for YAML generation
402
+ * Uses Action-Element-Verification model for generic prompt analysis
403
+ *
404
+ * @param {string} userPrompt - The user's test description
405
+ * @param {object} options - Options: appId, forceGenerate
406
+ * @returns {object} Analysis result with YAML or clarification questions
407
+ */
408
+ export async function validateAndGenerate(userPrompt, options = {}) {
409
+ const { appId = null, forceGenerate = false } = options;
410
+
411
+ if (!userPrompt || typeof userPrompt !== 'string' || userPrompt.trim().length < 5) {
412
+ return formatResponse({
413
+ success: false,
414
+ error: "Prompt is too short or empty",
415
+ hint: "Provide a description of the test you want to create. Example: 'Test login with username abc and password 1234'",
416
+ });
417
+ }
418
+
419
+ // Step 1: Analyze the prompt
420
+ const analysis = analyzePrompt(userPrompt, { appId });
421
+
422
+ // Step 2: Check completeness level
423
+ const { assessment, extracted } = analysis;
424
+
425
+ // INSUFFICIENT: Cannot proceed without critical info
426
+ if (assessment.level === CompletenessLevel.INSUFFICIENT) {
427
+ const questions = generateClarificationQuestions(analysis);
428
+ const criticalQuestions = questions.filter(q => q.priority === 'critical');
429
+
430
+ return formatResponse({
431
+ success: false,
432
+ status: 'insufficient',
433
+ message: 'Cannot generate YAML: missing required information',
434
+ understood: {
435
+ appContext: extracted.appContext,
436
+ detectedActions: extracted.actions.map(a => a.type),
437
+ detectedElements: extracted.elements.map(e => e.type),
438
+ valuesFound: Object.keys(extracted.values),
439
+ },
440
+ requiredInfo: criticalQuestions.map(q => ({
441
+ field: q.field,
442
+ question: q.question,
443
+ })),
444
+ hint: 'Please provide at least the app ID (package name) to continue.',
445
+ });
446
+ }
447
+
448
+ // NEEDS_CLARIFICATION: Should ask, but can force generate
449
+ if (assessment.level === CompletenessLevel.NEEDS_CLARIFICATION && !forceGenerate) {
450
+ const questions = generateClarificationQuestions(analysis);
451
+
452
+ return formatResponse({
453
+ success: false,
454
+ status: 'needs_clarification',
455
+ message: 'Additional information recommended for reliable test generation',
456
+ completenessScore: assessment.score,
457
+ understood: {
458
+ appId: extracted.appContext.appId,
459
+ appName: extracted.appContext.appName,
460
+ detectedActions: extracted.actions.map(a => ({ type: a.type, context: a.context })),
461
+ detectedElements: extracted.elements.map(e => ({ type: e.type, label: e.label })),
462
+ valuesFound: extracted.values,
463
+ verifications: extracted.verifications.map(v => ({ type: v.type, target: v.target })),
464
+ },
465
+ questions: questions.map(q => ({
466
+ priority: q.priority,
467
+ field: q.field,
468
+ question: q.question,
469
+ required: q.required,
470
+ default: q.default,
471
+ })),
472
+ canForceGenerate: true,
473
+ hint: 'Answer the questions for better accuracy, or use forceGenerate: true to proceed with assumptions.',
474
+ });
475
+ }
476
+
477
+ // GENERATABLE: Can generate with assumptions (if forceGenerate is true)
478
+ // or ask for clarification
479
+ if (assessment.level === CompletenessLevel.GENERATABLE && !forceGenerate) {
480
+ const questions = generateClarificationQuestions(analysis);
481
+ const recommendedQuestions = questions.filter(q => q.priority === 'recommended');
482
+
483
+ return formatResponse({
484
+ success: false,
485
+ status: 'can_improve',
486
+ message: 'Prompt understood, but additional details would improve test reliability',
487
+ completenessScore: assessment.score,
488
+ understood: {
489
+ appId: extracted.appContext.appId,
490
+ detectedActions: extracted.actions.map(a => ({ type: a.type, context: a.context })),
491
+ detectedElements: extracted.elements.map(e => ({ type: e.type, label: e.label })),
492
+ valuesFound: extracted.values,
493
+ },
494
+ recommendations: recommendedQuestions.map(q => ({
495
+ field: q.field,
496
+ question: q.question,
497
+ default: q.default,
498
+ })),
499
+ assumptionsIfForced: assessment.assumptions,
500
+ canForceGenerate: true,
501
+ hint: 'Provide additional details or use forceGenerate: true to generate with assumptions.',
502
+ });
503
+ }
504
+
505
+ // COMPLETE or forceGenerate: Generate YAML
506
+ const result = generateYamlFromAnalysis(analysis, {
507
+ forceGenerate,
508
+ includeComments: true,
509
+ appId: appId || extracted.appContext.appId,
510
+ });
511
+
512
+ if (!result.success) {
513
+ return formatResponse({
514
+ success: false,
515
+ error: result.error,
516
+ missing: result.missing,
517
+ });
518
+ }
519
+
520
+ // Generate warnings if force generated
521
+ const warnings = forceGenerate && assessment.level !== CompletenessLevel.COMPLETE
522
+ ? generateWarnings(analysis)
523
+ : [];
524
+
525
+ return formatResponse({
526
+ success: true,
527
+ status: result.forceGenerated ? 'generated_with_warnings' : 'generated',
528
+ message: result.forceGenerated
529
+ ? 'YAML generated with assumptions. Review and update placeholders before running.'
530
+ : 'YAML generated successfully.',
531
+ yaml: result.yaml,
532
+ completenessScore: result.completenessScore,
533
+ forceGenerated: result.forceGenerated || false,
534
+ warnings: [
535
+ ...result.warnings,
536
+ ...warnings.map(w => w.message),
537
+ ],
538
+ assumptions: result.assumptions,
539
+ analysis: {
540
+ detectedActions: extracted.actions.length,
541
+ detectedElements: extracted.elements.length,
542
+ valuesFound: Object.keys(extracted.values).length,
543
+ verifications: extracted.verifications.length,
544
+ },
545
+ });
546
+ }
547
+
548
+ /**
549
+ * Analyze prompt without generating YAML
550
+ * Useful for understanding what the system detected
551
+ */
552
+ export async function analyzeTestPrompt(userPrompt, appId = null) {
553
+ if (!userPrompt || typeof userPrompt !== 'string') {
554
+ return formatResponse({
555
+ success: false,
556
+ error: "Invalid prompt",
557
+ });
558
+ }
559
+
560
+ const analysis = analyzePrompt(userPrompt, { appId });
561
+ const { extracted, assessment } = analysis;
562
+ const questions = generateClarificationQuestions(analysis);
563
+
564
+ return formatResponse({
565
+ success: true,
566
+ analysis: {
567
+ appContext: extracted.appContext,
568
+ actions: extracted.actions.map(a => ({
569
+ type: a.type,
570
+ keyword: a.keyword,
571
+ context: a.context,
572
+ })),
573
+ elements: extracted.elements.map(e => ({
574
+ type: e.type,
575
+ label: e.label,
576
+ keyword: e.keyword,
577
+ })),
578
+ values: extracted.values,
579
+ verifications: extracted.verifications.map(v => ({
580
+ type: v.type,
581
+ target: v.target,
582
+ })),
583
+ sequence: extracted.sequence,
584
+ },
585
+ assessment: {
586
+ level: assessment.level,
587
+ score: assessment.score,
588
+ issues: assessment.issues,
589
+ missing: assessment.missing,
590
+ assumptions: assessment.assumptions,
591
+ canForceGenerate: assessment.canForceGenerate,
592
+ },
593
+ clarificationQuestions: questions.map(q => ({
594
+ priority: q.priority,
595
+ field: q.field,
596
+ question: q.question,
597
+ required: q.required,
598
+ })),
599
+ });
600
+ }
601
+
302
602
  export default {
303
603
  registerAppElements,
304
604
  registerAppScreen,
@@ -313,5 +613,12 @@ export default {
313
613
  validateYamlBeforeRun,
314
614
  getTestPattern,
315
615
  getScreenAnalysis,
616
+ // Known Issues Tools
617
+ getKnownIssues,
618
+ getPlatformRules,
619
+ getGenerationRules,
620
+ // Prompt Analysis & Generation
621
+ validateAndGenerate,
622
+ analyzeTestPrompt,
316
623
  };
317
624