mcp-maestro-mobile-ai 1.4.0 → 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.
@@ -442,6 +442,9 @@ const validPatternNames = [
442
442
  "list",
443
443
  "settings",
444
444
  "logout",
445
+ "dropdown",
446
+ "flutter_login_with_dropdown",
447
+ "flutter_multi_dropdown",
445
448
  ];
446
449
 
447
450
  /**
@@ -460,6 +463,156 @@ export const getTestPatternSchema = z.object({
460
463
  */
461
464
  export const getScreenAnalysisHelpSchema = z.object({});
462
465
 
466
+ /**
467
+ * Valid pattern categories
468
+ */
469
+ const validPatternCategories = [
470
+ "keyboard_handling",
471
+ "element_interaction",
472
+ "timing_synchronization",
473
+ "input_handling",
474
+ "dropdown_picker",
475
+ "navigation",
476
+ "scroll_visibility",
477
+ "fallback_strategies",
478
+ ];
479
+
480
+ /**
481
+ * Schema for get_interaction_patterns tool
482
+ */
483
+ export const getKnownIssuesSchema = z.object({
484
+ category: z.enum(validPatternCategories).optional().describe("Filter patterns by category"),
485
+ });
486
+
487
+ /**
488
+ * Schema for get_patterns_by_category tool
489
+ */
490
+ export const getPlatformRulesSchema = z.object({
491
+ category: z.enum(validPatternCategories, {
492
+ errorMap: () => ({
493
+ message: `Category must be one of: ${validPatternCategories.join(", ")}`,
494
+ }),
495
+ }),
496
+ });
497
+
498
+ /**
499
+ * Schema for get_generation_rules tool (no params)
500
+ */
501
+ export const getGenerationRulesSchema = z.object({});
502
+
503
+ // ============================================
504
+ // PROMPT ANALYSIS & VALIDATION SCHEMAS
505
+ // ============================================
506
+
507
+ /**
508
+ * Schema for validate_prompt tool
509
+ * Analyzes user prompt using Action-Element-Verification model
510
+ */
511
+ export const validatePromptSchema = z.object({
512
+ userPrompt: z
513
+ .string()
514
+ .min(5, "Prompt too short - provide a test description")
515
+ .max(5000, "Prompt too long"),
516
+ appId: z
517
+ .string()
518
+ .max(150, "App ID too long")
519
+ .regex(appIdPattern, "Invalid app ID format")
520
+ .optional()
521
+ .describe("Optional app package ID. If not in prompt, must be provided here."),
522
+ forceGenerate: z
523
+ .boolean()
524
+ .optional()
525
+ .default(false)
526
+ .describe("If true, generate YAML with assumptions even if information is incomplete."),
527
+ });
528
+
529
+ /**
530
+ * Schema for analyze_prompt tool
531
+ * Analyzes prompt without generating YAML
532
+ */
533
+ export const analyzePromptSchema = z.object({
534
+ userPrompt: z
535
+ .string()
536
+ .min(5, "Prompt too short")
537
+ .max(5000, "Prompt too long"),
538
+ appId: z
539
+ .string()
540
+ .max(150)
541
+ .regex(appIdPattern, "Invalid app ID format")
542
+ .optional(),
543
+ });
544
+
545
+ // ============================================
546
+ // YAML CACHE SCHEMAS
547
+ // ============================================
548
+
549
+ /**
550
+ * Schema for save_to_cache tool
551
+ */
552
+ export const saveToCacheSchema = z.object({
553
+ prompt: z
554
+ .string()
555
+ .min(5, "Prompt too short")
556
+ .max(5000, "Prompt too long")
557
+ .describe("The original test prompt/description"),
558
+ yaml: yamlContentSchema.describe("The YAML content to cache"),
559
+ testName: testNameSchema.describe("Name of the test"),
560
+ appId: optionalAppIdSchema,
561
+ });
562
+
563
+ /**
564
+ * Schema for lookup_cache tool
565
+ */
566
+ export const lookupCacheSchema = z.object({
567
+ prompt: z
568
+ .string()
569
+ .min(5, "Prompt too short")
570
+ .max(5000, "Prompt too long")
571
+ .describe("The test prompt to look up in cache"),
572
+ appId: optionalAppIdSchema,
573
+ });
574
+
575
+ /**
576
+ * Schema for delete_from_cache tool
577
+ */
578
+ export const deleteFromCacheSchema = z.object({
579
+ hash: z
580
+ .string()
581
+ .min(8, "Hash too short")
582
+ .max(64, "Hash too long")
583
+ .describe("The cache hash to delete (from list_cache output)"),
584
+ });
585
+
586
+ /**
587
+ * Schema for list_cache tool (no params)
588
+ */
589
+ export const listCacheSchema = z.object({});
590
+
591
+ /**
592
+ * Schema for clear_cache tool (no params)
593
+ */
594
+ export const clearCacheSchema = z.object({});
595
+
596
+ /**
597
+ * Schema for get_cache_stats tool (no params)
598
+ */
599
+ export const getCacheStatsSchema = z.object({});
600
+
601
+ /**
602
+ * Schema for run_test_with_cache tool
603
+ */
604
+ export const runTestWithCacheSchema = z.object({
605
+ prompt: z
606
+ .string()
607
+ .min(5)
608
+ .max(5000)
609
+ .describe("The original test prompt - used for cache lookup"),
610
+ yaml: yamlContentSchema.describe("The YAML to use if not cached"),
611
+ name: testNameSchema,
612
+ appId: optionalAppIdSchema,
613
+ retries: retriesSchema,
614
+ });
615
+
463
616
  // ============================================
464
617
  // SCHEMA REGISTRY
465
618
  // ============================================
@@ -513,6 +666,24 @@ export const toolSchemas = {
513
666
  validate_yaml_structure: validateYamlStructureSchema,
514
667
  get_test_pattern: getTestPatternSchema,
515
668
  get_screen_analysis_help: getScreenAnalysisHelpSchema,
669
+
670
+ // Known Issues & Platform Rules
671
+ get_known_issues: getKnownIssuesSchema,
672
+ get_platform_rules: getPlatformRulesSchema,
673
+ get_generation_rules: getGenerationRulesSchema,
674
+
675
+ // Prompt Analysis & Validation
676
+ validate_prompt: validatePromptSchema,
677
+ analyze_prompt: analyzePromptSchema,
678
+
679
+ // YAML Cache
680
+ save_to_cache: saveToCacheSchema,
681
+ lookup_cache: lookupCacheSchema,
682
+ delete_from_cache: deleteFromCacheSchema,
683
+ list_cache: listCacheSchema,
684
+ clear_cache: clearCacheSchema,
685
+ get_cache_stats: getCacheStatsSchema,
686
+ run_test_with_cache: runTestWithCacheSchema,
516
687
  };
517
688
 
518
689
  // ============================================
@@ -633,4 +804,17 @@ export default {
633
804
  validateYamlStructureSchema,
634
805
  getTestPatternSchema,
635
806
  getScreenAnalysisHelpSchema,
807
+
808
+ // Prompt Analysis & Validation
809
+ validatePromptSchema,
810
+ analyzePromptSchema,
811
+
812
+ // YAML Cache
813
+ saveToCacheSchema,
814
+ lookupCacheSchema,
815
+ deleteFromCacheSchema,
816
+ listCacheSchema,
817
+ clearCacheSchema,
818
+ getCacheStatsSchema,
819
+ runTestWithCacheSchema,
636
820
  };
@@ -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