@deriv-com/fe-mcp-servers 0.0.13 → 0.0.15
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.
|
@@ -273,7 +273,6 @@ Parses and validates acceptance criteria from a ClickUp task for testability.
|
|
|
273
273
|
⚠️ **HARD VALIDATION** - Maps acceptance criteria items to UI elements and **FAILS on mismatches**.
|
|
274
274
|
|
|
275
275
|
This tool performs strict validation of AC against actual UI implementation. If the AC specifies one value but the UI has a different value, this tool will:
|
|
276
|
-
|
|
277
276
|
1. **FAIL** (`success: false`)
|
|
278
277
|
2. **Report** the exact mismatch
|
|
279
278
|
3. **Block** test generation until resolved
|
|
@@ -19449,12 +19449,224 @@ ${analysis.shouldCreateTest ? `1. Review the changed UI files above
|
|
|
19449
19449
|
}
|
|
19450
19450
|
};
|
|
19451
19451
|
}
|
|
19452
|
+
function detectProjectBaseUrl(projectPath = process.cwd(), explicitUrl = null) {
|
|
19453
|
+
if (explicitUrl) {
|
|
19454
|
+
const portMatch = explicitUrl.match(/:(\d+)/);
|
|
19455
|
+
return {
|
|
19456
|
+
url: explicitUrl,
|
|
19457
|
+
source: "explicit",
|
|
19458
|
+
port: portMatch ? parseInt(portMatch[1], 10) : 443,
|
|
19459
|
+
https: explicitUrl.startsWith("https")
|
|
19460
|
+
};
|
|
19461
|
+
}
|
|
19462
|
+
let detectedPort = null;
|
|
19463
|
+
let detectedHttps = false;
|
|
19464
|
+
let source = "fallback";
|
|
19465
|
+
const configChecks = [
|
|
19466
|
+
// Vite config
|
|
19467
|
+
{
|
|
19468
|
+
files: ["vite.config.js", "vite.config.ts", "vite.config.mjs"],
|
|
19469
|
+
patterns: [
|
|
19470
|
+
/server\s*:\s*\{[^}]*port\s*:\s*(\d+)/s,
|
|
19471
|
+
/port\s*:\s*(\d+)/,
|
|
19472
|
+
/preview\s*:\s*\{[^}]*port\s*:\s*(\d+)/s
|
|
19473
|
+
],
|
|
19474
|
+
httpsPatterns: [/https\s*:\s*true/, /server\s*:\s*\{[^}]*https\s*:/s],
|
|
19475
|
+
name: "vite.config"
|
|
19476
|
+
},
|
|
19477
|
+
// Next.js config
|
|
19478
|
+
{
|
|
19479
|
+
files: ["next.config.js", "next.config.mjs", "next.config.ts"],
|
|
19480
|
+
patterns: [/port\s*:\s*(\d+)/, /PORT\s*[=:]\s*(\d+)/],
|
|
19481
|
+
httpsPatterns: [],
|
|
19482
|
+
name: "next.config"
|
|
19483
|
+
},
|
|
19484
|
+
// Webpack config
|
|
19485
|
+
{
|
|
19486
|
+
files: ["webpack.config.js", "webpack.config.ts"],
|
|
19487
|
+
patterns: [
|
|
19488
|
+
/devServer\s*:\s*\{[^}]*port\s*:\s*(\d+)/s,
|
|
19489
|
+
/port\s*:\s*(\d+)/
|
|
19490
|
+
],
|
|
19491
|
+
httpsPatterns: [/https\s*:\s*true/],
|
|
19492
|
+
name: "webpack.config"
|
|
19493
|
+
},
|
|
19494
|
+
// Angular config
|
|
19495
|
+
{
|
|
19496
|
+
files: ["angular.json"],
|
|
19497
|
+
patterns: [/"port"\s*:\s*(\d+)/, /"ssl"\s*:\s*true/],
|
|
19498
|
+
httpsPatterns: [/"ssl"\s*:\s*true/],
|
|
19499
|
+
name: "angular.json"
|
|
19500
|
+
}
|
|
19501
|
+
];
|
|
19502
|
+
for (const config2 of configChecks) {
|
|
19503
|
+
for (const fileName of config2.files) {
|
|
19504
|
+
try {
|
|
19505
|
+
const configPath = join(projectPath, fileName);
|
|
19506
|
+
if (existsSync(configPath)) {
|
|
19507
|
+
const content = readFileSync(configPath, "utf-8");
|
|
19508
|
+
for (const pattern of config2.patterns) {
|
|
19509
|
+
const match = content.match(pattern);
|
|
19510
|
+
if (match && match[1]) {
|
|
19511
|
+
detectedPort = parseInt(match[1], 10);
|
|
19512
|
+
source = config2.name;
|
|
19513
|
+
break;
|
|
19514
|
+
}
|
|
19515
|
+
}
|
|
19516
|
+
for (const httpsPattern of config2.httpsPatterns) {
|
|
19517
|
+
if (httpsPattern.test(content)) {
|
|
19518
|
+
detectedHttps = true;
|
|
19519
|
+
break;
|
|
19520
|
+
}
|
|
19521
|
+
}
|
|
19522
|
+
if (detectedPort) break;
|
|
19523
|
+
}
|
|
19524
|
+
} catch {
|
|
19525
|
+
}
|
|
19526
|
+
}
|
|
19527
|
+
if (detectedPort) break;
|
|
19528
|
+
}
|
|
19529
|
+
if (!detectedPort) {
|
|
19530
|
+
try {
|
|
19531
|
+
const packageJsonPath = join(projectPath, "package.json");
|
|
19532
|
+
if (existsSync(packageJsonPath)) {
|
|
19533
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
19534
|
+
const scripts = packageJson.scripts || {};
|
|
19535
|
+
const scriptKeys = ["dev", "start", "serve", "develop"];
|
|
19536
|
+
for (const key of scriptKeys) {
|
|
19537
|
+
const script = scripts[key];
|
|
19538
|
+
if (script) {
|
|
19539
|
+
const portPatterns = [
|
|
19540
|
+
/--port[=\s]+(\d+)/,
|
|
19541
|
+
/-p[=\s]+(\d+)/,
|
|
19542
|
+
/PORT[=:]\s*(\d+)/,
|
|
19543
|
+
/localhost:(\d+)/,
|
|
19544
|
+
/127\.0\.0\.1:(\d+)/,
|
|
19545
|
+
/:(\d{4,5})\b/
|
|
19546
|
+
// Generic 4-5 digit port
|
|
19547
|
+
];
|
|
19548
|
+
for (const pattern of portPatterns) {
|
|
19549
|
+
const match = script.match(pattern);
|
|
19550
|
+
if (match && match[1]) {
|
|
19551
|
+
detectedPort = parseInt(match[1], 10);
|
|
19552
|
+
source = `package.json scripts.${key}`;
|
|
19553
|
+
break;
|
|
19554
|
+
}
|
|
19555
|
+
}
|
|
19556
|
+
if (/--https|HTTPS=true|ssl/.test(script)) {
|
|
19557
|
+
detectedHttps = true;
|
|
19558
|
+
}
|
|
19559
|
+
if (detectedPort) break;
|
|
19560
|
+
}
|
|
19561
|
+
}
|
|
19562
|
+
}
|
|
19563
|
+
} catch {
|
|
19564
|
+
}
|
|
19565
|
+
}
|
|
19566
|
+
if (!detectedPort) {
|
|
19567
|
+
const envFiles = [".env", ".env.local", ".env.development", ".env.dev"];
|
|
19568
|
+
for (const envFile of envFiles) {
|
|
19569
|
+
try {
|
|
19570
|
+
const envPath = join(projectPath, envFile);
|
|
19571
|
+
if (existsSync(envPath)) {
|
|
19572
|
+
const content = readFileSync(envPath, "utf-8");
|
|
19573
|
+
const portPatterns = [
|
|
19574
|
+
/^PORT\s*=\s*(\d+)/m,
|
|
19575
|
+
/^VITE_PORT\s*=\s*(\d+)/m,
|
|
19576
|
+
/^DEV_PORT\s*=\s*(\d+)/m,
|
|
19577
|
+
/^SERVER_PORT\s*=\s*(\d+)/m
|
|
19578
|
+
];
|
|
19579
|
+
for (const pattern of portPatterns) {
|
|
19580
|
+
const match = content.match(pattern);
|
|
19581
|
+
if (match && match[1]) {
|
|
19582
|
+
detectedPort = parseInt(match[1], 10);
|
|
19583
|
+
source = envFile;
|
|
19584
|
+
break;
|
|
19585
|
+
}
|
|
19586
|
+
}
|
|
19587
|
+
if (detectedPort) break;
|
|
19588
|
+
}
|
|
19589
|
+
} catch {
|
|
19590
|
+
}
|
|
19591
|
+
}
|
|
19592
|
+
}
|
|
19593
|
+
if (!detectedPort && process.env.MAESTRO_BASE_URL) {
|
|
19594
|
+
const envUrl = process.env.MAESTRO_BASE_URL;
|
|
19595
|
+
const portMatch = envUrl.match(/:(\d+)/);
|
|
19596
|
+
return {
|
|
19597
|
+
url: envUrl,
|
|
19598
|
+
source: "MAESTRO_BASE_URL env",
|
|
19599
|
+
port: portMatch ? parseInt(portMatch[1], 10) : 443,
|
|
19600
|
+
https: envUrl.startsWith("https")
|
|
19601
|
+
};
|
|
19602
|
+
}
|
|
19603
|
+
if (!detectedPort) {
|
|
19604
|
+
detectedPort = 3e3;
|
|
19605
|
+
source = "fallback (default)";
|
|
19606
|
+
}
|
|
19607
|
+
const protocol = detectedHttps ? "https" : "http";
|
|
19608
|
+
const url2 = `${protocol}://localhost:${detectedPort}`;
|
|
19609
|
+
return {
|
|
19610
|
+
url: url2,
|
|
19611
|
+
source,
|
|
19612
|
+
port: detectedPort,
|
|
19613
|
+
https: detectedHttps
|
|
19614
|
+
};
|
|
19615
|
+
}
|
|
19616
|
+
function enforceMandatoryTemplate(yaml, name = "", projectPath = process.cwd(), explicitBaseUrl = null) {
|
|
19617
|
+
if (!yaml || typeof yaml !== "string") {
|
|
19618
|
+
yaml = "";
|
|
19619
|
+
}
|
|
19620
|
+
const detected = detectProjectBaseUrl(projectPath, explicitBaseUrl);
|
|
19621
|
+
const detectedUrl = detected.url;
|
|
19622
|
+
const mandatoryHeader = `appId: web
|
|
19623
|
+
name: '${name}'
|
|
19624
|
+
url: \${BASE_URL}
|
|
19625
|
+
|
|
19626
|
+
env:
|
|
19627
|
+
BASE_URL: \${MAESTRO_BASE_URL || "${detectedUrl}"}
|
|
19628
|
+
`;
|
|
19629
|
+
const hasAppIdWeb = /^appId:\s*web/m.test(yaml);
|
|
19630
|
+
const hasName = /^name:\s*['"]/m.test(yaml);
|
|
19631
|
+
const hasUrl = /^url:\s*\$\{BASE_URL\}/m.test(yaml);
|
|
19632
|
+
const hasEnv = /^env:\s*$/m.test(yaml);
|
|
19633
|
+
const hasBaseUrl = /BASE_URL:\s*\$\{MAESTRO_BASE_URL/m.test(yaml);
|
|
19634
|
+
if (hasAppIdWeb && hasName && hasUrl && hasEnv && hasBaseUrl) {
|
|
19635
|
+
if (name && yaml.match(/^name:\s*['"]\s*['"]/m)) {
|
|
19636
|
+
yaml = yaml.replace(/^name:\s*['"]\s*['"]/m, `name: '${name}'`);
|
|
19637
|
+
}
|
|
19638
|
+
return yaml;
|
|
19639
|
+
}
|
|
19640
|
+
let testSteps = "";
|
|
19641
|
+
const stepsMatch = yaml.match(/^---\s*\n([\s\S]*)$/m);
|
|
19642
|
+
if (stepsMatch) {
|
|
19643
|
+
testSteps = stepsMatch[1];
|
|
19644
|
+
} else {
|
|
19645
|
+
const stepsStart = yaml.search(
|
|
19646
|
+
/\n(- launchApp|- tapOn|- assertVisible|- inputText|- extendedWaitUntil)/
|
|
19647
|
+
);
|
|
19648
|
+
if (stepsStart > 0) {
|
|
19649
|
+
testSteps = yaml.substring(stepsStart + 1);
|
|
19650
|
+
} else {
|
|
19651
|
+
if (yaml.trim().startsWith("-")) {
|
|
19652
|
+
testSteps = yaml.trim();
|
|
19653
|
+
} else {
|
|
19654
|
+
testSteps = yaml.trim();
|
|
19655
|
+
}
|
|
19656
|
+
}
|
|
19657
|
+
}
|
|
19658
|
+
const enforcedYaml = `${mandatoryHeader}---
|
|
19659
|
+
${testSteps.trim()}
|
|
19660
|
+
`;
|
|
19661
|
+
return enforcedYaml;
|
|
19662
|
+
}
|
|
19452
19663
|
function writeTestFile(options = {}) {
|
|
19453
19664
|
const {
|
|
19454
19665
|
yaml,
|
|
19455
19666
|
fileName,
|
|
19456
19667
|
directory = "maestro",
|
|
19457
19668
|
basePath = process.cwd(),
|
|
19669
|
+
baseUrl = null,
|
|
19458
19670
|
execute = true,
|
|
19459
19671
|
deviceId = null,
|
|
19460
19672
|
env = {},
|
|
@@ -19478,6 +19690,14 @@ function writeTestFile(options = {}) {
|
|
|
19478
19690
|
message: "\u274C Missing required parameters: yaml and fileName are required"
|
|
19479
19691
|
};
|
|
19480
19692
|
}
|
|
19693
|
+
const testName = feature && action ? `${feature} - ${action}` : feature || action || "";
|
|
19694
|
+
const enforcedYaml = enforceMandatoryTemplate(
|
|
19695
|
+
yaml,
|
|
19696
|
+
testName,
|
|
19697
|
+
basePath,
|
|
19698
|
+
baseUrl
|
|
19699
|
+
);
|
|
19700
|
+
const detectedUrl = detectProjectBaseUrl(basePath, baseUrl);
|
|
19481
19701
|
const normalizedFileName = fileName.endsWith(".yaml") ? fileName : `${fileName}.yaml`;
|
|
19482
19702
|
const targetDir = join(basePath, directory);
|
|
19483
19703
|
const filePath = join(targetDir, normalizedFileName);
|
|
@@ -19485,16 +19705,19 @@ function writeTestFile(options = {}) {
|
|
|
19485
19705
|
if (!existsSync(targetDir)) {
|
|
19486
19706
|
mkdirSync(targetDir, { recursive: true });
|
|
19487
19707
|
}
|
|
19488
|
-
writeFileSync(filePath,
|
|
19708
|
+
writeFileSync(filePath, enforcedYaml, "utf-8");
|
|
19489
19709
|
const fd = openSync(filePath, "r");
|
|
19490
19710
|
fsyncSync(fd);
|
|
19491
19711
|
closeSync(fd);
|
|
19492
19712
|
const result = {
|
|
19493
19713
|
success: true,
|
|
19494
19714
|
filePath,
|
|
19495
|
-
message: `\u2705 Test file written successfully to: ${filePath}
|
|
19496
|
-
|
|
19715
|
+
message: `\u2705 Test file written successfully to: ${filePath}
|
|
19716
|
+
\u{1F4CD} Base URL: ${detectedUrl.url} (detected from: ${detectedUrl.source})`,
|
|
19717
|
+
generation,
|
|
19497
19718
|
// Include test generation guidelines and instructions
|
|
19719
|
+
detectedUrl
|
|
19720
|
+
// Include URL detection info
|
|
19498
19721
|
};
|
|
19499
19722
|
if (execute) {
|
|
19500
19723
|
const sleep = (ms) => {
|
|
@@ -20978,11 +21201,21 @@ Saves generated test YAML to a file and AUTOMATICALLY RUNS IT. This is the prima
|
|
|
20978
21201
|
- The execution result is included in the response
|
|
20979
21202
|
- This tool internally calls maestro_generate_test to provide guidelines and instructions
|
|
20980
21203
|
|
|
21204
|
+
## URL Auto-Detection
|
|
21205
|
+
Base URL is automatically detected from project config files:
|
|
21206
|
+
1. **Explicit baseUrl parameter** (highest priority)
|
|
21207
|
+
2. **Config files**: vite.config.js, next.config.js, webpack.config.js, angular.json
|
|
21208
|
+
3. **package.json scripts**: --port flags in dev/start scripts
|
|
21209
|
+
4. **.env files**: PORT, VITE_PORT, DEV_PORT variables
|
|
21210
|
+
5. **MAESTRO_BASE_URL env var**
|
|
21211
|
+
6. **Fallback**: http://localhost:3000
|
|
21212
|
+
|
|
20981
21213
|
## Parameters
|
|
20982
21214
|
- yaml: The YAML content to write (required)
|
|
20983
21215
|
- fileName: Name of the file, e.g., "login_test.yaml" (required)
|
|
20984
21216
|
- directory: Target directory (default: "maestro")
|
|
20985
21217
|
- basePath: Project base path (default: current directory)
|
|
21218
|
+
- baseUrl: Explicit base URL override (optional, auto-detected if not provided)
|
|
20986
21219
|
- execute: Whether to auto-execute after writing (default: true)
|
|
20987
21220
|
- deviceId: Device ID for execution (optional)
|
|
20988
21221
|
- env: Environment variables for execution (optional)
|
|
@@ -20995,8 +21228,9 @@ Saves generated test YAML to a file and AUTOMATICALLY RUNS IT. This is the prima
|
|
|
20995
21228
|
## Output
|
|
20996
21229
|
- success: boolean
|
|
20997
21230
|
- filePath: Path where file was written
|
|
20998
|
-
- message: Status message
|
|
21231
|
+
- message: Status message with detected URL info
|
|
20999
21232
|
- generation: Test generation guidelines and instructions (from maestro_generate_test)
|
|
21233
|
+
- detectedUrl: URL detection info (url, source, port, https)
|
|
21000
21234
|
- execution: Test execution results (if execute=true)`,
|
|
21001
21235
|
inputSchema: {
|
|
21002
21236
|
type: "object",
|
|
@@ -21017,6 +21251,10 @@ Saves generated test YAML to a file and AUTOMATICALLY RUNS IT. This is the prima
|
|
|
21017
21251
|
type: "string",
|
|
21018
21252
|
description: "Project base path (default: current directory)"
|
|
21019
21253
|
},
|
|
21254
|
+
baseUrl: {
|
|
21255
|
+
type: "string",
|
|
21256
|
+
description: "Explicit base URL override (optional). If not provided, auto-detected from project config (vite.config, next.config, package.json scripts, .env files)"
|
|
21257
|
+
},
|
|
21020
21258
|
execute: {
|
|
21021
21259
|
type: "boolean",
|
|
21022
21260
|
description: "Whether to auto-execute the test after writing (default: true)"
|
|
@@ -21386,6 +21624,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
21386
21624
|
fileName,
|
|
21387
21625
|
directory,
|
|
21388
21626
|
basePath,
|
|
21627
|
+
baseUrl,
|
|
21389
21628
|
execute,
|
|
21390
21629
|
deviceId,
|
|
21391
21630
|
env,
|
|
@@ -21400,6 +21639,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
21400
21639
|
fileName,
|
|
21401
21640
|
directory: directory || "maestro",
|
|
21402
21641
|
basePath: basePath || process.cwd(),
|
|
21642
|
+
baseUrl: baseUrl || null,
|
|
21643
|
+
// Auto-detected from project config if not provided
|
|
21403
21644
|
execute: execute !== false,
|
|
21404
21645
|
// defaults to true
|
|
21405
21646
|
deviceId: deviceId || null,
|