Skip to main content
Glama

run_maestro_flow

Execute Maestro end-to-end test flows for Android and iOS apps, returning structured results with failure bundles containing screenshots and logs for debugging.

Instructions

Run a Maestro E2E test flow. Returns structured results with step-by-step status. On failure, generates a failure bundle with screenshot and logs for debugging.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
flowPathYesPath to the Maestro flow YAML file
platformYesTarget platform
deviceIdNoDevice ID or name (optional, uses first available)
appIdNoApp package (Android) or bundle ID (iOS)
timeoutMsNoTimeout in milliseconds (default: 300000)
generateFailureBundleNoGenerate failure bundle with screenshot and logs on failure (default: true)
envNoEnvironment variables for the flow

Implementation Reference

  • The primary handler function for the 'run_maestro_flow' MCP tool. Validates inputs, resolves the target device, checks Maestro CLI availability, executes the flow using runMaestroFlow helper, optionally generates a failure bundle on error, and returns a structured result including summary for AI analysis.
    export async function runMaestroFlowTool(args: RunMaestroFlowArgs): Promise<RunMaestroFlowResult> { const { flowPath, platform, deviceId, appId, timeoutMs = 300000, generateFailureBundle: shouldGenerateBundle = true, env = {}, } = args; // Validate platform if (!isPlatform(platform)) { throw Errors.invalidArguments(`Invalid platform: ${platform}. Must be 'android' or 'ios'`); } // Check if Maestro is available const maestroAvailable = await isMaestroAvailable(); if (!maestroAvailable) { throw Errors.invalidArguments( 'Maestro CLI not found. Install from https://maestro.mobile.dev/' ); } // Get target device const resolvedDeviceId = await resolveDevice(platform as Platform, deviceId); if (!resolvedDeviceId) { throw Errors.invalidArguments(`No ${platform} device found`); } // Run the flow const maestroOptions: MaestroRunOptions = { flowPath, platform: platform as Platform, deviceId: resolvedDeviceId, appId, timeoutMs, env, }; const flowResult = await runMaestroFlow(maestroOptions); // Build result const result: RunMaestroFlowResult = { flowResult, summary: createFlowSummary(flowResult), }; // Generate failure bundle if flow failed if (!flowResult.success && shouldGenerateBundle) { try { const bundle = await generateFailureBundle({ flowResult, platform: platform as Platform, deviceId: resolvedDeviceId, appIdentifier: appId, includeScreenshot: true, includeLogs: platform === 'android', }); result.failureBundle = getFailureBundleSummary(bundle); } catch (error) { console.error('[run_maestro_flow] Failed to generate failure bundle:', error); } } return result; }
  • TypeScript interfaces defining the input arguments (RunMaestroFlowArgs) and output structure (RunMaestroFlowResult) for the run_maestro_flow tool.
    export interface RunMaestroFlowArgs { /** Path to the Maestro flow YAML file */ flowPath: string; /** Target platform */ platform: string; /** Device ID (optional, uses first available) */ deviceId?: string; /** App package/bundle ID */ appId?: string; /** Timeout in milliseconds */ timeoutMs?: number; /** Generate failure bundle on failure */ generateFailureBundle?: boolean; /** Environment variables for the flow */ env?: Record<string, string>; } /** * Result structure for run_maestro_flow */ export interface RunMaestroFlowResult { /** Flow execution result */ flowResult: FlowResult; /** Failure bundle if test failed and generateFailureBundle is true */ failureBundle?: ReturnType<typeof getFailureBundleSummary>; /** Summary for AI consumption */ summary: string; }
  • Tool registration function that adds 'run_maestro_flow' to the MCP tool registry, providing description, JSON schema for inputs (via createInputSchema), and binding the handler function.
    export function registerRunMaestroFlowTool(): void { getToolRegistry().register( 'run_maestro_flow', { description: 'Run a Maestro E2E test flow. Returns structured results with step-by-step status. On failure, generates a failure bundle with screenshot and logs for debugging.', inputSchema: createInputSchema( { flowPath: { type: 'string', description: 'Path to the Maestro flow YAML file', }, platform: { type: 'string', enum: ['android', 'ios'], description: 'Target platform', }, deviceId: { type: 'string', description: 'Device ID or name (optional, uses first available)', }, appId: { type: 'string', description: 'App package (Android) or bundle ID (iOS)', }, timeoutMs: { type: 'number', description: 'Timeout in milliseconds (default: 300000)', }, generateFailureBundle: { type: 'boolean', description: 'Generate failure bundle with screenshot and logs on failure (default: true)', }, env: { type: 'object', description: 'Environment variables for the flow', additionalProperties: { type: 'string' }, }, }, ['flowPath', 'platform'] ), }, (args) => runMaestroFlowTool(args as unknown as RunMaestroFlowArgs) ); }
  • Key helper function called by the handler to execute the actual Maestro CLI 'maestro test' command. Constructs command line arguments (device, platform, env vars, flow path), runs via executeShell, parses raw output to extract per-step results (passed/failed commands), computes timings, and structures as FlowResult.
    export async function runMaestroFlow(options: MaestroRunOptions): Promise<FlowResult> { const { flowPath, platform, deviceId, appId, timeoutMs = 300000, // 5 minutes format, outputPath, env = {}, } = options; // Validate flow file exists if (!existsSync(flowPath)) { return { flowName: basename(flowPath), flowPath, success: false, totalSteps: 0, passedSteps: 0, failedAtStep: -1, durationMs: 0, steps: [], error: `Flow file not found: ${flowPath}`, }; } const startTime = Date.now(); const flowName = basename(flowPath); // Build Maestro command - global options must come BEFORE 'test' subcommand const args: string[] = []; // Add global device selection (must come before 'test') if (deviceId) { args.push('--device', deviceId); } // Add global platform option (must come before 'test') args.push('--platform', platform); // Now add the test subcommand and flow path args.push('test'); // Add app ID as environment variable via -e flag (required for Maestro) if (appId) { args.push('-e', `APP_ID=${appId}`); } // Add custom env vars for (const [key, value] of Object.entries(env)) { args.push('-e', `${key}=${value}`); } // Add format and output (Maestro supports: JUNIT, HTML, NOOP) if (format) { args.push('--format', format.toUpperCase()); } if (outputPath) { args.push('--output', outputPath); } // Add flow path last args.push(flowPath); // Build environment const envVars: Record<string, string> = { ...process.env as Record<string, string>, }; // Run Maestro const result = await executeShell('maestro', args, { timeoutMs, silent: false, env: envVars, }); const durationMs = Date.now() - startTime; // Build FlowResult by parsing steps from raw output const steps: FlowStep[] = []; let failedAtStep = -1; let passedSteps = 0; // Parse steps from stdout const parsedSteps = parseStepsFromOutput(result.stdout); for (let i = 0; i < parsedSteps.length; i++) { const step = parsedSteps[i]; if (step.status === 'passed') passedSteps++; if (step.status === 'failed' && failedAtStep === -1) failedAtStep = i; steps.push(step); } // Success is determined primarily by exit code // - Exit code 0 means Maestro completed successfully // - Step parsing is for detailed reporting only (Maestro output format may vary) const success = result.exitCode === 0 && failedAtStep === -1; return { flowName, flowPath, success, totalSteps: steps.length, passedSteps, failedAtStep, durationMs, steps, error: !success ? (result.stderr || result.stdout || 'Flow execution failed') : undefined, }; }
  • Supporting helper invoked on test failure to generate a 'failure bundle' containing screenshot, filtered logs, failure analysis suggestions, used for debugging E2E flows.
    export async function generateFailureBundle( options: FailureBundleOptions ): Promise<FailureBundle> { const { flowResult, platform, deviceId, appIdentifier, includeScreenshot = true, includeLogs = true, logWindowMs = 10000, includeUiHierarchy = false, } = options; const bundle: FailureBundle = { id: generateBundleId(), timestamp: Date.now(), platform, deviceId, flowResult, logs: [], suggestions: [], }; // Add app identifier if (appIdentifier) { bundle.appIdentifier = appIdentifier; } // Capture screenshot at failure point if (includeScreenshot && !flowResult.success) { try { bundle.failureScreenshot = await captureFailureScreenshot(platform, deviceId); } catch (error) { console.error('[failure-bundle] Failed to capture screenshot:', error); } } // Capture logs around failure time if (includeLogs && platform === 'android') { try { const failureTime = bundle.timestamp; const logs = await captureRecentLogs(deviceId, appIdentifier, logWindowMs); bundle.logs = filterLogsAroundFailure(logs, failureTime, logWindowMs); } catch (error) { console.error('[failure-bundle] Failed to capture logs:', error); } } // Capture UI hierarchy (if requested) if (includeUiHierarchy) { // UI hierarchy capture would be done here // Currently skipped as it requires additional implementation } // Analyze failure and generate suggestions bundle.suggestions = analyzeFailure(bundle); return bundle; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/abd3lraouf/specter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server