muagqa 1.4.0 → 1.4.2
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/package.json +1 -1
- package/src/index.js +29 -49
- package/src/recordMode.js +20 -0
- package/src/templateBuilder.js +66 -0
- package/src/templateMode.js +35 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
import { exec } from "child_process";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
import { promisify } from "util";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
5
|
import chalk from "chalk";
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import run from "./runner.js";
|
|
6
|
+
import { startRecording } from "./recordMode.js";
|
|
7
|
+
import { generateTemplateTests } from "./templateMode.js";
|
|
10
8
|
|
|
11
9
|
const execAsync = promisify(exec);
|
|
12
10
|
const require = createRequire(import.meta.url);
|
|
@@ -36,62 +34,44 @@ async function checkForUpdate({ force = false } = {}) {
|
|
|
36
34
|
}
|
|
37
35
|
}
|
|
38
36
|
|
|
37
|
+
function extractToken(args) {
|
|
38
|
+
if (args[0] === "pull" && args[1]) {
|
|
39
|
+
return args[1];
|
|
40
|
+
}
|
|
41
|
+
return args.find(arg => !arg.startsWith("--"));
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
async function startCLI() {
|
|
40
|
-
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
const isTemplate = args.includes("--template");
|
|
47
|
+
const isRecord = args.includes("--record");
|
|
48
|
+
const wantsVersion = args.includes("--version") || args.includes("-v");
|
|
49
|
+
const wantsCheckUpdate = args.includes("--check-update");
|
|
50
|
+
const token = extractToken(args);
|
|
41
51
|
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"Pull test cases and generate tests",
|
|
47
|
-
y => {
|
|
48
|
-
return y
|
|
49
|
-
.positional("token", {
|
|
50
|
-
type: "string",
|
|
51
|
-
describe: "One-time session token"
|
|
52
|
-
})
|
|
53
|
-
.option("mode", {
|
|
54
|
-
choices: ["record", "template"],
|
|
55
|
-
default: "record",
|
|
56
|
-
describe: "Execution mode"
|
|
57
|
-
});
|
|
58
|
-
},
|
|
59
|
-
async args => {
|
|
60
|
-
ranCommand = true;
|
|
61
|
-
checkForUpdate();
|
|
62
|
-
await run(args.token, args.mode);
|
|
63
|
-
}
|
|
64
|
-
)
|
|
65
|
-
.option("check-update", {
|
|
66
|
-
type: "boolean",
|
|
67
|
-
describe: "Check for updates"
|
|
68
|
-
})
|
|
69
|
-
.version(`muagqa v${pkg.version}`)
|
|
70
|
-
.alias("version", "v")
|
|
71
|
-
.help()
|
|
72
|
-
.parseAsync();
|
|
52
|
+
if (wantsVersion) {
|
|
53
|
+
console.log(`muagqa v${pkg.version}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
73
56
|
|
|
74
|
-
if (
|
|
57
|
+
if (wantsCheckUpdate) {
|
|
75
58
|
await checkForUpdate({ force: true });
|
|
76
59
|
return;
|
|
77
60
|
}
|
|
78
61
|
|
|
79
|
-
if (
|
|
62
|
+
if (isTemplate) {
|
|
63
|
+
checkForUpdate();
|
|
64
|
+
await generateTemplateTests(token);
|
|
80
65
|
return;
|
|
81
66
|
}
|
|
82
67
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
name: "token",
|
|
89
|
-
message: "Enter your one-time session token:",
|
|
90
|
-
validate: v => v.trim() !== "" || "Token is required."
|
|
91
|
-
}
|
|
92
|
-
]);
|
|
68
|
+
if (isRecord) {
|
|
69
|
+
checkForUpdate();
|
|
70
|
+
await startRecording(token);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
93
73
|
|
|
94
|
-
|
|
74
|
+
console.log("Please specify --template or --record mode");
|
|
95
75
|
}
|
|
96
76
|
|
|
97
77
|
startCLI();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import run from "./runner.js";
|
|
3
|
+
|
|
4
|
+
export async function startRecording(sessionToken) {
|
|
5
|
+
let token = sessionToken || process.env.MUAGQA_SESSION;
|
|
6
|
+
|
|
7
|
+
if (!token) {
|
|
8
|
+
const answer = await inquirer.prompt([
|
|
9
|
+
{
|
|
10
|
+
type: "input",
|
|
11
|
+
name: "token",
|
|
12
|
+
message: "Enter your one-time session token:",
|
|
13
|
+
validate: v => v.trim() !== "" || "Token is required."
|
|
14
|
+
}
|
|
15
|
+
]);
|
|
16
|
+
token = answer.token;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
await run(token, "record");
|
|
20
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
function formatArg(value) {
|
|
2
|
+
if (Array.isArray(value)) {
|
|
3
|
+
return value.map(formatArg).join(", ");
|
|
4
|
+
}
|
|
5
|
+
if (typeof value === "string") {
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
return JSON.stringify(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatArgs(args) {
|
|
12
|
+
if (Array.isArray(args)) {
|
|
13
|
+
return args.map(formatArg).join(", ");
|
|
14
|
+
}
|
|
15
|
+
if (args === undefined) {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
return formatArg(args);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildTemplate(testCase) {
|
|
22
|
+
const steps = Array.isArray(testCase.steps) ? testCase.steps : [];
|
|
23
|
+
const lines = steps.map(step => {
|
|
24
|
+
const action = step.action || "click";
|
|
25
|
+
const args = formatArgs(step.args);
|
|
26
|
+
const argsText = args ? `(${args})` : "()";
|
|
27
|
+
return ` await page.${action}${argsText};`;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const title = testCase.title || "Generated test";
|
|
31
|
+
const description = testCase.description || "";
|
|
32
|
+
const expected = testCase.expected || "";
|
|
33
|
+
const startUrl =
|
|
34
|
+
testCase.startUrl || testCase.url || testCase.start_url || "";
|
|
35
|
+
|
|
36
|
+
const headerLines = [];
|
|
37
|
+
if (description) {
|
|
38
|
+
headerLines.push(`// Description: ${description}`);
|
|
39
|
+
}
|
|
40
|
+
if (expected) {
|
|
41
|
+
headerLines.push(`// Expected: ${expected}`);
|
|
42
|
+
}
|
|
43
|
+
if (testCase.id) {
|
|
44
|
+
headerLines.push(`// Test case ID: ${testCase.id}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const header = headerLines.length ? `${headerLines.join("\n")}\n` : "";
|
|
48
|
+
|
|
49
|
+
if (lines.length === 0) {
|
|
50
|
+
lines.push(" // TODO: add steps for this test case.");
|
|
51
|
+
if (startUrl) {
|
|
52
|
+
lines.push(` await page.goto("${startUrl}");`);
|
|
53
|
+
} else {
|
|
54
|
+
lines.push(" // await page.goto(\"https://example.com\");");
|
|
55
|
+
}
|
|
56
|
+
lines.push(" // Example:");
|
|
57
|
+
lines.push(" // await page.getByRole(\"button\", { name: \"Sign in\" }).click();");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return `import { test } from "@playwright/test";
|
|
61
|
+
|
|
62
|
+
${header}test("${title}", async ({ page }) => {
|
|
63
|
+
${lines.join("\n")}
|
|
64
|
+
});
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { validateSessionToken } from "./api.js";
|
|
5
|
+
import { buildTemplate } from "./templateBuilder.js";
|
|
6
|
+
|
|
7
|
+
export async function generateTemplateTests(sessionToken) {
|
|
8
|
+
const token = sessionToken || process.env.MUAGQA_SESSION;
|
|
9
|
+
|
|
10
|
+
if (!token) {
|
|
11
|
+
console.error("Missing session token. Set MUAGQA_SESSION or pass a token.");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = await validateSessionToken(token);
|
|
16
|
+
if (!result.success) {
|
|
17
|
+
console.error("Unable to fetch test flows — invalid token?");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const testCases = result.testCases || [];
|
|
22
|
+
|
|
23
|
+
const outputDir = path.resolve(process.cwd(), "muagqa-tests");
|
|
24
|
+
if (!fs.existsSync(outputDir)) {
|
|
25
|
+
fs.mkdirSync(outputDir);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const tc of testCases) {
|
|
29
|
+
const template = buildTemplate(tc);
|
|
30
|
+
const slug = tc.slug || tc.id || `test-${Date.now()}`;
|
|
31
|
+
fs.writeFileSync(path.join(outputDir, `${slug}.spec.js`), template, "utf8");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.green(`Generated ${testCases.length} test files in /muagqa-tests`));
|
|
35
|
+
}
|