muagqa 1.3.0 → 1.4.1
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/fileManager.js +4 -0
- package/src/index.js +28 -16
- package/src/recordMode.js +20 -0
- package/src/runner.js +33 -4
- package/src/templateBuilder.js +38 -0
- package/src/templateGenerator.js +28 -0
- package/src/templateMode.js +38 -0
package/package.json
CHANGED
package/src/fileManager.js
CHANGED
package/src/index.js
CHANGED
|
@@ -2,9 +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
|
|
6
|
+
import { startRecording } from "./recordMode.js";
|
|
7
|
+
import { generateTemplateTests } from "./templateMode.js";
|
|
8
8
|
|
|
9
9
|
const execAsync = promisify(exec);
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
@@ -34,32 +34,44 @@ async function checkForUpdate({ force = false } = {}) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
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
|
+
|
|
37
44
|
async function startCLI() {
|
|
38
|
-
const args =
|
|
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);
|
|
39
51
|
|
|
40
|
-
if (
|
|
52
|
+
if (wantsVersion) {
|
|
41
53
|
console.log(`muagqa v${pkg.version}`);
|
|
42
54
|
return;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
if (
|
|
57
|
+
if (wantsCheckUpdate) {
|
|
46
58
|
await checkForUpdate({ force: true });
|
|
47
59
|
return;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
if (isTemplate) {
|
|
63
|
+
checkForUpdate();
|
|
64
|
+
await generateTemplateTests(token);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
validate: v => v.trim() !== "" || "Token is required."
|
|
59
|
-
}
|
|
60
|
-
]);
|
|
68
|
+
if (isRecord) {
|
|
69
|
+
checkForUpdate();
|
|
70
|
+
await startRecording(token);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
61
73
|
|
|
62
|
-
|
|
74
|
+
console.log("Please specify --template or --record mode");
|
|
63
75
|
}
|
|
64
76
|
|
|
65
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
|
+
}
|
package/src/runner.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import ora from "ora";
|
|
3
3
|
import { validateSessionToken } from "./api.js";
|
|
4
|
-
import { ensureFolder } from "./fileManager.js";
|
|
4
|
+
import { ensureFolder, writeFileSafe } from "./fileManager.js";
|
|
5
5
|
import { openCodegenForTest } from "./playwrightRunner.js";
|
|
6
6
|
import { ensurePlaywrightInstalled } from "./playwrightInstaller.js";
|
|
7
|
+
import { buildTemplateTest } from "./templateGenerator.js";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
function getSafeSlug(testCase) {
|
|
10
|
+
const base =
|
|
11
|
+
testCase.slug ||
|
|
12
|
+
testCase.id ||
|
|
13
|
+
testCase.title ||
|
|
14
|
+
`test-${Date.now().toString(36)}`;
|
|
15
|
+
return String(base)
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9-_]+/g, "-")
|
|
18
|
+
.replace(/^-+|-+$/g, "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default async function run(token, mode = "record") {
|
|
9
22
|
const spinner = ora("Validating token...").start();
|
|
10
23
|
|
|
11
24
|
try {
|
|
@@ -26,6 +39,24 @@ export default async function run(token) {
|
|
|
26
39
|
return;
|
|
27
40
|
}
|
|
28
41
|
|
|
42
|
+
ensureFolder("./generated-tests");
|
|
43
|
+
|
|
44
|
+
if (mode === "template") {
|
|
45
|
+
console.log(chalk.cyan("\nMode: template (no browser)\n"));
|
|
46
|
+
console.log(chalk.cyan("Generating template-based Playwright tests...\n"));
|
|
47
|
+
|
|
48
|
+
for (const testCase of testCases) {
|
|
49
|
+
const slug = getSafeSlug(testCase);
|
|
50
|
+
const code = buildTemplateTest(testCase);
|
|
51
|
+
writeFileSafe(`./generated-tests/${slug}.spec.js`, code);
|
|
52
|
+
console.log(chalk.green(`Created ${slug}.spec.js`));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(chalk.green.bold("\nTemplate tests generated successfully!\n"));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.cyan("\nMode: record (browser opens)\n"));
|
|
29
60
|
console.log(chalk.green(`\n${testCases.length} test cases found.\n`));
|
|
30
61
|
|
|
31
62
|
const ok = ensurePlaywrightInstalled();
|
|
@@ -33,8 +64,6 @@ export default async function run(token) {
|
|
|
33
64
|
return;
|
|
34
65
|
}
|
|
35
66
|
|
|
36
|
-
ensureFolder("./generated-tests");
|
|
37
|
-
|
|
38
67
|
for (const testCase of testCases) {
|
|
39
68
|
await openCodegenForTest(testCase);
|
|
40
69
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
|
|
32
|
+
return `import { test } from "@playwright/test";
|
|
33
|
+
|
|
34
|
+
test("${title}", async ({ page }) => {
|
|
35
|
+
${lines.join("\n")}
|
|
36
|
+
});
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function buildTemplateTest(testCase) {
|
|
2
|
+
const title = testCase.title || "Generated test";
|
|
3
|
+
const description = testCase.description || "";
|
|
4
|
+
const startUrl =
|
|
5
|
+
testCase.startUrl || testCase.url || testCase.start_url || "about:blank";
|
|
6
|
+
|
|
7
|
+
const commentLines = [];
|
|
8
|
+
if (description) {
|
|
9
|
+
commentLines.push(`// Description: ${description}`);
|
|
10
|
+
}
|
|
11
|
+
if (testCase.id) {
|
|
12
|
+
commentLines.push(`// Test case ID: ${testCase.id}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const commentBlock = commentLines.length ? `${commentLines.join("\n")}\n` : "";
|
|
16
|
+
|
|
17
|
+
return `import { test, expect } from "@playwright/test";
|
|
18
|
+
|
|
19
|
+
${commentBlock}test("${title}", async ({ page }) => {
|
|
20
|
+
await page.goto("${startUrl}");
|
|
21
|
+
|
|
22
|
+
// TODO: add actions/assertions based on your test steps.
|
|
23
|
+
// Example:
|
|
24
|
+
// await page.getByRole("button", { name: "Sign in" }).click();
|
|
25
|
+
// await expect(page).toHaveURL(/dashboard/);
|
|
26
|
+
});
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
if (testCases.length > 0) {
|
|
23
|
+
console.log("Sample test case schema:", JSON.stringify(testCases[0], null, 2));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const outputDir = path.resolve(process.cwd(), "muagqa-tests");
|
|
27
|
+
if (!fs.existsSync(outputDir)) {
|
|
28
|
+
fs.mkdirSync(outputDir);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const tc of testCases) {
|
|
32
|
+
const template = buildTemplate(tc);
|
|
33
|
+
const slug = tc.slug || tc.id || `test-${Date.now()}`;
|
|
34
|
+
fs.writeFileSync(path.join(outputDir, `${slug}.spec.js`), template, "utf8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.green(`Generated ${testCases.length} test files in /muagqa-tests`));
|
|
38
|
+
}
|