@muggleai/works 4.0.1 → 4.0.3
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.
- package/dist/{chunk-AJKZXT7B.js → chunk-BQZQDOXI.js} +313 -60
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +103 -46
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/skills/muggle-test-feature-local/SKILL.md +103 -46
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs3 from 'fs';
|
|
2
|
-
import { readFileSync, existsSync, rmSync, mkdirSync, createWriteStream,
|
|
2
|
+
import { readFileSync, existsSync, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, statSync } from 'fs';
|
|
3
3
|
import * as os3 from 'os';
|
|
4
4
|
import { platform, arch, homedir } from 'os';
|
|
5
5
|
import * as path2 from 'path';
|
|
@@ -7,7 +7,7 @@ import { dirname, resolve, join } from 'path';
|
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import winston from 'winston';
|
|
9
9
|
import axios, { AxiosError } from 'axios';
|
|
10
|
-
import { spawn, exec } from 'child_process';
|
|
10
|
+
import { spawn, exec, execFile } from 'child_process';
|
|
11
11
|
import * as fs5 from 'fs/promises';
|
|
12
12
|
import { z, ZodError } from 'zod';
|
|
13
13
|
import * as crypto from 'crypto';
|
|
@@ -44,7 +44,7 @@ var DEFAULT_AUTH0_PRODUCTION_DOMAIN = "login.muggle-ai.com";
|
|
|
44
44
|
var DEFAULT_AUTH0_PRODUCTION_CLIENT_ID = "UgG5UjoyLksxMciWWKqVpwfWrJ4rFvtT";
|
|
45
45
|
var DEFAULT_AUTH0_PRODUCTION_AUDIENCE = "https://muggleai.us.auth0.com/api/v2/";
|
|
46
46
|
var DEFAULT_AUTH0_DEV_DOMAIN = "dev-po4mxmz0rd8a0w8w.us.auth0.com";
|
|
47
|
-
var DEFAULT_AUTH0_DEV_CLIENT_ID = "
|
|
47
|
+
var DEFAULT_AUTH0_DEV_CLIENT_ID = "GBvkMdTbCI80XJXnJ90MmbEvXwcWGUtw";
|
|
48
48
|
var DEFAULT_AUTH0_DEV_AUDIENCE = "https://dev-po4mxmz0rd8a0w8w.us.auth0.com/api/v2/";
|
|
49
49
|
var DEFAULT_AUTH0_SCOPE = "openid profile email offline_access";
|
|
50
50
|
var configInstance = null;
|
|
@@ -130,8 +130,7 @@ function getDataDir2() {
|
|
|
130
130
|
}
|
|
131
131
|
function getDownloadedElectronAppPath() {
|
|
132
132
|
const platformName = os3.platform();
|
|
133
|
-
const
|
|
134
|
-
const version = config.electronAppVersion;
|
|
133
|
+
const version = getElectronAppVersion();
|
|
135
134
|
const baseDir = path2.join(getDataDir2(), ELECTRON_APP_DIR, version);
|
|
136
135
|
let binaryPath;
|
|
137
136
|
switch (platformName) {
|
|
@@ -1865,9 +1864,24 @@ function getElectronAppPathOrThrow() {
|
|
|
1865
1864
|
const config = getConfig();
|
|
1866
1865
|
const electronAppPath = config.localQa.electronAppPath;
|
|
1867
1866
|
if (!electronAppPath || electronAppPath.trim() === "") {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1867
|
+
const version = getElectronAppVersion();
|
|
1868
|
+
const versionDir = getElectronAppDir(version);
|
|
1869
|
+
const envPath = process.env.ELECTRON_APP_PATH;
|
|
1870
|
+
const errorLines = [
|
|
1871
|
+
"Electron app binary not found.",
|
|
1872
|
+
"",
|
|
1873
|
+
` Expected version: ${version}`,
|
|
1874
|
+
` Checked directory: ${versionDir}`
|
|
1875
|
+
];
|
|
1876
|
+
if (envPath) {
|
|
1877
|
+
errorLines.push(` ELECTRON_APP_PATH: ${envPath} (not found or invalid)`);
|
|
1878
|
+
} else {
|
|
1879
|
+
errorLines.push(" ELECTRON_APP_PATH: (not set)");
|
|
1880
|
+
}
|
|
1881
|
+
errorLines.push("");
|
|
1882
|
+
errorLines.push("To fix this, run: muggle setup");
|
|
1883
|
+
errorLines.push("Or set ELECTRON_APP_PATH to the path of the MuggleAI executable.");
|
|
1884
|
+
throw new Error(errorLines.join("\n"));
|
|
1871
1885
|
}
|
|
1872
1886
|
return electronAppPath;
|
|
1873
1887
|
}
|
|
@@ -3159,17 +3173,17 @@ var PromptServiceClient = class {
|
|
|
3159
3173
|
* @param path - Path to validate.
|
|
3160
3174
|
* @throws GatewayError if path is not allowed.
|
|
3161
3175
|
*/
|
|
3162
|
-
validatePath(
|
|
3163
|
-
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) =>
|
|
3176
|
+
validatePath(path15) {
|
|
3177
|
+
const isAllowed = ALLOWED_UPSTREAM_PREFIXES.some((prefix) => path15.startsWith(prefix));
|
|
3164
3178
|
if (!isAllowed) {
|
|
3165
3179
|
const logger14 = getLogger();
|
|
3166
3180
|
logger14.error("Path not in allowlist", {
|
|
3167
|
-
path:
|
|
3181
|
+
path: path15,
|
|
3168
3182
|
allowedPrefixes: ALLOWED_UPSTREAM_PREFIXES
|
|
3169
3183
|
});
|
|
3170
3184
|
throw new GatewayError({
|
|
3171
3185
|
code: "FORBIDDEN" /* FORBIDDEN */,
|
|
3172
|
-
message: `Path '${
|
|
3186
|
+
message: `Path '${path15}' is not allowed`
|
|
3173
3187
|
});
|
|
3174
3188
|
}
|
|
3175
3189
|
}
|
|
@@ -5796,8 +5810,8 @@ function createUnifiedMcpServer(options) {
|
|
|
5796
5810
|
errors: error.issues
|
|
5797
5811
|
});
|
|
5798
5812
|
const issueMessages = error.issues.slice(0, 3).map((issue) => {
|
|
5799
|
-
const
|
|
5800
|
-
return
|
|
5813
|
+
const path15 = issue.path.join(".");
|
|
5814
|
+
return path15 ? `'${path15}': ${issue.message}` : issue.message;
|
|
5801
5815
|
});
|
|
5802
5816
|
return {
|
|
5803
5817
|
content: [
|
|
@@ -6065,6 +6079,72 @@ var logger8 = getLogger();
|
|
|
6065
6079
|
function getCursorMcpConfigPath() {
|
|
6066
6080
|
return join(homedir(), ".cursor", "mcp.json");
|
|
6067
6081
|
}
|
|
6082
|
+
function getExpectedExecutablePath(versionDir) {
|
|
6083
|
+
const os4 = platform();
|
|
6084
|
+
switch (os4) {
|
|
6085
|
+
case "darwin":
|
|
6086
|
+
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
6087
|
+
case "win32":
|
|
6088
|
+
return path2.join(versionDir, "MuggleAI.exe");
|
|
6089
|
+
case "linux":
|
|
6090
|
+
return path2.join(versionDir, "MuggleAI");
|
|
6091
|
+
default:
|
|
6092
|
+
throw new Error(`Unsupported platform: ${os4}`);
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
function verifyElectronAppInstallation() {
|
|
6096
|
+
const version = getElectronAppVersion();
|
|
6097
|
+
const versionDir = getElectronAppDir(version);
|
|
6098
|
+
const executablePath = getExpectedExecutablePath(versionDir);
|
|
6099
|
+
const metadataPath = path2.join(versionDir, ".install-metadata.json");
|
|
6100
|
+
const result = {
|
|
6101
|
+
valid: false,
|
|
6102
|
+
versionDir,
|
|
6103
|
+
executablePath,
|
|
6104
|
+
executableExists: false,
|
|
6105
|
+
executableIsFile: false,
|
|
6106
|
+
metadataExists: false,
|
|
6107
|
+
hasPartialArchive: false
|
|
6108
|
+
};
|
|
6109
|
+
if (!fs3.existsSync(versionDir)) {
|
|
6110
|
+
result.errorDetail = "Version directory does not exist";
|
|
6111
|
+
return result;
|
|
6112
|
+
}
|
|
6113
|
+
const archivePatterns = ["MuggleAI-darwin", "MuggleAI-win32", "MuggleAI-linux"];
|
|
6114
|
+
try {
|
|
6115
|
+
const files = fs3.readdirSync(versionDir);
|
|
6116
|
+
for (const file of files) {
|
|
6117
|
+
if (archivePatterns.some((pattern) => file.startsWith(pattern)) && (file.endsWith(".zip") || file.endsWith(".tar.gz"))) {
|
|
6118
|
+
result.hasPartialArchive = true;
|
|
6119
|
+
break;
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
} catch {
|
|
6123
|
+
}
|
|
6124
|
+
result.executableExists = fs3.existsSync(executablePath);
|
|
6125
|
+
if (!result.executableExists) {
|
|
6126
|
+
if (result.hasPartialArchive) {
|
|
6127
|
+
result.errorDetail = "Download incomplete: archive found but not extracted";
|
|
6128
|
+
} else {
|
|
6129
|
+
result.errorDetail = "Executable not found at expected path";
|
|
6130
|
+
}
|
|
6131
|
+
return result;
|
|
6132
|
+
}
|
|
6133
|
+
try {
|
|
6134
|
+
const stats = fs3.statSync(executablePath);
|
|
6135
|
+
result.executableIsFile = stats.isFile();
|
|
6136
|
+
if (!result.executableIsFile) {
|
|
6137
|
+
result.errorDetail = "Executable path exists but is not a file";
|
|
6138
|
+
return result;
|
|
6139
|
+
}
|
|
6140
|
+
} catch {
|
|
6141
|
+
result.errorDetail = "Cannot stat executable (broken symlink?)";
|
|
6142
|
+
return result;
|
|
6143
|
+
}
|
|
6144
|
+
result.metadataExists = fs3.existsSync(metadataPath);
|
|
6145
|
+
result.valid = true;
|
|
6146
|
+
return result;
|
|
6147
|
+
}
|
|
6068
6148
|
function validateCursorMcpConfig() {
|
|
6069
6149
|
const cursorMcpConfigPath = getCursorMcpConfigPath();
|
|
6070
6150
|
if (!existsSync(cursorMcpConfigPath)) {
|
|
@@ -6140,12 +6220,13 @@ function runDiagnostics() {
|
|
|
6140
6220
|
description: existsSync(dataDir) ? `Found at ${dataDir}` : `Not found at ${dataDir}`,
|
|
6141
6221
|
suggestion: "Run 'muggle login' to create the data directory"
|
|
6142
6222
|
});
|
|
6143
|
-
const electronInstalled = isElectronAppInstalled();
|
|
6144
6223
|
const electronVersion = getElectronAppVersion();
|
|
6145
6224
|
const bundledVersion = getBundledElectronAppVersion();
|
|
6146
6225
|
const versionSource = getElectronAppVersionSource();
|
|
6226
|
+
const installVerification = verifyElectronAppInstallation();
|
|
6147
6227
|
let electronDescription;
|
|
6148
|
-
|
|
6228
|
+
let electronSuggestion;
|
|
6229
|
+
if (installVerification.valid) {
|
|
6149
6230
|
electronDescription = `Installed (v${electronVersion})`;
|
|
6150
6231
|
switch (versionSource) {
|
|
6151
6232
|
case "env":
|
|
@@ -6155,16 +6236,30 @@ function runDiagnostics() {
|
|
|
6155
6236
|
electronDescription += ` [overridden from bundled v${bundledVersion}]`;
|
|
6156
6237
|
break;
|
|
6157
6238
|
}
|
|
6239
|
+
if (!installVerification.metadataExists) {
|
|
6240
|
+
electronDescription += " [missing metadata]";
|
|
6241
|
+
}
|
|
6158
6242
|
} else {
|
|
6159
6243
|
electronDescription = `Not installed (expected v${electronVersion})`;
|
|
6244
|
+
if (installVerification.errorDetail) {
|
|
6245
|
+
electronDescription += `
|
|
6246
|
+
\u2514\u2500 ${installVerification.errorDetail}`;
|
|
6247
|
+
electronDescription += `
|
|
6248
|
+
\u2514\u2500 Checked: ${installVerification.versionDir}`;
|
|
6249
|
+
}
|
|
6250
|
+
if (installVerification.hasPartialArchive) {
|
|
6251
|
+
electronSuggestion = "Run 'muggle setup --force' to re-download and extract";
|
|
6252
|
+
} else {
|
|
6253
|
+
electronSuggestion = "Run 'muggle setup' to download the Electron app";
|
|
6254
|
+
}
|
|
6160
6255
|
}
|
|
6161
6256
|
results.push({
|
|
6162
6257
|
name: "Electron App",
|
|
6163
|
-
passed:
|
|
6258
|
+
passed: installVerification.valid,
|
|
6164
6259
|
description: electronDescription,
|
|
6165
|
-
suggestion:
|
|
6260
|
+
suggestion: electronSuggestion
|
|
6166
6261
|
});
|
|
6167
|
-
if (
|
|
6262
|
+
if (installVerification.valid) {
|
|
6168
6263
|
results.push({
|
|
6169
6264
|
name: "Electron App Updates",
|
|
6170
6265
|
passed: true,
|
|
@@ -6266,8 +6361,8 @@ ${title}`, COLORS.bold + COLORS.cyan);
|
|
|
6266
6361
|
function cmd(cmd2) {
|
|
6267
6362
|
return colorize(cmd2, COLORS.green);
|
|
6268
6363
|
}
|
|
6269
|
-
function
|
|
6270
|
-
return colorize(
|
|
6364
|
+
function path12(path15) {
|
|
6365
|
+
return colorize(path15, COLORS.yellow);
|
|
6271
6366
|
}
|
|
6272
6367
|
function getHelpGuidance() {
|
|
6273
6368
|
const lines = [
|
|
@@ -6282,14 +6377,14 @@ function getHelpGuidance() {
|
|
|
6282
6377
|
" assistants with tools to perform automated QA testing of web applications.",
|
|
6283
6378
|
"",
|
|
6284
6379
|
" It supports both:",
|
|
6285
|
-
` ${colorize("\u2022", COLORS.green)} Cloud QA - Test remote production/staging sites`,
|
|
6380
|
+
` ${colorize("\u2022", COLORS.green)} Cloud QA - Test remote production/staging sites with a public URL`,
|
|
6286
6381
|
` ${colorize("\u2022", COLORS.green)} Local QA - Test localhost development servers`,
|
|
6287
6382
|
"",
|
|
6288
6383
|
header("Setup Instructions"),
|
|
6289
6384
|
"",
|
|
6290
6385
|
` ${colorize("Step 1:", COLORS.bold)} Configure your MCP client`,
|
|
6291
6386
|
"",
|
|
6292
|
-
` For ${colorize("Cursor", COLORS.bold)}, edit ${
|
|
6387
|
+
` For ${colorize("Cursor", COLORS.bold)}, edit ${path12("~/.cursor/mcp.json")}:`,
|
|
6293
6388
|
"",
|
|
6294
6389
|
` ${colorize("{", COLORS.dim)}`,
|
|
6295
6390
|
` ${colorize('"mcpServers"', COLORS.yellow)}: {`,
|
|
@@ -6308,7 +6403,6 @@ function getHelpGuidance() {
|
|
|
6308
6403
|
header("CLI Commands"),
|
|
6309
6404
|
"",
|
|
6310
6405
|
` ${colorize("Server Commands:", COLORS.bold)}`,
|
|
6311
|
-
` ${cmd("muggle")} Start MCP server (default)`,
|
|
6312
6406
|
` ${cmd("muggle serve")} Start MCP server with all tools`,
|
|
6313
6407
|
` ${cmd("muggle serve --qa")} Start with Cloud QA tools only`,
|
|
6314
6408
|
` ${cmd("muggle serve --local")} Start with Local QA tools only`,
|
|
@@ -6344,7 +6438,7 @@ function getHelpGuidance() {
|
|
|
6344
6438
|
` 2. ${colorize("Log in", COLORS.bold)} with your Muggle AI account`,
|
|
6345
6439
|
` 3. ${colorize("The tool call continues", COLORS.bold)} with your credentials`,
|
|
6346
6440
|
"",
|
|
6347
|
-
` Credentials are stored in ${
|
|
6441
|
+
` Credentials are stored in ${path12("~/.muggle-ai/credentials.json")}`,
|
|
6348
6442
|
"",
|
|
6349
6443
|
header("Available MCP Tools"),
|
|
6350
6444
|
"",
|
|
@@ -6359,12 +6453,12 @@ function getHelpGuidance() {
|
|
|
6359
6453
|
"",
|
|
6360
6454
|
header("Data Directory"),
|
|
6361
6455
|
"",
|
|
6362
|
-
` All data is stored in ${
|
|
6456
|
+
` All data is stored in ${path12("~/.muggle-ai/")}:`,
|
|
6363
6457
|
"",
|
|
6364
|
-
` ${
|
|
6365
|
-
` ${
|
|
6366
|
-
` ${
|
|
6367
|
-
` ${
|
|
6458
|
+
` ${path12("credentials.json")} Auth credentials (auto-generated)`,
|
|
6459
|
+
` ${path12("projects/")} Local test projects`,
|
|
6460
|
+
` ${path12("sessions/")} Test execution sessions`,
|
|
6461
|
+
` ${path12("electron-app/")} Downloaded Electron app binaries`,
|
|
6368
6462
|
"",
|
|
6369
6463
|
header("Troubleshooting"),
|
|
6370
6464
|
"",
|
|
@@ -6499,6 +6593,9 @@ async function serveCommand(options) {
|
|
|
6499
6593
|
}
|
|
6500
6594
|
}
|
|
6501
6595
|
var logger11 = getLogger();
|
|
6596
|
+
var MAX_RETRY_ATTEMPTS = 3;
|
|
6597
|
+
var RETRY_BASE_DELAY_MS = 2e3;
|
|
6598
|
+
var INSTALL_METADATA_FILE_NAME = ".install-metadata.json";
|
|
6502
6599
|
function getBinaryName() {
|
|
6503
6600
|
const os4 = platform();
|
|
6504
6601
|
const architecture = arch();
|
|
@@ -6515,21 +6612,47 @@ function getBinaryName() {
|
|
|
6515
6612
|
throw new Error(`Unsupported platform: ${os4}`);
|
|
6516
6613
|
}
|
|
6517
6614
|
}
|
|
6615
|
+
function getExpectedExecutablePath2(versionDir) {
|
|
6616
|
+
const os4 = platform();
|
|
6617
|
+
switch (os4) {
|
|
6618
|
+
case "darwin":
|
|
6619
|
+
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
6620
|
+
case "win32":
|
|
6621
|
+
return path2.join(versionDir, "MuggleAI.exe");
|
|
6622
|
+
case "linux":
|
|
6623
|
+
return path2.join(versionDir, "MuggleAI");
|
|
6624
|
+
default:
|
|
6625
|
+
throw new Error(`Unsupported platform: ${os4}`);
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
6518
6628
|
async function extractZip(zipPath, destDir) {
|
|
6519
6629
|
return new Promise((resolve4, reject) => {
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6630
|
+
if (platform() === "win32") {
|
|
6631
|
+
execFile(
|
|
6632
|
+
"powershell",
|
|
6633
|
+
["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
|
|
6634
|
+
(error) => {
|
|
6635
|
+
if (error) {
|
|
6636
|
+
reject(error);
|
|
6637
|
+
} else {
|
|
6638
|
+
resolve4();
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
);
|
|
6642
|
+
} else {
|
|
6643
|
+
execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
|
|
6644
|
+
if (error) {
|
|
6645
|
+
reject(error);
|
|
6646
|
+
} else {
|
|
6647
|
+
resolve4();
|
|
6648
|
+
}
|
|
6649
|
+
});
|
|
6650
|
+
}
|
|
6528
6651
|
});
|
|
6529
6652
|
}
|
|
6530
6653
|
async function extractTarGz(tarPath, destDir) {
|
|
6531
6654
|
return new Promise((resolve4, reject) => {
|
|
6532
|
-
|
|
6655
|
+
execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
|
|
6533
6656
|
if (error) {
|
|
6534
6657
|
reject(error);
|
|
6535
6658
|
} else {
|
|
@@ -6538,10 +6661,66 @@ async function extractTarGz(tarPath, destDir) {
|
|
|
6538
6661
|
});
|
|
6539
6662
|
});
|
|
6540
6663
|
}
|
|
6664
|
+
function sleep2(ms) {
|
|
6665
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
6666
|
+
}
|
|
6667
|
+
async function downloadWithRetry(downloadUrl, destPath) {
|
|
6668
|
+
let lastError = null;
|
|
6669
|
+
for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
|
|
6670
|
+
try {
|
|
6671
|
+
if (attempt > 1) {
|
|
6672
|
+
const delayMs = RETRY_BASE_DELAY_MS * Math.pow(2, attempt - 2);
|
|
6673
|
+
console.log(`Retry attempt ${attempt}/${MAX_RETRY_ATTEMPTS} after ${delayMs}ms delay...`);
|
|
6674
|
+
await sleep2(delayMs);
|
|
6675
|
+
}
|
|
6676
|
+
const response = await fetch(downloadUrl);
|
|
6677
|
+
if (!response.ok) {
|
|
6678
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
6679
|
+
}
|
|
6680
|
+
if (!response.body) {
|
|
6681
|
+
throw new Error("No response body received");
|
|
6682
|
+
}
|
|
6683
|
+
const fileStream = createWriteStream(destPath);
|
|
6684
|
+
await pipeline(response.body, fileStream);
|
|
6685
|
+
return true;
|
|
6686
|
+
} catch (error) {
|
|
6687
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
6688
|
+
console.error(`Download attempt ${attempt} failed: ${lastError.message}`);
|
|
6689
|
+
if (existsSync(destPath)) {
|
|
6690
|
+
rmSync(destPath, { force: true });
|
|
6691
|
+
}
|
|
6692
|
+
}
|
|
6693
|
+
}
|
|
6694
|
+
if (lastError) {
|
|
6695
|
+
throw new Error(`Download failed after ${MAX_RETRY_ATTEMPTS} attempts: ${lastError.message}`);
|
|
6696
|
+
}
|
|
6697
|
+
return false;
|
|
6698
|
+
}
|
|
6699
|
+
function writeInstallMetadata(params) {
|
|
6700
|
+
const metadata = {
|
|
6701
|
+
version: params.version,
|
|
6702
|
+
binaryName: params.binaryName,
|
|
6703
|
+
platformKey: params.platformKey,
|
|
6704
|
+
executableChecksum: params.executableChecksum,
|
|
6705
|
+
expectedArchiveChecksum: params.expectedArchiveChecksum,
|
|
6706
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6707
|
+
};
|
|
6708
|
+
writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
6709
|
+
`, "utf-8");
|
|
6710
|
+
}
|
|
6711
|
+
function cleanupFailedInstall(versionDir) {
|
|
6712
|
+
if (existsSync(versionDir)) {
|
|
6713
|
+
try {
|
|
6714
|
+
rmSync(versionDir, { recursive: true, force: true });
|
|
6715
|
+
} catch {
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
}
|
|
6541
6719
|
async function setupCommand(options) {
|
|
6542
6720
|
const version = getElectronAppVersion();
|
|
6543
6721
|
const baseUrl = getDownloadBaseUrl();
|
|
6544
6722
|
const versionDir = getElectronAppDir(version);
|
|
6723
|
+
const platformKey = getPlatformKey();
|
|
6545
6724
|
if (!options.force && isElectronAppInstalled()) {
|
|
6546
6725
|
console.log(`Electron app v${version} is already installed at ${versionDir}`);
|
|
6547
6726
|
console.log("Use --force to re-download.");
|
|
@@ -6556,22 +6735,14 @@ async function setupCommand(options) {
|
|
|
6556
6735
|
rmSync(versionDir, { recursive: true, force: true });
|
|
6557
6736
|
}
|
|
6558
6737
|
mkdirSync(versionDir, { recursive: true });
|
|
6559
|
-
const
|
|
6560
|
-
|
|
6561
|
-
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
6562
|
-
}
|
|
6563
|
-
const tempFile = `${versionDir}/${binaryName}`;
|
|
6564
|
-
const fileStream = createWriteStream(tempFile);
|
|
6565
|
-
if (!response.body) {
|
|
6566
|
-
throw new Error("No response body");
|
|
6567
|
-
}
|
|
6568
|
-
await pipeline(response.body, fileStream);
|
|
6738
|
+
const tempFile = path2.join(versionDir, binaryName);
|
|
6739
|
+
await downloadWithRetry(downloadUrl, tempFile);
|
|
6569
6740
|
console.log("Download complete, verifying checksum...");
|
|
6570
6741
|
const checksums = getElectronAppChecksums();
|
|
6571
6742
|
const expectedChecksum = getChecksumForPlatform(checksums);
|
|
6572
6743
|
const checksumResult = await verifyFileChecksum(tempFile, expectedChecksum);
|
|
6573
6744
|
if (!checksumResult.valid && expectedChecksum) {
|
|
6574
|
-
|
|
6745
|
+
cleanupFailedInstall(versionDir);
|
|
6575
6746
|
throw new Error(
|
|
6576
6747
|
`Checksum verification failed!
|
|
6577
6748
|
Expected: ${checksumResult.expected}
|
|
@@ -6590,6 +6761,25 @@ The downloaded file may be corrupted or tampered with.`
|
|
|
6590
6761
|
} else if (binaryName.endsWith(".tar.gz")) {
|
|
6591
6762
|
await extractTarGz(tempFile, versionDir);
|
|
6592
6763
|
}
|
|
6764
|
+
const executablePath = getExpectedExecutablePath2(versionDir);
|
|
6765
|
+
if (!existsSync(executablePath)) {
|
|
6766
|
+
cleanupFailedInstall(versionDir);
|
|
6767
|
+
throw new Error(
|
|
6768
|
+
`Extraction failed: executable not found at expected path.
|
|
6769
|
+
Expected: ${executablePath}
|
|
6770
|
+
The archive may be corrupted or in an unexpected format.`
|
|
6771
|
+
);
|
|
6772
|
+
}
|
|
6773
|
+
const executableChecksum = await calculateFileChecksum(executablePath);
|
|
6774
|
+
const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME);
|
|
6775
|
+
writeInstallMetadata({
|
|
6776
|
+
metadataPath,
|
|
6777
|
+
version,
|
|
6778
|
+
binaryName,
|
|
6779
|
+
platformKey,
|
|
6780
|
+
executableChecksum,
|
|
6781
|
+
expectedArchiveChecksum: expectedChecksum
|
|
6782
|
+
});
|
|
6593
6783
|
rmSync(tempFile, { force: true });
|
|
6594
6784
|
console.log(`Electron app installed to ${versionDir}`);
|
|
6595
6785
|
logger11.info("Setup complete", { version, path: versionDir });
|
|
@@ -6602,6 +6792,7 @@ The downloaded file may be corrupted or tampered with.`
|
|
|
6602
6792
|
}
|
|
6603
6793
|
var logger12 = getLogger();
|
|
6604
6794
|
var GITHUB_RELEASES_API = "https://api.github.com/repos/multiplex-ai/muggle-ai-works/releases";
|
|
6795
|
+
var INSTALL_METADATA_FILE_NAME2 = ".install-metadata.json";
|
|
6605
6796
|
var VERSION_OVERRIDE_FILE2 = "electron-app-version-override.json";
|
|
6606
6797
|
function getBinaryName2() {
|
|
6607
6798
|
const os4 = platform();
|
|
@@ -6704,21 +6895,59 @@ function compareVersions2(a, b) {
|
|
|
6704
6895
|
}
|
|
6705
6896
|
return 0;
|
|
6706
6897
|
}
|
|
6898
|
+
function getExpectedExecutablePath3(versionDir) {
|
|
6899
|
+
const os4 = platform();
|
|
6900
|
+
switch (os4) {
|
|
6901
|
+
case "darwin":
|
|
6902
|
+
return path2.join(versionDir, "MuggleAI.app", "Contents", "MacOS", "MuggleAI");
|
|
6903
|
+
case "win32":
|
|
6904
|
+
return path2.join(versionDir, "MuggleAI.exe");
|
|
6905
|
+
case "linux":
|
|
6906
|
+
return path2.join(versionDir, "MuggleAI");
|
|
6907
|
+
default:
|
|
6908
|
+
throw new Error(`Unsupported platform: ${os4}`);
|
|
6909
|
+
}
|
|
6910
|
+
}
|
|
6911
|
+
function writeInstallMetadata2(params) {
|
|
6912
|
+
const metadata = {
|
|
6913
|
+
version: params.version,
|
|
6914
|
+
binaryName: params.binaryName,
|
|
6915
|
+
platformKey: params.platformKey,
|
|
6916
|
+
executableChecksum: params.executableChecksum,
|
|
6917
|
+
expectedArchiveChecksum: params.expectedArchiveChecksum,
|
|
6918
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6919
|
+
};
|
|
6920
|
+
writeFileSync(params.metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
6921
|
+
`, "utf-8");
|
|
6922
|
+
}
|
|
6707
6923
|
async function extractZip2(zipPath, destDir) {
|
|
6708
6924
|
return new Promise((resolve4, reject) => {
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6925
|
+
if (platform() === "win32") {
|
|
6926
|
+
execFile(
|
|
6927
|
+
"powershell",
|
|
6928
|
+
["-command", `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`],
|
|
6929
|
+
(error) => {
|
|
6930
|
+
if (error) {
|
|
6931
|
+
reject(error);
|
|
6932
|
+
} else {
|
|
6933
|
+
resolve4();
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
);
|
|
6937
|
+
} else {
|
|
6938
|
+
execFile("unzip", ["-o", zipPath, "-d", destDir], (error) => {
|
|
6939
|
+
if (error) {
|
|
6940
|
+
reject(error);
|
|
6941
|
+
} else {
|
|
6942
|
+
resolve4();
|
|
6943
|
+
}
|
|
6944
|
+
});
|
|
6945
|
+
}
|
|
6717
6946
|
});
|
|
6718
6947
|
}
|
|
6719
6948
|
async function extractTarGz2(tarPath, destDir) {
|
|
6720
6949
|
return new Promise((resolve4, reject) => {
|
|
6721
|
-
|
|
6950
|
+
execFile("tar", ["-xzf", tarPath, "-C", destDir], (error) => {
|
|
6722
6951
|
if (error) {
|
|
6723
6952
|
reject(error);
|
|
6724
6953
|
} else {
|
|
@@ -6776,6 +7005,7 @@ async function fetchChecksumFromRelease(version) {
|
|
|
6776
7005
|
async function downloadAndInstall(version, downloadUrl, checksum) {
|
|
6777
7006
|
const versionDir = getElectronAppDir(version);
|
|
6778
7007
|
const binaryName = getBinaryName2();
|
|
7008
|
+
const platformKey = getPlatformKey();
|
|
6779
7009
|
console.log(`Downloading Muggle Test Electron app v${version}...`);
|
|
6780
7010
|
console.log(`URL: ${downloadUrl}`);
|
|
6781
7011
|
if (existsSync(versionDir)) {
|
|
@@ -6818,6 +7048,25 @@ The downloaded file may be corrupted or tampered with.`
|
|
|
6818
7048
|
} else if (binaryName.endsWith(".tar.gz")) {
|
|
6819
7049
|
await extractTarGz2(tempFile, versionDir);
|
|
6820
7050
|
}
|
|
7051
|
+
const executablePath = getExpectedExecutablePath3(versionDir);
|
|
7052
|
+
if (!existsSync(executablePath)) {
|
|
7053
|
+
rmSync(versionDir, { recursive: true, force: true });
|
|
7054
|
+
throw new Error(
|
|
7055
|
+
`Extraction failed: executable not found at expected path.
|
|
7056
|
+
Expected: ${executablePath}
|
|
7057
|
+
The archive may be corrupted or in an unexpected format.`
|
|
7058
|
+
);
|
|
7059
|
+
}
|
|
7060
|
+
const executableChecksum = await calculateFileChecksum(executablePath);
|
|
7061
|
+
const metadataPath = path2.join(versionDir, INSTALL_METADATA_FILE_NAME2);
|
|
7062
|
+
writeInstallMetadata2({
|
|
7063
|
+
metadataPath,
|
|
7064
|
+
version,
|
|
7065
|
+
binaryName,
|
|
7066
|
+
platformKey,
|
|
7067
|
+
executableChecksum,
|
|
7068
|
+
expectedArchiveChecksum: expectedChecksum || ""
|
|
7069
|
+
});
|
|
6821
7070
|
rmSync(tempFile, { force: true });
|
|
6822
7071
|
saveVersionOverride(version);
|
|
6823
7072
|
console.log(`Electron app v${version} installed to ${versionDir}`);
|
|
@@ -6900,7 +7149,11 @@ function createProgram() {
|
|
|
6900
7149
|
program.command("logout").description("Clear stored credentials").action(logoutCommand);
|
|
6901
7150
|
program.command("status").description("Show authentication status").action(statusCommand);
|
|
6902
7151
|
program.action(() => {
|
|
6903
|
-
|
|
7152
|
+
helpCommand();
|
|
7153
|
+
});
|
|
7154
|
+
program.on("command:*", () => {
|
|
7155
|
+
helpCommand();
|
|
7156
|
+
process.exit(1);
|
|
6904
7157
|
});
|
|
6905
7158
|
return program;
|
|
6906
7159
|
}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { src_exports2 as commands, createChildLogger, createUnifiedMcpServer, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, qa_exports as qa, server_exports as server, src_exports as shared } from './chunk-
|
|
1
|
+
export { src_exports2 as commands, createChildLogger, createUnifiedMcpServer, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, qa_exports as qa, server_exports as server, src_exports as shared } from './chunk-BQZQDOXI.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Run real-browser QA tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Ship quality products with AI-powered QA that validates your app's user experience — from Claude Code and Cursor to PR.",
|
|
5
|
-
"version": "4.0.
|
|
5
|
+
"version": "4.0.3",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: muggle-test-feature-local
|
|
3
|
-
description:
|
|
3
|
+
description: Run a real-browser QA test against localhost to verify a feature works correctly — signup flows, checkout, form validation, UI interactions, or any user-facing behavior. Launches a browser that executes test steps and captures screenshots. Use this skill whenever the user asks to test, QA, validate, or verify their web app, UI changes, user flows, or frontend behavior on localhost or a dev server — even if they don't mention 'muggle' or 'QA' explicitly.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Muggle Test Feature Local
|
|
@@ -12,54 +12,111 @@ Run end-to-end feature testing from UI against a local URL:
|
|
|
12
12
|
|
|
13
13
|
## Workflow
|
|
14
14
|
|
|
15
|
-
1.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
15
|
+
### 1. Auth
|
|
16
|
+
|
|
17
|
+
- `muggle-remote-auth-status`
|
|
18
|
+
- If needed: `muggle-remote-auth-login` + `muggle-remote-auth-poll`
|
|
19
|
+
|
|
20
|
+
### 2. Select project, use case, and test case
|
|
21
|
+
|
|
22
|
+
- Explicitly ask user to select each target to proceed.
|
|
23
|
+
- `muggle-remote-project-list`
|
|
24
|
+
- `muggle-remote-use-case-list`
|
|
25
|
+
- `muggle-remote-test-case-list-by-use-case`
|
|
26
|
+
|
|
27
|
+
### 3. Resolve local URL
|
|
28
|
+
|
|
29
|
+
- Use the URL provided by the user.
|
|
30
|
+
- If missing, ask explicitly (do not guess).
|
|
31
|
+
- Inform user the local URL does not affect the project's remote test.
|
|
32
|
+
|
|
33
|
+
### 4. Check for existing scripts and ask user to choose
|
|
34
|
+
|
|
35
|
+
Check BOTH cloud and local scripts to determine what's available:
|
|
36
|
+
|
|
37
|
+
1. **Check cloud scripts:** `muggle-remote-test-script-list` filtered by projectId
|
|
38
|
+
2. **Check local scripts:** `muggle-local-test-script-list` filtered by projectId
|
|
39
|
+
|
|
40
|
+
**Decision logic:**
|
|
41
|
+
|
|
42
|
+
| Cloud Script | Local Script (status: published/generated) | Action |
|
|
43
|
+
|--------------|---------------------------------------------|--------|
|
|
44
|
+
| Exists + ACTIVE | Exists | Ask user: "Replay existing script" or "Regenerate from scratch"? |
|
|
45
|
+
| Exists + ACTIVE | Not found | Sync from cloud first, then ask user |
|
|
46
|
+
| Not found | Exists | Ask user: "Replay local script" or "Regenerate"? |
|
|
47
|
+
| Not found | Not found | Default to generation (no need to ask) |
|
|
48
|
+
|
|
49
|
+
**When asking user, show:**
|
|
50
|
+
- Script name and ID
|
|
51
|
+
- When it was created/updated
|
|
52
|
+
- Number of steps
|
|
53
|
+
- Last run status if available
|
|
54
|
+
|
|
55
|
+
### 5. Prepare for execution
|
|
56
|
+
|
|
57
|
+
**For Replay:**
|
|
58
|
+
|
|
59
|
+
Local scripts contain the complete `actionScript` with element labels required for replay. Remote scripts only contain metadata.
|
|
60
|
+
|
|
61
|
+
1. Use `muggle-local-test-script-get` with `testScriptId` to fetch the FULL script including actionScript
|
|
62
|
+
2. The returned script includes all steps with `operation.label` paths needed for element location
|
|
63
|
+
3. Pass this complete script to `muggle-local-execute-replay`
|
|
64
|
+
|
|
65
|
+
**IMPORTANT:** Do NOT manually construct or simplify the actionScript. The electron app requires the complete script with all `label` paths intact to locate page elements during replay.
|
|
66
|
+
|
|
67
|
+
**For Generation:**
|
|
68
|
+
|
|
69
|
+
1. `muggle-remote-test-case-get` to fetch test case details
|
|
70
|
+
2. `muggle-local-execute-test-generation` with the test case
|
|
71
|
+
|
|
72
|
+
### 6. Approval requirement
|
|
73
|
+
|
|
74
|
+
- Before execution, get explicit user approval for launching Electron app.
|
|
75
|
+
- Show what will be executed (replay vs generation, test case name, URL).
|
|
76
|
+
- Only then set `approveElectronAppLaunch: true`.
|
|
77
|
+
|
|
78
|
+
### 7. Execute
|
|
79
|
+
|
|
80
|
+
**Replay:**
|
|
81
|
+
```
|
|
82
|
+
muggle-local-execute-replay with:
|
|
83
|
+
- testScript: (full script from muggle-local-test-script-get)
|
|
84
|
+
- localUrl: user-provided localhost URL
|
|
85
|
+
- approveElectronAppLaunch: true
|
|
86
|
+
- showUi: true (optional, lets user watch)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Generation:**
|
|
90
|
+
```
|
|
91
|
+
muggle-local-execute-test-generation with:
|
|
92
|
+
- testCase: (from muggle-remote-test-case-get)
|
|
93
|
+
- localUrl: user-provided localhost URL
|
|
94
|
+
- approveElectronAppLaunch: true
|
|
95
|
+
- showUi: true (optional)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 8. Publish generation results (generation only)
|
|
99
|
+
|
|
100
|
+
- Use `muggle-local-publish-test-script` after successful generation.
|
|
101
|
+
- This uploads the script to cloud so it can be replayed later.
|
|
102
|
+
- Return the remote URL for user to view the result.
|
|
103
|
+
|
|
104
|
+
### 9. Report results
|
|
105
|
+
|
|
106
|
+
- `muggle-local-run-result-get` with returned runId.
|
|
107
|
+
- Report:
|
|
108
|
+
- status (passed/failed)
|
|
109
|
+
- duration
|
|
110
|
+
- pass/fail summary
|
|
111
|
+
- steps summary (which steps passed/failed)
|
|
112
|
+
- artifacts path (screenshots location)
|
|
113
|
+
- script detail view URL
|
|
59
114
|
|
|
60
115
|
## Guardrails
|
|
61
116
|
|
|
62
117
|
- Do not silently skip auth.
|
|
63
|
-
- Do not silently skip
|
|
118
|
+
- Do not silently skip asking user when a replayable script exists.
|
|
64
119
|
- Do not launch Electron without explicit approval.
|
|
65
120
|
- Do not hide failing run details; include error and artifacts path.
|
|
121
|
+
- Do not simplify or reconstruct actionScript for replay; use the complete script from `muggle-local-test-script-get`.
|
|
122
|
+
- Always check local scripts before defaulting to generation.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muggleai/works",
|
|
3
3
|
"mcpName": "io.github.multiplex-ai/muggle",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.3",
|
|
5
5
|
"description": "Ship quality products with AI-powered QA that validates your app's user experience — from Claude Code and Cursor to PR.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Run real-browser QA tests on your web app from any AI coding agent. Generate test scripts from plain English, replay them on localhost, capture screenshots, and validate user flows like signup, checkout, and dashboards. Works across Claude Code, Cursor, Codex, and Windsurf.",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Ship quality products with AI-powered QA that validates your app's user experience — from Claude Code and Cursor to PR.",
|
|
5
|
-
"version": "4.0.
|
|
5
|
+
"version": "4.0.3",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: muggle-test-feature-local
|
|
3
|
-
description:
|
|
3
|
+
description: Run a real-browser QA test against localhost to verify a feature works correctly — signup flows, checkout, form validation, UI interactions, or any user-facing behavior. Launches a browser that executes test steps and captures screenshots. Use this skill whenever the user asks to test, QA, validate, or verify their web app, UI changes, user flows, or frontend behavior on localhost or a dev server — even if they don't mention 'muggle' or 'QA' explicitly.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Muggle Test Feature Local
|
|
@@ -12,54 +12,111 @@ Run end-to-end feature testing from UI against a local URL:
|
|
|
12
12
|
|
|
13
13
|
## Workflow
|
|
14
14
|
|
|
15
|
-
1.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
15
|
+
### 1. Auth
|
|
16
|
+
|
|
17
|
+
- `muggle-remote-auth-status`
|
|
18
|
+
- If needed: `muggle-remote-auth-login` + `muggle-remote-auth-poll`
|
|
19
|
+
|
|
20
|
+
### 2. Select project, use case, and test case
|
|
21
|
+
|
|
22
|
+
- Explicitly ask user to select each target to proceed.
|
|
23
|
+
- `muggle-remote-project-list`
|
|
24
|
+
- `muggle-remote-use-case-list`
|
|
25
|
+
- `muggle-remote-test-case-list-by-use-case`
|
|
26
|
+
|
|
27
|
+
### 3. Resolve local URL
|
|
28
|
+
|
|
29
|
+
- Use the URL provided by the user.
|
|
30
|
+
- If missing, ask explicitly (do not guess).
|
|
31
|
+
- Inform user the local URL does not affect the project's remote test.
|
|
32
|
+
|
|
33
|
+
### 4. Check for existing scripts and ask user to choose
|
|
34
|
+
|
|
35
|
+
Check BOTH cloud and local scripts to determine what's available:
|
|
36
|
+
|
|
37
|
+
1. **Check cloud scripts:** `muggle-remote-test-script-list` filtered by projectId
|
|
38
|
+
2. **Check local scripts:** `muggle-local-test-script-list` filtered by projectId
|
|
39
|
+
|
|
40
|
+
**Decision logic:**
|
|
41
|
+
|
|
42
|
+
| Cloud Script | Local Script (status: published/generated) | Action |
|
|
43
|
+
|--------------|---------------------------------------------|--------|
|
|
44
|
+
| Exists + ACTIVE | Exists | Ask user: "Replay existing script" or "Regenerate from scratch"? |
|
|
45
|
+
| Exists + ACTIVE | Not found | Sync from cloud first, then ask user |
|
|
46
|
+
| Not found | Exists | Ask user: "Replay local script" or "Regenerate"? |
|
|
47
|
+
| Not found | Not found | Default to generation (no need to ask) |
|
|
48
|
+
|
|
49
|
+
**When asking user, show:**
|
|
50
|
+
- Script name and ID
|
|
51
|
+
- When it was created/updated
|
|
52
|
+
- Number of steps
|
|
53
|
+
- Last run status if available
|
|
54
|
+
|
|
55
|
+
### 5. Prepare for execution
|
|
56
|
+
|
|
57
|
+
**For Replay:**
|
|
58
|
+
|
|
59
|
+
Local scripts contain the complete `actionScript` with element labels required for replay. Remote scripts only contain metadata.
|
|
60
|
+
|
|
61
|
+
1. Use `muggle-local-test-script-get` with `testScriptId` to fetch the FULL script including actionScript
|
|
62
|
+
2. The returned script includes all steps with `operation.label` paths needed for element location
|
|
63
|
+
3. Pass this complete script to `muggle-local-execute-replay`
|
|
64
|
+
|
|
65
|
+
**IMPORTANT:** Do NOT manually construct or simplify the actionScript. The electron app requires the complete script with all `label` paths intact to locate page elements during replay.
|
|
66
|
+
|
|
67
|
+
**For Generation:**
|
|
68
|
+
|
|
69
|
+
1. `muggle-remote-test-case-get` to fetch test case details
|
|
70
|
+
2. `muggle-local-execute-test-generation` with the test case
|
|
71
|
+
|
|
72
|
+
### 6. Approval requirement
|
|
73
|
+
|
|
74
|
+
- Before execution, get explicit user approval for launching Electron app.
|
|
75
|
+
- Show what will be executed (replay vs generation, test case name, URL).
|
|
76
|
+
- Only then set `approveElectronAppLaunch: true`.
|
|
77
|
+
|
|
78
|
+
### 7. Execute
|
|
79
|
+
|
|
80
|
+
**Replay:**
|
|
81
|
+
```
|
|
82
|
+
muggle-local-execute-replay with:
|
|
83
|
+
- testScript: (full script from muggle-local-test-script-get)
|
|
84
|
+
- localUrl: user-provided localhost URL
|
|
85
|
+
- approveElectronAppLaunch: true
|
|
86
|
+
- showUi: true (optional, lets user watch)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Generation:**
|
|
90
|
+
```
|
|
91
|
+
muggle-local-execute-test-generation with:
|
|
92
|
+
- testCase: (from muggle-remote-test-case-get)
|
|
93
|
+
- localUrl: user-provided localhost URL
|
|
94
|
+
- approveElectronAppLaunch: true
|
|
95
|
+
- showUi: true (optional)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 8. Publish generation results (generation only)
|
|
99
|
+
|
|
100
|
+
- Use `muggle-local-publish-test-script` after successful generation.
|
|
101
|
+
- This uploads the script to cloud so it can be replayed later.
|
|
102
|
+
- Return the remote URL for user to view the result.
|
|
103
|
+
|
|
104
|
+
### 9. Report results
|
|
105
|
+
|
|
106
|
+
- `muggle-local-run-result-get` with returned runId.
|
|
107
|
+
- Report:
|
|
108
|
+
- status (passed/failed)
|
|
109
|
+
- duration
|
|
110
|
+
- pass/fail summary
|
|
111
|
+
- steps summary (which steps passed/failed)
|
|
112
|
+
- artifacts path (screenshots location)
|
|
113
|
+
- script detail view URL
|
|
59
114
|
|
|
60
115
|
## Guardrails
|
|
61
116
|
|
|
62
117
|
- Do not silently skip auth.
|
|
63
|
-
- Do not silently skip
|
|
118
|
+
- Do not silently skip asking user when a replayable script exists.
|
|
64
119
|
- Do not launch Electron without explicit approval.
|
|
65
120
|
- Do not hide failing run details; include error and artifacts path.
|
|
121
|
+
- Do not simplify or reconstruct actionScript for replay; use the complete script from `muggle-local-test-script-get`.
|
|
122
|
+
- Always check local scripts before defaulting to generation.
|