@deriv-com/fe-mcp-servers 0.0.11 → 0.0.12

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.
@@ -147,6 +147,37 @@ Returns common Maestro patterns with "when to use" guidance.
147
147
  - Pattern YAML code
148
148
  - Usage guidance for each pattern
149
149
 
150
+ ### `maestro_ensure_installed` Tool
151
+
152
+ ⚠️ **PREREQUISITE**: Call this FIRST before using `maestro_write_test` or `maestro_run_all_tests`!
153
+
154
+ Checks if Maestro CLI is installed and automatically installs it if missing.
155
+
156
+ **When to Call:**
157
+
158
+ - ALWAYS before `maestro_write_test` (when execute=true)
159
+ - ALWAYS before `maestro_run_all_tests`
160
+ - When setting up a new development environment
161
+
162
+ **Parameters:**
163
+
164
+ - `autoInstall` (boolean, optional): Whether to automatically install if not found (default: true)
165
+ - `forceReinstall` (boolean, optional): Whether to force reinstall even if already installed (default: false)
166
+
167
+ **Returns:**
168
+
169
+ - `installed`: boolean - Whether Maestro is now installed
170
+ - `version`: string - Installed version (if available)
171
+ - `wasInstalled`: boolean - Whether this call performed the installation
172
+ - `message`: string - Status message
173
+ - `details`: object - Additional info (path, install method)
174
+
175
+ **Installation Method:**
176
+
177
+ ```bash
178
+ curl -fsSL "https://get.maestro.mobile.dev" | bash
179
+ ```
180
+
150
181
  ### `maestro_analyze_changes` Tool
151
182
 
152
183
  Automatically analyzes actual git changes in a repository and provides test recommendations.
@@ -164,6 +195,122 @@ Automatically analyzes actual git changes in a repository and provides test reco
164
195
  - Interactive elements detected (onClick, Button, Form, etc.)
165
196
  - Suggested flow type and whether to create tests
166
197
 
198
+ ---
199
+
200
+ ## 🚀 Test Execution Tools
201
+
202
+ ### `maestro_write_test` Tool
203
+
204
+ Writes a Maestro test YAML file to disk.
205
+
206
+ **Parameters:**
207
+
208
+ - `yaml` (string, required): The YAML content to write
209
+ - `fileName` (string, required): Name of the file (e.g., "login_test.yaml")
210
+ - `directory` (string, optional): Target directory (default: "maestro")
211
+ - `basePath` (string, optional): Project base path (default: current directory)
212
+
213
+ **Returns:**
214
+
215
+ - `success`: boolean
216
+ - `filePath`: Path where file was written
217
+ - `message`: Status message
218
+
219
+ ### `maestro_execute_test` Tool
220
+
221
+ Executes a single Maestro test file using the Maestro CLI.
222
+
223
+ **Prerequisites:**
224
+
225
+ - Maestro CLI must be installed (`curl -fsSL https://get.maestro.dev | bash`)
226
+ - A device/simulator must be running
227
+
228
+ **Parameters:**
229
+
230
+ - `flowFile` (string, required): Path to the .yaml flow file
231
+ - `deviceId` (string, optional): Target device ID
232
+ - `env` (object, optional): Environment variables to pass
233
+ - `timeout` (number, optional): Execution timeout in ms (default: 120000)
234
+
235
+ **Returns:**
236
+
237
+ - `success`: boolean
238
+ - `output`: Test output/logs
239
+ - `exitCode`: Process exit code
240
+ - `duration`: Execution time in ms
241
+
242
+ ### `maestro_execute_tests_sequential` Tool
243
+
244
+ Executes multiple Maestro test files sequentially with aggregated results.
245
+
246
+ **Parameters:**
247
+
248
+ - `flowFiles` (array, required): Array of flow file paths to execute
249
+ - `deviceId` (string, optional): Target device ID
250
+ - `env` (object, optional): Environment variables to pass
251
+ - `stopOnFailure` (boolean, optional): Stop on first failure (default: false)
252
+
253
+ **Returns:**
254
+
255
+ - `success`: boolean (true if all tests passed)
256
+ - `results`: Array of individual test results
257
+ - `summary`: Aggregated statistics (passed, failed, skipped, duration)
258
+
259
+ ### `maestro_generate_and_run` Tool
260
+
261
+ All-in-one workflow: Generate, write, and execute a Maestro test.
262
+
263
+ **Parameters:**
264
+
265
+ - `feature` (string, required): Feature being tested
266
+ - `action` (string, required): Action being tested
267
+ - `flowType` (string, required): Flow type (auth, form, sidebar, modal, navigation, extended)
268
+ - `basePath` (string, optional): Project base path
269
+ - `deviceId` (string, optional): Target device ID
270
+ - `env` (object, optional): Environment variables
271
+ - `execute` (boolean, optional): Execute after writing (default: true)
272
+ - `changedElements` (array, optional): List of changed UI elements
273
+ - `existingTests` (array, optional): List of existing related test files
274
+
275
+ **Returns:**
276
+
277
+ - `generation`: Generated template and guidelines
278
+ - `write`: File write result
279
+ - `execution`: Test execution results (if execute=true)
280
+ - `summary`: Human-readable summary
281
+
282
+ ### `maestro_discover_tests` Tool
283
+
284
+ Discovers all Maestro test files in a directory.
285
+
286
+ **Parameters:**
287
+
288
+ - `directory` (string, optional): Directory to search (default: "maestro")
289
+ - `basePath` (string, optional): Project base path
290
+
291
+ **Returns:**
292
+
293
+ - `files`: Array of file paths
294
+ - `count`: Number of files found
295
+
296
+ ### `maestro_run_all_tests` Tool
297
+
298
+ Runs all Maestro tests in a directory sequentially.
299
+
300
+ **Parameters:**
301
+
302
+ - `directory` (string, optional): Directory containing tests (default: "maestro")
303
+ - `basePath` (string, optional): Project base path
304
+ - `deviceId` (string, optional): Target device ID
305
+ - `env` (object, optional): Environment variables
306
+ - `stopOnFailure` (boolean, optional): Stop on first failure (default: false)
307
+
308
+ **Returns:**
309
+
310
+ - `discovery`: List of discovered test files
311
+ - `execution`: Results with pass/fail status for each test
312
+ - `summary`: Aggregated statistics
313
+
167
314
  ## 🎯 Usage Examples
168
315
 
169
316
  Once configured in your MCP client, you can use the tools in your conversations:
@@ -237,6 +384,58 @@ env:
237
384
  3. **Quick Reference**: `maestro_cheat_sheet()`
238
385
  4. **Pattern Lookup**: `maestro_pattern(patternType: "login")`
239
386
  5. **UI Discovery**: `maestro_ui_inspection()`
387
+ 6. **Pre-check Installation**: `maestro_ensure_installed()` - Verifies Maestro CLI is ready
388
+
389
+ ### Test Execution Use Cases
390
+
391
+ 6. **Generate & Run Test**:
392
+
393
+ ```
394
+ maestro_generate_and_run(
395
+ feature: "Login",
396
+ action: "OAuth flow",
397
+ flowType: "auth",
398
+ basePath: "/path/to/project"
399
+ )
400
+ ```
401
+
402
+ 7. **Run All Tests in Directory**:
403
+
404
+ ```
405
+ maestro_run_all_tests(
406
+ directory: "maestro",
407
+ basePath: "/path/to/project",
408
+ stopOnFailure: false
409
+ )
410
+ ```
411
+
412
+ 8. **Execute Specific Tests Sequentially**:
413
+
414
+ ```
415
+ maestro_execute_tests_sequential(
416
+ flowFiles: ["maestro/login.yaml", "maestro/checkout.yaml"],
417
+ env: { "BASE_URL": "https://staging.example.com" }
418
+ )
419
+ ```
420
+
421
+ 9. **Write Test File Only (No Execution)**:
422
+
423
+ ```
424
+ maestro_write_test(
425
+ yaml: "<generated yaml content>",
426
+ fileName: "user_settings_reset.yaml",
427
+ basePath: "/path/to/project"
428
+ )
429
+ ```
430
+
431
+ 10. **Discover Available Tests**:
432
+
433
+ ```
434
+ maestro_discover_tests(
435
+ directory: "maestro",
436
+ basePath: "/path/to/project"
437
+ )
438
+ ```
240
439
 
241
440
  ## Flow Types
242
441
 
@@ -259,6 +458,73 @@ env:
259
458
  | `lastCommit` | Most recent commit | `git diff HEAD~1` |
260
459
  | `branch` | Compare to base branch | `git diff {baseBranch}...HEAD` |
261
460
 
461
+ ## 🔄 Complete Workflow Example
462
+
463
+ ### ⚠️ IMPORTANT: Always Check Installation First!
464
+
465
+ When a user asks to "generate and run test cases" or use Maestro, follow this order:
466
+
467
+ ```bash
468
+ # Step 0: ALWAYS check Maestro installation first!
469
+ maestro_ensure_installed()
470
+
471
+ # If not installed, the tool will automatically install it via:
472
+ # curl -fsSL "https://get.maestro.mobile.dev" | bash
473
+ ```
474
+
475
+ ### End-to-End: Analyze Changes → Generate → Execute
476
+
477
+ ```bash
478
+ # Step 0: Ensure Maestro is installed (REQUIRED)
479
+ maestro_ensure_installed()
480
+
481
+ # Step 1: Analyze git changes to see what needs testing
482
+ maestro_analyze_changes(repoPath: "/path/to/project", changeType: "staged")
483
+
484
+ # Output shows: Settings.tsx changed with onClick handler
485
+ # Suggested flow type: form
486
+
487
+ # Step 2: Generate and run the test in one call
488
+ maestro_generate_and_run(
489
+ feature: "User Settings",
490
+ action: "Reset defaults",
491
+ flowType: "form",
492
+ basePath: "/path/to/project",
493
+ env: { "BASE_URL": "https://localhost:8443" }
494
+ )
495
+
496
+ # Output:
497
+ # - File written to: /path/to/project/maestro/user_settings_reset_defaults.yaml
498
+ # - Test executed: ✅ Passed (duration: 12.5s)
499
+ ```
500
+
501
+ ### Sequential Test Suite Execution
502
+
503
+ ```bash
504
+ # Step 0: Ensure Maestro is installed (REQUIRED)
505
+ maestro_ensure_installed()
506
+
507
+ # Discover all tests
508
+ maestro_discover_tests(basePath: "/path/to/project")
509
+ # Output: Found 5 test files
510
+
511
+ # Run all tests with stop-on-failure
512
+ maestro_run_all_tests(
513
+ basePath: "/path/to/project",
514
+ stopOnFailure: true,
515
+ env: { "BASE_URL": "https://staging.example.com" }
516
+ )
517
+
518
+ # Output:
519
+ # ✅ login_basic.yaml (8.2s)
520
+ # ✅ user_settings_open.yaml (5.1s)
521
+ # ❌ checkout_submit.yaml (12.3s) - FAILED
522
+ # ⏭️ payment_confirm.yaml - SKIPPED
523
+ # ⏭️ logout.yaml - SKIPPED
524
+ #
525
+ # Summary: 2 passed, 1 failed, 2 skipped
526
+ ```
527
+
262
528
  ## 📊 Examples
263
529
 
264
530
  ### Auth Flow Template
@@ -280,6 +546,9 @@ env:
280
546
  - tapOn:
281
547
  text: "Next"
282
548
  repeat: 3
549
+ - tapOn:
550
+ text: "Got it"
551
+ repeat: 3
283
552
  - tapOn:
284
553
  text: "Log in"
285
554
  - inputText: ${USER_EMAIL}
@@ -18492,7 +18492,17 @@ var StdioServerTransport = class {
18492
18492
  };
18493
18493
 
18494
18494
  // maestro-ai/src/mcp.js
18495
- import { execSync } from "child_process";
18495
+ import { execSync, spawn } from "child_process";
18496
+ import {
18497
+ writeFileSync,
18498
+ existsSync,
18499
+ mkdirSync,
18500
+ readFileSync,
18501
+ openSync,
18502
+ closeSync,
18503
+ fsyncSync
18504
+ } from "fs";
18505
+ import { join, dirname } from "path";
18496
18506
  var FLOW_TYPES = {
18497
18507
  auth: {
18498
18508
  description: "Login, signup, or password reset flows",
@@ -18520,6 +18530,156 @@ var FLOW_TYPES = {
18520
18530
  requiresOnboarding: false
18521
18531
  }
18522
18532
  };
18533
+ function isMaestroInstalled() {
18534
+ try {
18535
+ execSync("which maestro", { encoding: "utf-8", stdio: "pipe" });
18536
+ return true;
18537
+ } catch {
18538
+ return false;
18539
+ }
18540
+ }
18541
+ function installMaestro() {
18542
+ const installCmd = 'curl -fsSL "https://get.maestro.mobile.dev" | bash';
18543
+ try {
18544
+ try {
18545
+ execSync(installCmd, {
18546
+ encoding: "utf-8",
18547
+ stdio: "pipe",
18548
+ shell: "/bin/bash",
18549
+ timeout: 12e4
18550
+ // 2 minute timeout for install
18551
+ });
18552
+ } catch (curlError) {
18553
+ const curlOutput = curlError.stderr || curlError.stdout || curlError.message;
18554
+ throw new Error(`Installation failed: ${curlOutput}`);
18555
+ }
18556
+ if (isMaestroInstalled()) {
18557
+ return {
18558
+ success: true,
18559
+ message: "\u2705 Maestro CLI installed successfully!",
18560
+ method: "curl"
18561
+ };
18562
+ }
18563
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
18564
+ const maestroBin = join(homeDir, ".maestro", "bin");
18565
+ process.env.PATH = `${maestroBin}:${process.env.PATH}`;
18566
+ try {
18567
+ execSync(`${join(maestroBin, "maestro")} --version`, {
18568
+ encoding: "utf-8",
18569
+ stdio: "pipe"
18570
+ });
18571
+ return {
18572
+ success: true,
18573
+ message: `\u2705 Maestro CLI installed successfully! Added ${maestroBin} to PATH.`,
18574
+ method: "curl"
18575
+ };
18576
+ } catch {
18577
+ return {
18578
+ success: false,
18579
+ message: `\u26A0\uFE0F Maestro installed but not in PATH. Add ${maestroBin} to your PATH and restart your terminal.`,
18580
+ method: "curl"
18581
+ };
18582
+ }
18583
+ } catch (error2) {
18584
+ return {
18585
+ success: false,
18586
+ message: `\u274C Failed to install Maestro: ${error2.message}
18587
+
18588
+ Manual install:
18589
+ ${installCmd}`,
18590
+ method: "curl"
18591
+ };
18592
+ }
18593
+ }
18594
+ function ensureMaestroAvailable() {
18595
+ if (isMaestroInstalled()) {
18596
+ return {
18597
+ available: true,
18598
+ message: "Maestro CLI is available",
18599
+ installed: false
18600
+ };
18601
+ }
18602
+ const installResult = installMaestro();
18603
+ return {
18604
+ available: installResult.success,
18605
+ message: installResult.message,
18606
+ installed: installResult.success
18607
+ };
18608
+ }
18609
+ function getMaestroVersion() {
18610
+ try {
18611
+ const version2 = execSync("maestro --version", {
18612
+ encoding: "utf-8",
18613
+ stdio: "pipe"
18614
+ }).trim();
18615
+ return version2;
18616
+ } catch {
18617
+ return null;
18618
+ }
18619
+ }
18620
+ function ensureMaestroInstalled(options = {}) {
18621
+ const { autoInstall = true, forceReinstall = false } = options;
18622
+ const result = {
18623
+ installed: false,
18624
+ version: null,
18625
+ wasInstalled: false,
18626
+ message: "",
18627
+ details: {
18628
+ installMethod: null,
18629
+ path: null
18630
+ }
18631
+ };
18632
+ const alreadyInstalled = isMaestroInstalled();
18633
+ const currentVersion = alreadyInstalled ? getMaestroVersion() : null;
18634
+ if (alreadyInstalled && !forceReinstall) {
18635
+ result.installed = true;
18636
+ result.version = currentVersion;
18637
+ result.wasInstalled = false;
18638
+ result.message = `\u2705 Maestro CLI is already installed (${currentVersion || "version unknown"})`;
18639
+ try {
18640
+ const path = execSync("which maestro", {
18641
+ encoding: "utf-8",
18642
+ stdio: "pipe"
18643
+ }).trim();
18644
+ result.details.path = path;
18645
+ } catch {
18646
+ }
18647
+ return result;
18648
+ }
18649
+ if (!autoInstall) {
18650
+ result.installed = false;
18651
+ result.version = null;
18652
+ result.wasInstalled = false;
18653
+ result.message = `\u274C Maestro CLI is not installed. Set autoInstall: true to install automatically.`;
18654
+ result.details.installCommand = 'curl -fsSL "https://get.maestro.mobile.dev" | bash';
18655
+ return result;
18656
+ }
18657
+ const installResult = installMaestro();
18658
+ if (installResult.success) {
18659
+ const newVersion = getMaestroVersion();
18660
+ result.installed = true;
18661
+ result.version = newVersion;
18662
+ result.wasInstalled = true;
18663
+ result.message = installResult.message;
18664
+ result.details.installMethod = installResult.method;
18665
+ try {
18666
+ const path = execSync("which maestro", {
18667
+ encoding: "utf-8",
18668
+ stdio: "pipe"
18669
+ }).trim();
18670
+ result.details.path = path;
18671
+ } catch {
18672
+ }
18673
+ } else {
18674
+ result.installed = false;
18675
+ result.version = null;
18676
+ result.wasInstalled = false;
18677
+ result.message = installResult.message;
18678
+ result.details.installMethod = installResult.method;
18679
+ result.details.installCommand = 'curl -fsSL "https://get.maestro.mobile.dev" | bash';
18680
+ }
18681
+ return result;
18682
+ }
18523
18683
  function getMaestroCheatSheet() {
18524
18684
  const commands = [
18525
18685
  {
@@ -19289,6 +19449,305 @@ ${analysis.shouldCreateTest ? `1. Review the changed UI files above
19289
19449
  }
19290
19450
  };
19291
19451
  }
19452
+ function writeTestFile(options = {}) {
19453
+ const {
19454
+ yaml,
19455
+ fileName,
19456
+ directory = "maestro",
19457
+ basePath = process.cwd(),
19458
+ execute = true,
19459
+ deviceId = null,
19460
+ env = {},
19461
+ feature = "",
19462
+ action = "",
19463
+ flowType = null,
19464
+ changedElements = [],
19465
+ existingTests = []
19466
+ } = options;
19467
+ const generation = generateMaestroTest({
19468
+ feature,
19469
+ action,
19470
+ flowType,
19471
+ changedElements,
19472
+ existingTests
19473
+ });
19474
+ if (!yaml || !fileName) {
19475
+ return {
19476
+ success: false,
19477
+ filePath: null,
19478
+ message: "\u274C Missing required parameters: yaml and fileName are required"
19479
+ };
19480
+ }
19481
+ const normalizedFileName = fileName.endsWith(".yaml") ? fileName : `${fileName}.yaml`;
19482
+ const targetDir = join(basePath, directory);
19483
+ const filePath = join(targetDir, normalizedFileName);
19484
+ try {
19485
+ if (!existsSync(targetDir)) {
19486
+ mkdirSync(targetDir, { recursive: true });
19487
+ }
19488
+ writeFileSync(filePath, yaml, "utf-8");
19489
+ const fd = openSync(filePath, "r");
19490
+ fsyncSync(fd);
19491
+ closeSync(fd);
19492
+ const result = {
19493
+ success: true,
19494
+ filePath,
19495
+ message: `\u2705 Test file written successfully to: ${filePath}`,
19496
+ generation
19497
+ // Include test generation guidelines and instructions
19498
+ };
19499
+ if (execute) {
19500
+ const sleep = (ms) => {
19501
+ const end = Date.now() + ms;
19502
+ while (Date.now() < end) {
19503
+ }
19504
+ };
19505
+ sleep(500);
19506
+ const execution = executeTest({
19507
+ flowFile: filePath,
19508
+ deviceId,
19509
+ env
19510
+ });
19511
+ result.execution = execution;
19512
+ result.message += execution.success ? "\n\u2705 Test executed successfully!" : "\n\u274C Test execution failed.";
19513
+ }
19514
+ return result;
19515
+ } catch (error2) {
19516
+ return {
19517
+ success: false,
19518
+ filePath: null,
19519
+ message: `\u274C Failed to write test file: ${error2.message}`
19520
+ };
19521
+ }
19522
+ }
19523
+ function executeTest(options = {}) {
19524
+ const { flowFile, deviceId = null, env = {}, timeout = 12e4 } = options;
19525
+ if (!flowFile) {
19526
+ return {
19527
+ success: false,
19528
+ output: "\u274C Missing required parameter: flowFile",
19529
+ exitCode: 1,
19530
+ duration: 0
19531
+ };
19532
+ }
19533
+ const maestroCheck = ensureMaestroAvailable();
19534
+ if (!maestroCheck.available) {
19535
+ return {
19536
+ success: false,
19537
+ output: maestroCheck.message,
19538
+ exitCode: 1,
19539
+ duration: 0,
19540
+ maestroInstalled: false
19541
+ };
19542
+ }
19543
+ const args = ["test"];
19544
+ if (deviceId) {
19545
+ args.push("--device", deviceId);
19546
+ }
19547
+ Object.entries(env).forEach(([key, value]) => {
19548
+ args.push("-e", `${key}=${value}`);
19549
+ });
19550
+ args.push(flowFile);
19551
+ const command = `maestro ${args.join(" ")}`;
19552
+ const startTime = Date.now();
19553
+ try {
19554
+ const output = execSync(command, {
19555
+ encoding: "utf-8",
19556
+ timeout,
19557
+ maxBuffer: 10 * 1024 * 1024,
19558
+ env: { ...process.env, ...env }
19559
+ });
19560
+ const duration3 = Date.now() - startTime;
19561
+ return {
19562
+ success: true,
19563
+ output: `${maestroCheck.installed ? "\u{1F4E6} " + maestroCheck.message + "\n\n" : ""}\u2705 Test passed!
19564
+
19565
+ Command: ${command}
19566
+
19567
+ ${output}`,
19568
+ exitCode: 0,
19569
+ duration: duration3,
19570
+ command,
19571
+ maestroInstalled: maestroCheck.installed
19572
+ };
19573
+ } catch (error2) {
19574
+ const duration3 = Date.now() - startTime;
19575
+ const output = error2.stdout || error2.stderr || error2.message;
19576
+ return {
19577
+ success: false,
19578
+ output: `${maestroCheck.installed ? "\u{1F4E6} " + maestroCheck.message + "\n\n" : ""}\u274C Test failed!
19579
+
19580
+ Command: ${command}
19581
+
19582
+ ${output}`,
19583
+ exitCode: error2.status || 1,
19584
+ duration: duration3,
19585
+ command,
19586
+ maestroInstalled: maestroCheck.installed
19587
+ };
19588
+ }
19589
+ }
19590
+ function executeTestsSequentially(options = {}) {
19591
+ const {
19592
+ flowFiles = [],
19593
+ deviceId = null,
19594
+ env = {},
19595
+ stopOnFailure = false
19596
+ } = options;
19597
+ if (!flowFiles || flowFiles.length === 0) {
19598
+ return {
19599
+ success: false,
19600
+ results: [],
19601
+ summary: {
19602
+ total: 0,
19603
+ passed: 0,
19604
+ failed: 0,
19605
+ skipped: 0,
19606
+ totalDuration: 0
19607
+ }
19608
+ };
19609
+ }
19610
+ const results = [];
19611
+ let stopped = false;
19612
+ for (const flowFile of flowFiles) {
19613
+ if (stopped) {
19614
+ results.push({
19615
+ flowFile,
19616
+ success: false,
19617
+ output: "\u23ED\uFE0F Skipped due to previous failure",
19618
+ exitCode: -1,
19619
+ duration: 0,
19620
+ skipped: true
19621
+ });
19622
+ continue;
19623
+ }
19624
+ const result = executeTest({ flowFile, deviceId, env });
19625
+ results.push({
19626
+ flowFile,
19627
+ ...result,
19628
+ skipped: false
19629
+ });
19630
+ if (!result.success && stopOnFailure) {
19631
+ stopped = true;
19632
+ }
19633
+ }
19634
+ const passed = results.filter((r) => r.success).length;
19635
+ const failed = results.filter((r) => !r.success && !r.skipped).length;
19636
+ const skipped = results.filter((r) => r.skipped).length;
19637
+ const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
19638
+ return {
19639
+ success: failed === 0,
19640
+ results,
19641
+ summary: {
19642
+ total: flowFiles.length,
19643
+ passed,
19644
+ failed,
19645
+ skipped,
19646
+ totalDuration,
19647
+ report: `
19648
+ ## \u{1F4CA} Test Execution Summary
19649
+
19650
+ | Metric | Value |
19651
+ |--------|-------|
19652
+ | Total Tests | ${flowFiles.length} |
19653
+ | \u2705 Passed | ${passed} |
19654
+ | \u274C Failed | ${failed} |
19655
+ | \u23ED\uFE0F Skipped | ${skipped} |
19656
+ | \u23F1\uFE0F Duration | ${(totalDuration / 1e3).toFixed(2)}s |
19657
+
19658
+ ### Results by Flow:
19659
+ ${results.map(
19660
+ (r) => `- ${r.success ? "\u2705" : r.skipped ? "\u23ED\uFE0F" : "\u274C"} \`${r.flowFile}\` (${(r.duration / 1e3).toFixed(2)}s)`
19661
+ ).join("\n")}
19662
+ `
19663
+ }
19664
+ };
19665
+ }
19666
+ function discoverTestFiles(options = {}) {
19667
+ const { directory = "maestro", basePath = process.cwd() } = options;
19668
+ const targetDir = join(basePath, directory);
19669
+ if (!existsSync(targetDir)) {
19670
+ return {
19671
+ files: [],
19672
+ count: 0,
19673
+ message: `\u274C Directory not found: ${targetDir}`
19674
+ };
19675
+ }
19676
+ try {
19677
+ const result = execSync(`find "${targetDir}" -name "*.yaml" -type f`, {
19678
+ encoding: "utf-8"
19679
+ });
19680
+ const files = result.trim().split("\n").filter(Boolean);
19681
+ return {
19682
+ files,
19683
+ count: files.length,
19684
+ message: `Found ${files.length} test file(s) in ${targetDir}`
19685
+ };
19686
+ } catch (error2) {
19687
+ return {
19688
+ files: [],
19689
+ count: 0,
19690
+ message: `\u274C Error discovering files: ${error2.message}`
19691
+ };
19692
+ }
19693
+ }
19694
+ function runAllTests(options = {}) {
19695
+ const {
19696
+ directory = "maestro",
19697
+ basePath = process.cwd(),
19698
+ deviceId = null,
19699
+ env = {},
19700
+ stopOnFailure = false,
19701
+ files = null
19702
+ } = options;
19703
+ let discovery;
19704
+ let filesToRun;
19705
+ if (files && Array.isArray(files) && files.length > 0) {
19706
+ filesToRun = files.map((f) => {
19707
+ if (f.startsWith("/")) {
19708
+ return f;
19709
+ }
19710
+ return join(basePath, f);
19711
+ });
19712
+ discovery = {
19713
+ files: filesToRun,
19714
+ count: filesToRun.length,
19715
+ message: `Using ${filesToRun.length} specified test file(s)`,
19716
+ mode: "specified"
19717
+ };
19718
+ } else {
19719
+ discovery = discoverTestFiles({ directory, basePath });
19720
+ discovery.mode = "discovered";
19721
+ filesToRun = discovery.files;
19722
+ }
19723
+ if (!filesToRun || filesToRun.length === 0) {
19724
+ return {
19725
+ discovery,
19726
+ execution: {
19727
+ success: false,
19728
+ results: [],
19729
+ summary: {
19730
+ total: 0,
19731
+ passed: 0,
19732
+ failed: 0,
19733
+ skipped: 0,
19734
+ totalDuration: 0,
19735
+ report: "No test files found to execute."
19736
+ }
19737
+ }
19738
+ };
19739
+ }
19740
+ const execution = executeTestsSequentially({
19741
+ flowFiles: filesToRun,
19742
+ deviceId,
19743
+ env,
19744
+ stopOnFailure
19745
+ });
19746
+ return {
19747
+ discovery,
19748
+ execution
19749
+ };
19750
+ }
19292
19751
 
19293
19752
  // maestro-ai/src/mcp-server.js
19294
19753
  var transport = new StdioServerTransport();
@@ -19306,6 +19765,53 @@ var server = new Server(
19306
19765
  server.setRequestHandler(ListToolsRequestSchema, async () => {
19307
19766
  return {
19308
19767
  tools: [
19768
+ {
19769
+ name: "maestro_ensure_installed",
19770
+ description: `\u26A0\uFE0F PREREQUISITE: Call this FIRST before using maestro_write_test or maestro_run_all_tests!
19771
+
19772
+ Checks if Maestro CLI is installed and automatically installs it if missing.
19773
+
19774
+ ## \u{1F6A8} CRITICAL: Always Call First
19775
+ When a user asks to "generate and run tests" or "run Maestro tests", you MUST:
19776
+ 1. FIRST call maestro_ensure_installed() to verify/install Maestro CLI
19777
+ 2. THEN proceed with test generation/execution tools
19778
+
19779
+ ## Purpose
19780
+ Ensures Maestro CLI is available before running any Maestro commands. This tool:
19781
+ - Checks if Maestro CLI is installed on the system
19782
+ - Automatically installs it if missing (via Homebrew or curl)
19783
+ - Returns version and path information
19784
+
19785
+ ## When to Use
19786
+ - ALWAYS before maestro_write_test (if execute=true)
19787
+ - ALWAYS before maestro_run_all_tests
19788
+ - When setting up a new development environment
19789
+ - To troubleshoot Maestro installation issues
19790
+
19791
+ ## Installation Method
19792
+ Uses curl installer: \`curl -fsSL "https://get.maestro.mobile.dev" | bash\`
19793
+
19794
+ ## Output
19795
+ - installed: Whether Maestro is now installed
19796
+ - version: Installed version (if available)
19797
+ - wasInstalled: Whether this call performed the installation
19798
+ - message: Status message
19799
+ - details: Additional info (path, install method)`,
19800
+ inputSchema: {
19801
+ type: "object",
19802
+ properties: {
19803
+ autoInstall: {
19804
+ type: "boolean",
19805
+ description: "Whether to automatically install Maestro if not found (default: true)"
19806
+ },
19807
+ forceReinstall: {
19808
+ type: "boolean",
19809
+ description: "Whether to force reinstall even if already installed (default: false)"
19810
+ }
19811
+ },
19812
+ required: []
19813
+ }
19814
+ },
19309
19815
  {
19310
19816
  name: "maestro_generate_test",
19311
19817
  description: `Generates comprehensive Maestro test instructions for web applications.
@@ -19542,6 +20048,198 @@ Automatically runs git commands to:
19542
20048
  },
19543
20049
  required: ["repoPath"]
19544
20050
  }
20051
+ },
20052
+ {
20053
+ name: "maestro_write_test",
20054
+ description: `Writes a Maestro test YAML file to disk and automatically executes it.
20055
+
20056
+ ## \u{1F6A8} PREREQUISITE: Call maestro_ensure_installed() FIRST before using this tool!
20057
+
20058
+ ## Purpose
20059
+ Saves generated test YAML to a file and AUTOMATICALLY RUNS IT. This is the primary tool for "generate and run" workflows.
20060
+
20061
+ ## IMPORTANT
20062
+ - \u26A0\uFE0F ALWAYS call maestro_ensure_installed() first to ensure Maestro CLI is available!
20063
+ - This tool ALREADY executes the test by default - do NOT call maestro_run_all_tests after this!
20064
+ - Use this for running newly generated tests
20065
+ - The execution result is included in the response
20066
+ - This tool internally calls maestro_generate_test to provide guidelines and instructions
20067
+
20068
+ ## Parameters
20069
+ - yaml: The YAML content to write (required)
20070
+ - fileName: Name of the file, e.g., "login_test.yaml" (required)
20071
+ - directory: Target directory (default: "maestro")
20072
+ - basePath: Project base path (default: current directory)
20073
+ - execute: Whether to auto-execute after writing (default: true)
20074
+ - deviceId: Device ID for execution (optional)
20075
+ - env: Environment variables for execution (optional)
20076
+ - feature: Feature being tested (for test generation guidelines)
20077
+ - action: Action being tested (for test generation guidelines)
20078
+ - flowType: Type of flow template (auth, sidebar, form, modal, navigation, extended)
20079
+ - changedElements: List of UI elements that were changed
20080
+ - existingTests: List of existing related test files
20081
+
20082
+ ## Output
20083
+ - success: boolean
20084
+ - filePath: Path where file was written
20085
+ - message: Status message
20086
+ - generation: Test generation guidelines and instructions (from maestro_generate_test)
20087
+ - execution: Test execution results (if execute=true)`,
20088
+ inputSchema: {
20089
+ type: "object",
20090
+ properties: {
20091
+ yaml: {
20092
+ type: "string",
20093
+ description: "The YAML content to write"
20094
+ },
20095
+ fileName: {
20096
+ type: "string",
20097
+ description: 'Name of the file (e.g., "login_test.yaml")'
20098
+ },
20099
+ directory: {
20100
+ type: "string",
20101
+ description: 'Target directory (default: "maestro")'
20102
+ },
20103
+ basePath: {
20104
+ type: "string",
20105
+ description: "Project base path (default: current directory)"
20106
+ },
20107
+ execute: {
20108
+ type: "boolean",
20109
+ description: "Whether to auto-execute the test after writing (default: true)"
20110
+ },
20111
+ deviceId: {
20112
+ type: "string",
20113
+ description: "Device ID to run on (optional)"
20114
+ },
20115
+ env: {
20116
+ type: "object",
20117
+ description: "Environment variables to pass",
20118
+ additionalProperties: { type: "string" }
20119
+ },
20120
+ feature: {
20121
+ type: "string",
20122
+ description: 'Feature being tested for generation guidelines (e.g., "User Settings")'
20123
+ },
20124
+ action: {
20125
+ type: "string",
20126
+ description: 'Action being tested for generation guidelines (e.g., "Reset defaults")'
20127
+ },
20128
+ flowType: {
20129
+ type: "string",
20130
+ enum: [
20131
+ "auth",
20132
+ "sidebar",
20133
+ "form",
20134
+ "modal",
20135
+ "navigation",
20136
+ "extended"
20137
+ ],
20138
+ description: "Type of flow template for generation guidelines"
20139
+ },
20140
+ changedElements: {
20141
+ type: "array",
20142
+ items: { type: "string" },
20143
+ description: "List of UI elements that were changed (from git diff)"
20144
+ },
20145
+ existingTests: {
20146
+ type: "array",
20147
+ items: { type: "string" },
20148
+ description: "List of existing related test files to potentially extend"
20149
+ }
20150
+ },
20151
+ required: ["yaml", "fileName"]
20152
+ }
20153
+ },
20154
+ {
20155
+ name: "maestro_discover_tests",
20156
+ description: `Discovers all Maestro test files in a directory.
20157
+
20158
+ ## Purpose
20159
+ Lists all .yaml test files in the maestro/ directory.
20160
+
20161
+ ## Parameters
20162
+ - directory: Directory to search (default: "maestro")
20163
+ - basePath: Project base path
20164
+
20165
+ ## Output
20166
+ - files: Array of file paths
20167
+ - count: Number of files found`,
20168
+ inputSchema: {
20169
+ type: "object",
20170
+ properties: {
20171
+ directory: {
20172
+ type: "string",
20173
+ description: 'Directory to search (default: "maestro")'
20174
+ },
20175
+ basePath: {
20176
+ type: "string",
20177
+ description: "Project base path"
20178
+ }
20179
+ },
20180
+ required: []
20181
+ }
20182
+ },
20183
+ {
20184
+ name: "maestro_run_all_tests",
20185
+ description: `Runs the ENTIRE Maestro test suite.
20186
+
20187
+ ## \u{1F6A8} PREREQUISITE: Call maestro_ensure_installed() FIRST before using this tool!
20188
+
20189
+ ## \u26A0\uFE0F ONLY use when user EXPLICITLY asks to "run all tests" or "run the test suite"
20190
+
20191
+ ## DO NOT USE for:
20192
+ - "generate and run test cases" \u2192 use maestro_write_test instead (it auto-executes)
20193
+ - Running a newly generated test \u2192 maestro_write_test already does this
20194
+ - Any workflow that involves generating new tests
20195
+
20196
+ ## Purpose
20197
+ Discovers and executes ALL existing .yaml test files in the maestro/ directory.
20198
+
20199
+ ## Parameters
20200
+ - files: Specific test file paths (optional)
20201
+ - directory: Directory containing tests (default: "maestro")
20202
+ - basePath: Project base path
20203
+ - deviceId: Target device ID
20204
+ - env: Environment variables
20205
+ - stopOnFailure: Stop on first failure (default: false)
20206
+
20207
+ ## Output
20208
+ - discovery: List of test files found
20209
+ - execution: Results with pass/fail status for each test
20210
+ - summary: Aggregated statistics`,
20211
+ inputSchema: {
20212
+ type: "object",
20213
+ properties: {
20214
+ files: {
20215
+ type: "array",
20216
+ items: { type: "string" },
20217
+ description: "Specific test file paths to run (if provided, only these files are executed instead of discovering all)"
20218
+ },
20219
+ directory: {
20220
+ type: "string",
20221
+ description: 'Directory containing tests (default: "maestro") - only used if files is not provided'
20222
+ },
20223
+ basePath: {
20224
+ type: "string",
20225
+ description: "Project base path"
20226
+ },
20227
+ deviceId: {
20228
+ type: "string",
20229
+ description: "Device ID to run on"
20230
+ },
20231
+ env: {
20232
+ type: "object",
20233
+ description: "Environment variables",
20234
+ additionalProperties: { type: "string" }
20235
+ },
20236
+ stopOnFailure: {
20237
+ type: "boolean",
20238
+ description: "Stop on first failure (default: false)"
20239
+ }
20240
+ },
20241
+ required: []
20242
+ }
19545
20243
  }
19546
20244
  ]
19547
20245
  };
@@ -19550,6 +20248,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
19550
20248
  const { name, arguments: args } = request.params;
19551
20249
  let result;
19552
20250
  switch (name) {
20251
+ case "maestro_ensure_installed": {
20252
+ const { autoInstall, forceReinstall } = args || {};
20253
+ result = ensureMaestroInstalled({
20254
+ autoInstall: autoInstall !== false,
20255
+ // defaults to true
20256
+ forceReinstall: forceReinstall || false
20257
+ });
20258
+ break;
20259
+ }
19553
20260
  case "maestro_generate_test": {
19554
20261
  const { feature, action, flowType, changedElements, existingTests } = args || {};
19555
20262
  result = generateMaestroTest({
@@ -19600,6 +20307,58 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
19600
20307
  });
19601
20308
  break;
19602
20309
  }
20310
+ case "maestro_write_test": {
20311
+ const {
20312
+ yaml,
20313
+ fileName,
20314
+ directory,
20315
+ basePath,
20316
+ execute,
20317
+ deviceId,
20318
+ env,
20319
+ feature,
20320
+ action,
20321
+ flowType,
20322
+ changedElements,
20323
+ existingTests
20324
+ } = args || {};
20325
+ result = writeTestFile({
20326
+ yaml,
20327
+ fileName,
20328
+ directory: directory || "maestro",
20329
+ basePath: basePath || process.cwd(),
20330
+ execute: execute !== false,
20331
+ // defaults to true
20332
+ deviceId: deviceId || null,
20333
+ env: env || {},
20334
+ feature: feature || "",
20335
+ action: action || "",
20336
+ flowType: flowType || null,
20337
+ changedElements: changedElements || [],
20338
+ existingTests: existingTests || []
20339
+ });
20340
+ break;
20341
+ }
20342
+ case "maestro_discover_tests": {
20343
+ const { directory, basePath } = args || {};
20344
+ result = discoverTestFiles({
20345
+ directory: directory || "maestro",
20346
+ basePath: basePath || process.cwd()
20347
+ });
20348
+ break;
20349
+ }
20350
+ case "maestro_run_all_tests": {
20351
+ const { files, directory, basePath, deviceId, env, stopOnFailure } = args || {};
20352
+ result = runAllTests({
20353
+ files: files || null,
20354
+ directory: directory || "maestro",
20355
+ basePath: basePath || process.cwd(),
20356
+ deviceId: deviceId || null,
20357
+ env: env || {},
20358
+ stopOnFailure: stopOnFailure || false
20359
+ });
20360
+ break;
20361
+ }
19603
20362
  default:
19604
20363
  throw new Error(`Unknown tool: ${name}`);
19605
20364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deriv-com/fe-mcp-servers",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Collection of Front-End Model Context Protocol (MCP) servers for reusability and standardization",
5
5
  "type": "module",
6
6
  "bin": {