@muuktest/amikoo-playwright 2.0.0

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.
Files changed (70) hide show
  1. package/README.md +26 -0
  2. package/dist/capture.cjs +57 -0
  3. package/dist/capture.cjs.map +1 -0
  4. package/dist/capture.d.cts +6 -0
  5. package/dist/capture.d.ts +6 -0
  6. package/dist/capture.js +23 -0
  7. package/dist/capture.js.map +1 -0
  8. package/dist/cli/agent-setup.cjs +68 -0
  9. package/dist/cli/agent-setup.cjs.map +1 -0
  10. package/dist/cli/agent-setup.d.cts +11 -0
  11. package/dist/cli/agent-setup.d.ts +11 -0
  12. package/dist/cli/agent-setup.js +31 -0
  13. package/dist/cli/agent-setup.js.map +1 -0
  14. package/dist/cli/amikoo-playwright-agent.md +82 -0
  15. package/dist/cli/fixture-creator.cjs +163 -0
  16. package/dist/cli/fixture-creator.cjs.map +1 -0
  17. package/dist/cli/fixture-creator.d.cts +12 -0
  18. package/dist/cli/fixture-creator.d.ts +12 -0
  19. package/dist/cli/fixture-creator.js +128 -0
  20. package/dist/cli/fixture-creator.js.map +1 -0
  21. package/dist/cli/index.cjs +134 -0
  22. package/dist/cli/index.cjs.map +1 -0
  23. package/dist/cli/index.d.cts +1 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +111 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/cli/mcp-setup.cjs +116 -0
  28. package/dist/cli/mcp-setup.cjs.map +1 -0
  29. package/dist/cli/mcp-setup.d.cts +12 -0
  30. package/dist/cli/mcp-setup.d.ts +12 -0
  31. package/dist/cli/mcp-setup.js +81 -0
  32. package/dist/cli/mcp-setup.js.map +1 -0
  33. package/dist/cli/scanner.cjs +137 -0
  34. package/dist/cli/scanner.cjs.map +1 -0
  35. package/dist/cli/scanner.d.cts +16 -0
  36. package/dist/cli/scanner.d.ts +16 -0
  37. package/dist/cli/scanner.js +100 -0
  38. package/dist/cli/scanner.js.map +1 -0
  39. package/dist/cli/test-updater.cjs +131 -0
  40. package/dist/cli/test-updater.cjs.map +1 -0
  41. package/dist/cli/test-updater.d.cts +12 -0
  42. package/dist/cli/test-updater.d.ts +12 -0
  43. package/dist/cli/test-updater.js +96 -0
  44. package/dist/cli/test-updater.js.map +1 -0
  45. package/dist/dom/buildDomTree.js +1760 -0
  46. package/dist/helpers/dom-extractor.cjs +344 -0
  47. package/dist/helpers/dom-extractor.cjs.map +1 -0
  48. package/dist/helpers/dom-extractor.d.cts +9 -0
  49. package/dist/helpers/dom-extractor.d.ts +9 -0
  50. package/dist/helpers/dom-extractor.js +318 -0
  51. package/dist/helpers/dom-extractor.js.map +1 -0
  52. package/dist/helpers/dom-service.cjs +365 -0
  53. package/dist/helpers/dom-service.cjs.map +1 -0
  54. package/dist/helpers/dom-service.d.cts +82 -0
  55. package/dist/helpers/dom-service.d.ts +82 -0
  56. package/dist/helpers/dom-service.js +338 -0
  57. package/dist/helpers/dom-service.js.map +1 -0
  58. package/dist/helpers/failure-analyzer.cjs +276 -0
  59. package/dist/helpers/failure-analyzer.cjs.map +1 -0
  60. package/dist/helpers/failure-analyzer.d.cts +100 -0
  61. package/dist/helpers/failure-analyzer.d.ts +100 -0
  62. package/dist/helpers/failure-analyzer.js +241 -0
  63. package/dist/helpers/failure-analyzer.js.map +1 -0
  64. package/dist/index.cjs +32 -0
  65. package/dist/index.cjs.map +1 -0
  66. package/dist/index.d.cts +3 -0
  67. package/dist/index.d.ts +3 -0
  68. package/dist/index.js +7 -0
  69. package/dist/index.js.map +1 -0
  70. package/package.json +51 -0
@@ -0,0 +1,81 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ const MCP_FILE_REL = path.join(".vscode", "mcp.json");
4
+ const AMIKOO_SERVER = {
5
+ command: "npx",
6
+ args: ["-y", "@muuktest/amikoo-mcp@latest"],
7
+ env: { MUUK_KEY: "${input:muuk_key}" }
8
+ };
9
+ const MUUK_KEY_INPUT = {
10
+ id: "muuk_key",
11
+ type: "promptString",
12
+ description: "MuukTest Key (available to download from MuukTest account)",
13
+ password: true
14
+ };
15
+ function checkExistingMcpConfig(projectDir) {
16
+ const filePath = path.join(projectDir, MCP_FILE_REL);
17
+ if (!fs.existsSync(filePath)) {
18
+ return { exists: false, hasServer: false, hasInput: false, config: null };
19
+ }
20
+ let config;
21
+ try {
22
+ config = JSON.parse(fs.readFileSync(filePath, "utf8"));
23
+ } catch {
24
+ console.warn(`[amikoo] Warning: ${MCP_FILE_REL} contains invalid JSON \u2014 skipping MCP setup.`);
25
+ return { exists: true, hasServer: false, hasInput: false, config: null };
26
+ }
27
+ const servers = config.servers;
28
+ const hasServer = !!servers?.["amikoo-mcp"];
29
+ const inputs = config.inputs;
30
+ const hasInput = Array.isArray(inputs) && inputs.some((i) => i.id === "muuk_key");
31
+ return { exists: true, hasServer, hasInput, config };
32
+ }
33
+ function planMcpConfig(projectDir) {
34
+ const filePath = path.join(projectDir, MCP_FILE_REL);
35
+ const existing = checkExistingMcpConfig(projectDir);
36
+ if (existing.hasServer || existing.hasInput) {
37
+ return { action: "skip", path: filePath, showWarning: false };
38
+ }
39
+ if (existing.exists && existing.config === null) {
40
+ return { action: "skip", path: filePath, showWarning: false };
41
+ }
42
+ if (!existing.exists) {
43
+ return { action: "create", path: filePath, showWarning: true };
44
+ }
45
+ return { action: "update", path: filePath, showWarning: false };
46
+ }
47
+ function createOrUpdateMcpConfig(projectDir, plan) {
48
+ const filePath = path.join(projectDir, MCP_FILE_REL);
49
+ if (plan.action === "skip") {
50
+ return { path: filePath, action: "skipped" };
51
+ }
52
+ if (plan.action === "create") {
53
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
54
+ const config2 = {
55
+ servers: { "amikoo-mcp": AMIKOO_SERVER },
56
+ inputs: [MUUK_KEY_INPUT]
57
+ };
58
+ fs.writeFileSync(filePath, JSON.stringify(config2, null, 2) + "\n", "utf8");
59
+ return { path: filePath, action: "created" };
60
+ }
61
+ const raw = fs.readFileSync(filePath, "utf8");
62
+ const config = JSON.parse(raw);
63
+ if (!config.servers || typeof config.servers !== "object") {
64
+ config.servers = {};
65
+ }
66
+ config.servers["amikoo-mcp"] = AMIKOO_SERVER;
67
+ if (!Array.isArray(config.inputs)) {
68
+ config.inputs = [];
69
+ }
70
+ const inputs = config.inputs;
71
+ if (!inputs.some((i) => i.id === "muuk_key")) {
72
+ inputs.push(MUUK_KEY_INPUT);
73
+ }
74
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf8");
75
+ return { path: filePath, action: "updated" };
76
+ }
77
+ export {
78
+ createOrUpdateMcpConfig,
79
+ planMcpConfig
80
+ };
81
+ //# sourceMappingURL=mcp-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/mcp-setup.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nconst MCP_FILE_REL = path.join('.vscode', 'mcp.json');\n\nconst AMIKOO_SERVER = {\n command: 'npx',\n args: ['-y', '@muuktest/amikoo-mcp@latest'],\n env: { MUUK_KEY: '${input:muuk_key}' },\n};\n\nconst MUUK_KEY_INPUT = {\n id: 'muuk_key',\n type: 'promptString',\n description: 'MuukTest Key (available to download from MuukTest account)',\n password: true,\n};\n\nexport interface McpConfigPlan {\n action: 'create' | 'update' | 'skip';\n path: string;\n showWarning: boolean;\n}\n\ninterface ExistingConfig {\n exists: boolean;\n hasServer: boolean;\n hasInput: boolean;\n config: Record<string, unknown> | null;\n}\n\nfunction checkExistingMcpConfig(projectDir: string): ExistingConfig {\n const filePath = path.join(projectDir, MCP_FILE_REL);\n if (!fs.existsSync(filePath)) {\n return { exists: false, hasServer: false, hasInput: false, config: null };\n }\n\n let config: Record<string, unknown>;\n try {\n config = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n } catch {\n console.warn(`[amikoo] Warning: ${MCP_FILE_REL} contains invalid JSON — skipping MCP setup.`);\n return { exists: true, hasServer: false, hasInput: false, config: null };\n }\n\n const servers = config.servers as Record<string, unknown> | undefined;\n const hasServer = !!servers?.['amikoo-mcp'];\n\n const inputs = config.inputs as Array<Record<string, unknown>> | undefined;\n const hasInput = Array.isArray(inputs) && inputs.some(i => i.id === 'muuk_key');\n\n return { exists: true, hasServer, hasInput, config };\n}\n\nexport function planMcpConfig(projectDir: string): McpConfigPlan {\n const filePath = path.join(projectDir, MCP_FILE_REL);\n const existing = checkExistingMcpConfig(projectDir);\n\n if (existing.hasServer || existing.hasInput) {\n return { action: 'skip', path: filePath, showWarning: false };\n }\n\n if (existing.exists && existing.config === null) {\n // File exists but invalid JSON — skip to avoid corruption\n return { action: 'skip', path: filePath, showWarning: false };\n }\n\n if (!existing.exists) {\n return { action: 'create', path: filePath, showWarning: true };\n }\n\n return { action: 'update', path: filePath, showWarning: false };\n}\n\nexport function createOrUpdateMcpConfig(\n projectDir: string,\n plan: McpConfigPlan,\n): { path: string; action: 'created' | 'updated' | 'skipped' } {\n const filePath = path.join(projectDir, MCP_FILE_REL);\n\n if (plan.action === 'skip') {\n return { path: filePath, action: 'skipped' };\n }\n\n if (plan.action === 'create') {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n const config = {\n servers: { 'amikoo-mcp': AMIKOO_SERVER },\n inputs: [MUUK_KEY_INPUT],\n };\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\\n', 'utf8');\n return { path: filePath, action: 'created' };\n }\n\n // update — merge into existing config\n const raw = fs.readFileSync(filePath, 'utf8');\n const config = JSON.parse(raw) as Record<string, unknown>;\n\n if (!config.servers || typeof config.servers !== 'object') {\n config.servers = {};\n }\n (config.servers as Record<string, unknown>)['amikoo-mcp'] = AMIKOO_SERVER;\n\n if (!Array.isArray(config.inputs)) {\n config.inputs = [];\n }\n const inputs = config.inputs as Array<Record<string, unknown>>;\n if (!inputs.some(i => i.id === 'muuk_key')) {\n inputs.push(MUUK_KEY_INPUT);\n }\n\n fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\\n', 'utf8');\n return { path: filePath, action: 'updated' };\n}\n"],"mappings":"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","names":["config"]}
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var scanner_exports = {};
30
+ __export(scanner_exports, {
31
+ detectLanguage: () => detectLanguage,
32
+ isESM: () => isESM,
33
+ scanProject: () => scanProject,
34
+ toFileInfo: () => toFileInfo
35
+ });
36
+ module.exports = __toCommonJS(scanner_exports);
37
+ var import_fs = __toESM(require("fs"), 1);
38
+ var import_path = __toESM(require("path"), 1);
39
+ function findProjectRoot(cwd) {
40
+ let dir = cwd;
41
+ while (true) {
42
+ if (import_fs.default.existsSync(import_path.default.join(dir, "package.json"))) return dir;
43
+ const parent = import_path.default.dirname(dir);
44
+ if (parent === dir) return cwd;
45
+ dir = parent;
46
+ }
47
+ }
48
+ function globSync(dir, patterns, excludeDirs) {
49
+ const results = [];
50
+ function walk(current) {
51
+ let entries;
52
+ try {
53
+ entries = import_fs.default.readdirSync(current, { withFileTypes: true });
54
+ } catch {
55
+ return;
56
+ }
57
+ for (const entry of entries) {
58
+ const fullPath = import_path.default.join(current, entry.name);
59
+ if (entry.isDirectory()) {
60
+ if (!excludeDirs.includes(entry.name)) walk(fullPath);
61
+ } else if (entry.isFile()) {
62
+ if (patterns.some((p) => p.test(entry.name))) results.push(fullPath);
63
+ }
64
+ }
65
+ }
66
+ walk(dir);
67
+ return results;
68
+ }
69
+ function isESM(filePath, content, projectRoot) {
70
+ const ext = import_path.default.extname(filePath);
71
+ if (ext === ".mjs") return true;
72
+ if (ext === ".cjs") return false;
73
+ if (/^import\s/m.test(content) || /^export\s/m.test(content)) return true;
74
+ if (/require\s*\(/.test(content) || /module\.exports/.test(content)) return false;
75
+ try {
76
+ const pkg = JSON.parse(import_fs.default.readFileSync(import_path.default.join(projectRoot, "package.json"), "utf8"));
77
+ if (pkg.type === "module") return true;
78
+ } catch {
79
+ }
80
+ return false;
81
+ }
82
+ function detectLanguage(filePath) {
83
+ return /\.(ts|tsx)$/.test(filePath) ? "ts" : "js";
84
+ }
85
+ function toFileInfo(filePath, projectRoot) {
86
+ const content = import_fs.default.readFileSync(filePath, "utf8");
87
+ const esm = isESM(filePath, content, projectRoot);
88
+ return {
89
+ path: filePath,
90
+ language: detectLanguage(filePath),
91
+ moduleSystem: esm ? "esm" : "cjs"
92
+ };
93
+ }
94
+ async function scanProject(cwd) {
95
+ const projectRoot = findProjectRoot(cwd);
96
+ const excludeDirs = ["node_modules", "dist", ".git", "coverage"];
97
+ const fixtureNamePattern = /^fixtures?\.(ts|js)$/;
98
+ const allSourceFiles = globSync(projectRoot, [/\.(ts|tsx|js|mjs|cjs)$/], excludeDirs);
99
+ let fixtureFile = null;
100
+ for (const f of allSourceFiles) {
101
+ if (fixtureNamePattern.test(import_path.default.basename(f))) {
102
+ fixtureFile = f;
103
+ break;
104
+ }
105
+ }
106
+ if (!fixtureFile) {
107
+ for (const f of allSourceFiles) {
108
+ try {
109
+ const content = import_fs.default.readFileSync(f, "utf8");
110
+ if (/base\.extend\s*\(|test\.extend\s*\(/.test(content)) {
111
+ fixtureFile = f;
112
+ break;
113
+ }
114
+ } catch {
115
+ }
116
+ }
117
+ }
118
+ const testFilePattern = /\.(spec|test)\.(ts|js)$/;
119
+ const candidateTestFiles = globSync(projectRoot, [testFilePattern], excludeDirs);
120
+ const testFiles = candidateTestFiles.filter((f) => {
121
+ try {
122
+ const content = import_fs.default.readFileSync(f, "utf8");
123
+ return /from\s+['"]@playwright\/test['"]/.test(content) || /require\s*\(\s*['"]@playwright\/test['"]\s*\)/.test(content);
124
+ } catch {
125
+ return false;
126
+ }
127
+ });
128
+ return { fixtureFile, testFiles, projectRoot };
129
+ }
130
+ // Annotate the CommonJS export names for ESM import in node:
131
+ 0 && (module.exports = {
132
+ detectLanguage,
133
+ isESM,
134
+ scanProject,
135
+ toFileInfo
136
+ });
137
+ //# sourceMappingURL=scanner.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/scanner.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nexport interface ScanResult {\n fixtureFile: string | null;\n testFiles: string[];\n projectRoot: string;\n}\n\nfunction findProjectRoot(cwd: string): string {\n let dir = cwd;\n while (true) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return cwd;\n dir = parent;\n }\n}\n\nfunction globSync(dir: string, patterns: RegExp[], excludeDirs: string[]): string[] {\n const results: string[] = [];\n function walk(current: string) {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(current, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const fullPath = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (!excludeDirs.includes(entry.name)) walk(fullPath);\n } else if (entry.isFile()) {\n if (patterns.some(p => p.test(entry.name))) results.push(fullPath);\n }\n }\n }\n walk(dir);\n return results;\n}\n\nfunction isESM(filePath: string, content: string, projectRoot: string): boolean {\n const ext = path.extname(filePath);\n if (ext === '.mjs') return true;\n if (ext === '.cjs') return false;\n if (/^import\\s/m.test(content) || /^export\\s/m.test(content)) return true;\n if (/require\\s*\\(/.test(content) || /module\\.exports/.test(content)) return false;\n try {\n const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));\n if (pkg.type === 'module') return true;\n } catch {}\n return false;\n}\n\nfunction detectLanguage(filePath: string): 'ts' | 'js' {\n return /\\.(ts|tsx)$/.test(filePath) ? 'ts' : 'js';\n}\n\nexport interface FileInfo {\n path: string;\n language: 'ts' | 'js';\n moduleSystem: 'esm' | 'cjs';\n}\n\nfunction toFileInfo(filePath: string, projectRoot: string): FileInfo {\n const content = fs.readFileSync(filePath, 'utf8');\n const esm = isESM(filePath, content, projectRoot);\n return {\n path: filePath,\n language: detectLanguage(filePath),\n moduleSystem: esm ? 'esm' : 'cjs',\n };\n}\n\nexport async function scanProject(cwd: string): Promise<ScanResult> {\n const projectRoot = findProjectRoot(cwd);\n const excludeDirs = ['node_modules', 'dist', '.git', 'coverage'];\n\n // Fixture detection — pass 1: by name\n const fixtureNamePattern = /^fixtures?\\.(ts|js)$/;\n const allSourceFiles = globSync(projectRoot, [/\\.(ts|tsx|js|mjs|cjs)$/], excludeDirs);\n\n let fixtureFile: string | null = null;\n\n for (const f of allSourceFiles) {\n if (fixtureNamePattern.test(path.basename(f))) {\n fixtureFile = f;\n break;\n }\n }\n\n // Fixture detection — pass 2: by content (base.extend / test.extend)\n if (!fixtureFile) {\n for (const f of allSourceFiles) {\n try {\n const content = fs.readFileSync(f, 'utf8');\n if (/base\\.extend\\s*\\(|test\\.extend\\s*\\(/.test(content)) {\n fixtureFile = f;\n break;\n }\n } catch {}\n }\n }\n\n // Test file detection\n const testFilePattern = /\\.(spec|test)\\.(ts|js)$/;\n const candidateTestFiles = globSync(projectRoot, [testFilePattern], excludeDirs);\n\n const testFiles = candidateTestFiles.filter(f => {\n try {\n const content = fs.readFileSync(f, 'utf8');\n return /from\\s+['\"]@playwright\\/test['\"]/.test(content) ||\n /require\\s*\\(\\s*['\"]@playwright\\/test['\"]\\s*\\)/.test(content);\n } catch {\n return false;\n }\n });\n\n return { fixtureFile, testFiles, projectRoot };\n}\n\nexport { toFileInfo, detectLanguage, isESM };\n"],"mappings":"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","names":["fs","path"]}
@@ -0,0 +1,16 @@
1
+ interface ScanResult {
2
+ fixtureFile: string | null;
3
+ testFiles: string[];
4
+ projectRoot: string;
5
+ }
6
+ declare function isESM(filePath: string, content: string, projectRoot: string): boolean;
7
+ declare function detectLanguage(filePath: string): 'ts' | 'js';
8
+ interface FileInfo {
9
+ path: string;
10
+ language: 'ts' | 'js';
11
+ moduleSystem: 'esm' | 'cjs';
12
+ }
13
+ declare function toFileInfo(filePath: string, projectRoot: string): FileInfo;
14
+ declare function scanProject(cwd: string): Promise<ScanResult>;
15
+
16
+ export { type FileInfo, type ScanResult, detectLanguage, isESM, scanProject, toFileInfo };
@@ -0,0 +1,16 @@
1
+ interface ScanResult {
2
+ fixtureFile: string | null;
3
+ testFiles: string[];
4
+ projectRoot: string;
5
+ }
6
+ declare function isESM(filePath: string, content: string, projectRoot: string): boolean;
7
+ declare function detectLanguage(filePath: string): 'ts' | 'js';
8
+ interface FileInfo {
9
+ path: string;
10
+ language: 'ts' | 'js';
11
+ moduleSystem: 'esm' | 'cjs';
12
+ }
13
+ declare function toFileInfo(filePath: string, projectRoot: string): FileInfo;
14
+ declare function scanProject(cwd: string): Promise<ScanResult>;
15
+
16
+ export { type FileInfo, type ScanResult, detectLanguage, isESM, scanProject, toFileInfo };
@@ -0,0 +1,100 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ function findProjectRoot(cwd) {
4
+ let dir = cwd;
5
+ while (true) {
6
+ if (fs.existsSync(path.join(dir, "package.json"))) return dir;
7
+ const parent = path.dirname(dir);
8
+ if (parent === dir) return cwd;
9
+ dir = parent;
10
+ }
11
+ }
12
+ function globSync(dir, patterns, excludeDirs) {
13
+ const results = [];
14
+ function walk(current) {
15
+ let entries;
16
+ try {
17
+ entries = fs.readdirSync(current, { withFileTypes: true });
18
+ } catch {
19
+ return;
20
+ }
21
+ for (const entry of entries) {
22
+ const fullPath = path.join(current, entry.name);
23
+ if (entry.isDirectory()) {
24
+ if (!excludeDirs.includes(entry.name)) walk(fullPath);
25
+ } else if (entry.isFile()) {
26
+ if (patterns.some((p) => p.test(entry.name))) results.push(fullPath);
27
+ }
28
+ }
29
+ }
30
+ walk(dir);
31
+ return results;
32
+ }
33
+ function isESM(filePath, content, projectRoot) {
34
+ const ext = path.extname(filePath);
35
+ if (ext === ".mjs") return true;
36
+ if (ext === ".cjs") return false;
37
+ if (/^import\s/m.test(content) || /^export\s/m.test(content)) return true;
38
+ if (/require\s*\(/.test(content) || /module\.exports/.test(content)) return false;
39
+ try {
40
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"));
41
+ if (pkg.type === "module") return true;
42
+ } catch {
43
+ }
44
+ return false;
45
+ }
46
+ function detectLanguage(filePath) {
47
+ return /\.(ts|tsx)$/.test(filePath) ? "ts" : "js";
48
+ }
49
+ function toFileInfo(filePath, projectRoot) {
50
+ const content = fs.readFileSync(filePath, "utf8");
51
+ const esm = isESM(filePath, content, projectRoot);
52
+ return {
53
+ path: filePath,
54
+ language: detectLanguage(filePath),
55
+ moduleSystem: esm ? "esm" : "cjs"
56
+ };
57
+ }
58
+ async function scanProject(cwd) {
59
+ const projectRoot = findProjectRoot(cwd);
60
+ const excludeDirs = ["node_modules", "dist", ".git", "coverage"];
61
+ const fixtureNamePattern = /^fixtures?\.(ts|js)$/;
62
+ const allSourceFiles = globSync(projectRoot, [/\.(ts|tsx|js|mjs|cjs)$/], excludeDirs);
63
+ let fixtureFile = null;
64
+ for (const f of allSourceFiles) {
65
+ if (fixtureNamePattern.test(path.basename(f))) {
66
+ fixtureFile = f;
67
+ break;
68
+ }
69
+ }
70
+ if (!fixtureFile) {
71
+ for (const f of allSourceFiles) {
72
+ try {
73
+ const content = fs.readFileSync(f, "utf8");
74
+ if (/base\.extend\s*\(|test\.extend\s*\(/.test(content)) {
75
+ fixtureFile = f;
76
+ break;
77
+ }
78
+ } catch {
79
+ }
80
+ }
81
+ }
82
+ const testFilePattern = /\.(spec|test)\.(ts|js)$/;
83
+ const candidateTestFiles = globSync(projectRoot, [testFilePattern], excludeDirs);
84
+ const testFiles = candidateTestFiles.filter((f) => {
85
+ try {
86
+ const content = fs.readFileSync(f, "utf8");
87
+ return /from\s+['"]@playwright\/test['"]/.test(content) || /require\s*\(\s*['"]@playwright\/test['"]\s*\)/.test(content);
88
+ } catch {
89
+ return false;
90
+ }
91
+ });
92
+ return { fixtureFile, testFiles, projectRoot };
93
+ }
94
+ export {
95
+ detectLanguage,
96
+ isESM,
97
+ scanProject,
98
+ toFileInfo
99
+ };
100
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/scanner.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nexport interface ScanResult {\n fixtureFile: string | null;\n testFiles: string[];\n projectRoot: string;\n}\n\nfunction findProjectRoot(cwd: string): string {\n let dir = cwd;\n while (true) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return cwd;\n dir = parent;\n }\n}\n\nfunction globSync(dir: string, patterns: RegExp[], excludeDirs: string[]): string[] {\n const results: string[] = [];\n function walk(current: string) {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(current, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const fullPath = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (!excludeDirs.includes(entry.name)) walk(fullPath);\n } else if (entry.isFile()) {\n if (patterns.some(p => p.test(entry.name))) results.push(fullPath);\n }\n }\n }\n walk(dir);\n return results;\n}\n\nfunction isESM(filePath: string, content: string, projectRoot: string): boolean {\n const ext = path.extname(filePath);\n if (ext === '.mjs') return true;\n if (ext === '.cjs') return false;\n if (/^import\\s/m.test(content) || /^export\\s/m.test(content)) return true;\n if (/require\\s*\\(/.test(content) || /module\\.exports/.test(content)) return false;\n try {\n const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));\n if (pkg.type === 'module') return true;\n } catch {}\n return false;\n}\n\nfunction detectLanguage(filePath: string): 'ts' | 'js' {\n return /\\.(ts|tsx)$/.test(filePath) ? 'ts' : 'js';\n}\n\nexport interface FileInfo {\n path: string;\n language: 'ts' | 'js';\n moduleSystem: 'esm' | 'cjs';\n}\n\nfunction toFileInfo(filePath: string, projectRoot: string): FileInfo {\n const content = fs.readFileSync(filePath, 'utf8');\n const esm = isESM(filePath, content, projectRoot);\n return {\n path: filePath,\n language: detectLanguage(filePath),\n moduleSystem: esm ? 'esm' : 'cjs',\n };\n}\n\nexport async function scanProject(cwd: string): Promise<ScanResult> {\n const projectRoot = findProjectRoot(cwd);\n const excludeDirs = ['node_modules', 'dist', '.git', 'coverage'];\n\n // Fixture detection — pass 1: by name\n const fixtureNamePattern = /^fixtures?\\.(ts|js)$/;\n const allSourceFiles = globSync(projectRoot, [/\\.(ts|tsx|js|mjs|cjs)$/], excludeDirs);\n\n let fixtureFile: string | null = null;\n\n for (const f of allSourceFiles) {\n if (fixtureNamePattern.test(path.basename(f))) {\n fixtureFile = f;\n break;\n }\n }\n\n // Fixture detection — pass 2: by content (base.extend / test.extend)\n if (!fixtureFile) {\n for (const f of allSourceFiles) {\n try {\n const content = fs.readFileSync(f, 'utf8');\n if (/base\\.extend\\s*\\(|test\\.extend\\s*\\(/.test(content)) {\n fixtureFile = f;\n break;\n }\n } catch {}\n }\n }\n\n // Test file detection\n const testFilePattern = /\\.(spec|test)\\.(ts|js)$/;\n const candidateTestFiles = globSync(projectRoot, [testFilePattern], excludeDirs);\n\n const testFiles = candidateTestFiles.filter(f => {\n try {\n const content = fs.readFileSync(f, 'utf8');\n return /from\\s+['\"]@playwright\\/test['\"]/.test(content) ||\n /require\\s*\\(\\s*['\"]@playwright\\/test['\"]\\s*\\)/.test(content);\n } catch {\n return false;\n }\n });\n\n return { fixtureFile, testFiles, projectRoot };\n}\n\nexport { toFileInfo, detectLanguage, isESM };\n"],"mappings":"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","names":[]}
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var test_updater_exports = {};
30
+ __export(test_updater_exports, {
31
+ planTestImports: () => planTestImports,
32
+ updateTestImports: () => updateTestImports
33
+ });
34
+ module.exports = __toCommonJS(test_updater_exports);
35
+ var import_fs = __toESM(require("fs"), 1);
36
+ var import_path = __toESM(require("path"), 1);
37
+ var import_scanner = require("./scanner.cjs");
38
+ function toRelativePath(from, to) {
39
+ let rel = import_path.default.relative(import_path.default.dirname(from), to);
40
+ rel = rel.replace(/\.(ts|tsx)$/, "");
41
+ if (!rel.startsWith(".")) rel = "./" + rel;
42
+ return rel;
43
+ }
44
+ function updateTestImports(testFiles, fixturePath, projectRoot) {
45
+ const updated = [];
46
+ const skipped = [];
47
+ for (const testFile of testFiles) {
48
+ let content = import_fs.default.readFileSync(testFile, "utf8");
49
+ const relPath = toRelativePath(testFile, fixturePath);
50
+ if (content.includes(relPath)) {
51
+ skipped.push(testFile);
52
+ continue;
53
+ }
54
+ const esm = (0, import_scanner.isESM)(testFile, content, projectRoot);
55
+ let changed = false;
56
+ if (esm) {
57
+ const importRegex = /^import\s+\{([^}]+)\}\s+from\s+['"]@playwright\/test['"]/gm;
58
+ content = content.replace(importRegex, (match, imports) => {
59
+ if (/^import\s+type/.test(match)) return match;
60
+ const importList = imports.split(",").map((s) => s.trim()).filter(Boolean);
61
+ const hasTest = importList.some((i) => i === "test" || i.startsWith("test ") || i.includes(" as test"));
62
+ if (!hasTest) return match;
63
+ changed = true;
64
+ return `import { ${importList.join(", ")} } from '${relPath}'`;
65
+ });
66
+ } else {
67
+ const requireRegex = /const\s+\{([^}]+)\}\s*=\s*require\s*\(\s*['"]@playwright\/test['"]\s*\)/g;
68
+ content = content.replace(requireRegex, (match, imports) => {
69
+ const importList = imports.split(",").map((s) => s.trim()).filter(Boolean);
70
+ const hasTest = importList.some((i) => i === "test" || i.startsWith("test ") || i.includes(": test"));
71
+ if (!hasTest) return match;
72
+ changed = true;
73
+ return `const { ${importList.join(", ")} } = require('${relPath}')`;
74
+ });
75
+ }
76
+ if (changed) {
77
+ import_fs.default.writeFileSync(testFile, content, "utf8");
78
+ updated.push(testFile);
79
+ } else {
80
+ skipped.push(testFile);
81
+ }
82
+ }
83
+ return { updated, skipped };
84
+ }
85
+ function planTestImports(testFiles, fixturePath, projectRoot) {
86
+ const toUpdate = [];
87
+ const toSkip = [];
88
+ for (const testFile of testFiles) {
89
+ const content = import_fs.default.readFileSync(testFile, "utf8");
90
+ const relPath = toRelativePath(testFile, fixturePath);
91
+ if (content.includes(relPath)) {
92
+ toSkip.push(testFile);
93
+ continue;
94
+ }
95
+ const esm = (0, import_scanner.isESM)(testFile, content, projectRoot);
96
+ let wouldChange = false;
97
+ if (esm) {
98
+ const importRegex = /^import\s+\{([^}]+)\}\s+from\s+['"]@playwright\/test['"]/gm;
99
+ let m;
100
+ while ((m = importRegex.exec(content)) !== null) {
101
+ if (/^import\s+type/.test(m[0])) continue;
102
+ const importList = m[1].split(",").map((s) => s.trim()).filter(Boolean);
103
+ const hasTest = importList.some((i) => i === "test" || i.startsWith("test ") || i.includes(" as test"));
104
+ if (hasTest) {
105
+ wouldChange = true;
106
+ break;
107
+ }
108
+ }
109
+ } else {
110
+ const requireRegex = /const\s+\{([^}]+)\}\s*=\s*require\s*\(\s*['"]@playwright\/test['"]\s*\)/g;
111
+ let m;
112
+ while ((m = requireRegex.exec(content)) !== null) {
113
+ const importList = m[1].split(",").map((s) => s.trim()).filter(Boolean);
114
+ const hasTest = importList.some((i) => i === "test" || i.startsWith("test ") || i.includes(": test"));
115
+ if (hasTest) {
116
+ wouldChange = true;
117
+ break;
118
+ }
119
+ }
120
+ }
121
+ if (wouldChange) toUpdate.push(testFile);
122
+ else toSkip.push(testFile);
123
+ }
124
+ return { toUpdate, toSkip };
125
+ }
126
+ // Annotate the CommonJS export names for ESM import in node:
127
+ 0 && (module.exports = {
128
+ planTestImports,
129
+ updateTestImports
130
+ });
131
+ //# sourceMappingURL=test-updater.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/test-updater.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\nimport { isESM } from './scanner.js';\n\nexport interface UpdateResult {\n updated: string[];\n skipped: string[];\n}\n\nexport interface TestImportPlan {\n toUpdate: string[];\n toSkip: string[];\n}\n\nfunction toRelativePath(from: string, to: string): string {\n let rel = path.relative(path.dirname(from), to);\n // Remove extension for cleaner imports\n rel = rel.replace(/\\.(ts|tsx)$/, '');\n if (!rel.startsWith('.')) rel = './' + rel;\n return rel;\n}\n\nexport function updateTestImports(testFiles: string[], fixturePath: string, projectRoot: string): UpdateResult {\n const updated: string[] = [];\n const skipped: string[] = [];\n\n for (const testFile of testFiles) {\n let content = fs.readFileSync(testFile, 'utf8');\n const relPath = toRelativePath(testFile, fixturePath);\n\n // Skip if already importing from fixture path\n if (content.includes(relPath)) {\n skipped.push(testFile);\n continue;\n }\n\n const esm = isESM(testFile, content, projectRoot);\n let changed = false;\n\n if (esm) {\n // Replace: import { test[, expect] } from '@playwright/test'\n // but NOT import type lines, and NOT standalone expect-only imports\n const importRegex = /^import\\s+\\{([^}]+)\\}\\s+from\\s+['\"]@playwright\\/test['\"]/gm;\n content = content.replace(importRegex, (match, imports: string) => {\n // Skip `import type` — handled by checking what came before\n if (/^import\\s+type/.test(match)) return match;\n\n const importList = imports.split(',').map((s: string) => s.trim()).filter(Boolean);\n const hasTest = importList.some(i => i === 'test' || i.startsWith('test ') || i.includes(' as test'));\n if (!hasTest) return match; // leave standalone expect imports alone\n\n changed = true;\n return `import { ${importList.join(', ')} } from '${relPath}'`;\n });\n } else {\n // Replace: const { test[, expect] } = require('@playwright/test')\n const requireRegex = /const\\s+\\{([^}]+)\\}\\s*=\\s*require\\s*\\(\\s*['\"]@playwright\\/test['\"]\\s*\\)/g;\n content = content.replace(requireRegex, (match, imports: string) => {\n const importList = imports.split(',').map((s: string) => s.trim()).filter(Boolean);\n const hasTest = importList.some(i => i === 'test' || i.startsWith('test ') || i.includes(': test'));\n if (!hasTest) return match;\n\n changed = true;\n return `const { ${importList.join(', ')} } = require('${relPath}')`;\n });\n }\n\n if (changed) {\n fs.writeFileSync(testFile, content, 'utf8');\n updated.push(testFile);\n } else {\n skipped.push(testFile);\n }\n }\n\n return { updated, skipped };\n}\n\nexport function planTestImports(\n testFiles: string[],\n fixturePath: string,\n projectRoot: string\n): TestImportPlan {\n const toUpdate: string[] = [];\n const toSkip: string[] = [];\n\n for (const testFile of testFiles) {\n const content = fs.readFileSync(testFile, 'utf8');\n const relPath = toRelativePath(testFile, fixturePath);\n\n if (content.includes(relPath)) { toSkip.push(testFile); continue; }\n\n const esm = isESM(testFile, content, projectRoot);\n let wouldChange = false;\n\n if (esm) {\n const importRegex = /^import\\s+\\{([^}]+)\\}\\s+from\\s+['\"]@playwright\\/test['\"]/gm;\n let m;\n while ((m = importRegex.exec(content)) !== null) {\n if (/^import\\s+type/.test(m[0])) continue;\n const importList = m[1].split(',').map((s: string) => s.trim()).filter(Boolean);\n const hasTest = importList.some((i: string) => i === 'test' || i.startsWith('test ') || i.includes(' as test'));\n if (hasTest) { wouldChange = true; break; }\n }\n } else {\n const requireRegex = /const\\s+\\{([^}]+)\\}\\s*=\\s*require\\s*\\(\\s*['\"]@playwright\\/test['\"]\\s*\\)/g;\n let m;\n while ((m = requireRegex.exec(content)) !== null) {\n const importList = m[1].split(',').map((s: string) => s.trim()).filter(Boolean);\n const hasTest = importList.some((i: string) => i === 'test' || i.startsWith('test ') || i.includes(': test'));\n if (hasTest) { wouldChange = true; break; }\n }\n }\n\n if (wouldChange) toUpdate.push(testFile);\n else toSkip.push(testFile);\n }\n\n return { toUpdate, toSkip };\n}\n"],"mappings":"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","names":["path","fs"]}
@@ -0,0 +1,12 @@
1
+ interface UpdateResult {
2
+ updated: string[];
3
+ skipped: string[];
4
+ }
5
+ interface TestImportPlan {
6
+ toUpdate: string[];
7
+ toSkip: string[];
8
+ }
9
+ declare function updateTestImports(testFiles: string[], fixturePath: string, projectRoot: string): UpdateResult;
10
+ declare function planTestImports(testFiles: string[], fixturePath: string, projectRoot: string): TestImportPlan;
11
+
12
+ export { type TestImportPlan, type UpdateResult, planTestImports, updateTestImports };
@@ -0,0 +1,12 @@
1
+ interface UpdateResult {
2
+ updated: string[];
3
+ skipped: string[];
4
+ }
5
+ interface TestImportPlan {
6
+ toUpdate: string[];
7
+ toSkip: string[];
8
+ }
9
+ declare function updateTestImports(testFiles: string[], fixturePath: string, projectRoot: string): UpdateResult;
10
+ declare function planTestImports(testFiles: string[], fixturePath: string, projectRoot: string): TestImportPlan;
11
+
12
+ export { type TestImportPlan, type UpdateResult, planTestImports, updateTestImports };