@continuum8032/playwright-ai-tools 0.2.0-oidc-bootstrap.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.
- package/LICENSE +21 -0
- package/README.md +14 -0
- package/dist/chunk-3VDDPPGG.js +927 -0
- package/dist/chunk-GSXFV7YQ.js +544 -0
- package/dist/chunk-QL3XW2UN.js +205 -0
- package/dist/cli.cjs +1750 -0
- package/dist/cli.d.cts +4 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +308 -0
- package/dist/index.cjs +2030 -0
- package/dist/index.d.cts +218 -0
- package/dist/index.d.ts +218 -0
- package/dist/index.js +354 -0
- package/dist/reporter.cjs +1265 -0
- package/dist/reporter.d.cts +22 -0
- package/dist/reporter.d.ts +22 -0
- package/dist/reporter.js +133 -0
- package/dist/types-B55GjfW7.d.cts +355 -0
- package/dist/types-B55GjfW7.d.ts +355 -0
- package/package.json +58 -0
package/dist/cli.d.cts
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
OpenAIResponsesProvider,
|
|
4
|
+
TemplateTestGenerationProvider,
|
|
5
|
+
benchmarkFailures,
|
|
6
|
+
generatePlaywrightTestDraft,
|
|
7
|
+
normalizePlaywrightJsonReport
|
|
8
|
+
} from "./chunk-GSXFV7YQ.js";
|
|
9
|
+
import {
|
|
10
|
+
SQLiteStore,
|
|
11
|
+
analyzeFailures,
|
|
12
|
+
generatePatchSuggestions,
|
|
13
|
+
htmlReport,
|
|
14
|
+
markdownReport
|
|
15
|
+
} from "./chunk-3VDDPPGG.js";
|
|
16
|
+
|
|
17
|
+
// src/cli.ts
|
|
18
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
19
|
+
import path from "path";
|
|
20
|
+
|
|
21
|
+
// src/privacy.ts
|
|
22
|
+
var SECRET_PATTERNS = [
|
|
23
|
+
[/\b(authorization\s*:\s*bearer)\s+[A-Za-z0-9._~+/-]+=*/gi, "$1 [REDACTED]"],
|
|
24
|
+
[/\b(cookie\s*:\s*)[^\n\r]+/gi, "$1[REDACTED]"],
|
|
25
|
+
[/\b(password|passwd|token|api[_-]?key|secret)=([^&\s]+)/gi, "$1=[REDACTED]"],
|
|
26
|
+
[/\b(password|passwd|token|api[_-]?key|secret)\s*:\s*["']?[^"',\s}]+/gi, "$1: [REDACTED]"]
|
|
27
|
+
];
|
|
28
|
+
function redactSensitiveText(text) {
|
|
29
|
+
return SECRET_PATTERNS.reduce((value, [pattern, replacement]) => value.replace(pattern, replacement), text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/cli.ts
|
|
33
|
+
function argValue(args, name) {
|
|
34
|
+
const index = args.indexOf(name);
|
|
35
|
+
return index >= 0 ? args[index + 1] : void 0;
|
|
36
|
+
}
|
|
37
|
+
function hasFlag(args, name) {
|
|
38
|
+
return args.includes(name);
|
|
39
|
+
}
|
|
40
|
+
async function commandInit() {
|
|
41
|
+
await mkdir(".playwright-ai-tools", { recursive: true });
|
|
42
|
+
await writeFile(
|
|
43
|
+
".playwright-ai-tools/config.json",
|
|
44
|
+
`${JSON.stringify({ outputLanguage: process.env.PW_AI_OUTPUT_LANGUAGE || "ru", privacy: { redactSecrets: true } }, null, 2)}
|
|
45
|
+
`,
|
|
46
|
+
"utf8"
|
|
47
|
+
);
|
|
48
|
+
await writeFile(
|
|
49
|
+
".playwright-ai-tools/knowledge.md",
|
|
50
|
+
"# Project testing knowledge\n\nAdd selectors policy, test data rules, known flaky areas, and ownership notes here.\n",
|
|
51
|
+
"utf8"
|
|
52
|
+
);
|
|
53
|
+
console.log("Created .playwright-ai-tools/config.json");
|
|
54
|
+
}
|
|
55
|
+
async function writeJson(output, payload) {
|
|
56
|
+
const text = `${JSON.stringify(payload, null, 2)}
|
|
57
|
+
`;
|
|
58
|
+
if (!output) {
|
|
59
|
+
console.log(text.trimEnd());
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await mkdir(path.dirname(output), { recursive: true });
|
|
63
|
+
await writeFile(output, text, "utf8");
|
|
64
|
+
}
|
|
65
|
+
async function commandDoctor(args) {
|
|
66
|
+
const configPath = argValue(args, "--config") || "playwright.config.ts";
|
|
67
|
+
const output = argValue(args, "--output");
|
|
68
|
+
let config = "";
|
|
69
|
+
let configExists = true;
|
|
70
|
+
try {
|
|
71
|
+
config = await readFile(configPath, "utf8");
|
|
72
|
+
} catch {
|
|
73
|
+
configExists = false;
|
|
74
|
+
}
|
|
75
|
+
const checks = [
|
|
76
|
+
{
|
|
77
|
+
id: "playwright-config",
|
|
78
|
+
status: configExists ? "pass" : "fail",
|
|
79
|
+
message: configExists ? `Found ${configPath}` : `Missing ${configPath}`
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "trace",
|
|
83
|
+
status: /trace\s*:\s*['"](?:on|on-first-retry|retain-on-failure)['"]/.test(config) ? "pass" : "warn",
|
|
84
|
+
message: "Enable trace on retry or failure for useful evidence."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "screenshot",
|
|
88
|
+
status: /screenshot\s*:\s*['"](?:on|only-on-failure)['"]/.test(config) ? "pass" : "warn",
|
|
89
|
+
message: "Enable screenshots on failure for visual evidence."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "video",
|
|
93
|
+
status: /video\s*:\s*['"](?:on|retain-on-failure|on-first-retry)['"]/.test(config) ? "pass" : "warn",
|
|
94
|
+
message: "Enable video on failure when debugging interactions matters."
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "privacy",
|
|
98
|
+
status: "pass",
|
|
99
|
+
message: "Text knowledge imports and generated summaries redact common secrets by default."
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
await writeJson(output, {
|
|
103
|
+
status: checks.some((check) => check.status === "fail") ? "fail" : "pass",
|
|
104
|
+
checks
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function commandKnowledgeImport(args) {
|
|
108
|
+
const source = argValue(args, "--source");
|
|
109
|
+
if (!source) throw new Error("Missing --source <path>");
|
|
110
|
+
const database = argValue(args, "--database") || path.join(".playwright-ai-tools", "ai-tools.sqlite");
|
|
111
|
+
const namespace = argValue(args, "--namespace") || "default";
|
|
112
|
+
const store = new SQLiteStore({ path: database });
|
|
113
|
+
await store.migrate();
|
|
114
|
+
const raw = await readFile(source, "utf8");
|
|
115
|
+
const text = redactSensitiveText(raw);
|
|
116
|
+
await store.upsertMemory({
|
|
117
|
+
namespace,
|
|
118
|
+
kind: "knowledge",
|
|
119
|
+
key: path.basename(source),
|
|
120
|
+
value: { source, text },
|
|
121
|
+
tags: ["knowledge", ...path.basename(source).replace(/\.[^.]+$/, "").split(/[-_\s]+/).filter(Boolean)],
|
|
122
|
+
confidence: 1,
|
|
123
|
+
source: "knowledge-import"
|
|
124
|
+
});
|
|
125
|
+
await store.close();
|
|
126
|
+
console.log(`Imported knowledge from ${source}`);
|
|
127
|
+
}
|
|
128
|
+
async function commandFeedbackRecord(args) {
|
|
129
|
+
const signature = argValue(args, "--signature");
|
|
130
|
+
const decision = argValue(args, "--decision");
|
|
131
|
+
if (!signature) throw new Error("Missing --signature <signature>");
|
|
132
|
+
if (!decision) throw new Error("Missing --decision <approved|rejected|needs_work|mark_flaky>");
|
|
133
|
+
const database = argValue(args, "--database") || path.join(".playwright-ai-tools", "ai-tools.sqlite");
|
|
134
|
+
const reason = argValue(args, "--reason");
|
|
135
|
+
const store = new SQLiteStore({ path: database });
|
|
136
|
+
await store.migrate();
|
|
137
|
+
await store.recordFeedback({ signature, decision, reason, metadata: {} });
|
|
138
|
+
await store.close();
|
|
139
|
+
console.log(`Recorded ${decision} feedback for ${signature}`);
|
|
140
|
+
}
|
|
141
|
+
async function commandHistoryFind(args) {
|
|
142
|
+
const database = argValue(args, "--database") || path.join(".playwright-ai-tools", "ai-tools.sqlite");
|
|
143
|
+
const query = argValue(args, "--query") || "";
|
|
144
|
+
const output = argValue(args, "--output");
|
|
145
|
+
const limit = Number(argValue(args, "--limit") || 20);
|
|
146
|
+
const store = new SQLiteStore({ path: database });
|
|
147
|
+
await store.migrate();
|
|
148
|
+
const payload = {
|
|
149
|
+
failures: await store.findFailures({ query, limit }),
|
|
150
|
+
memory: await store.searchMemory({ query, limit }),
|
|
151
|
+
feedback: await store.searchFeedback({ query, limit })
|
|
152
|
+
};
|
|
153
|
+
await store.close();
|
|
154
|
+
await writeJson(output, payload);
|
|
155
|
+
}
|
|
156
|
+
async function commandGithubSummary(args) {
|
|
157
|
+
const reportPath = argValue(args, "--report");
|
|
158
|
+
if (!reportPath) throw new Error("Missing --report <path>");
|
|
159
|
+
const output = argValue(args, "--output");
|
|
160
|
+
if (!output) throw new Error("Missing --output <path>");
|
|
161
|
+
const artifactBaseUrl = argValue(args, "--artifact-base-url")?.replace(/\/$/, "");
|
|
162
|
+
const report = JSON.parse(await readFile(reportPath, "utf8"));
|
|
163
|
+
const lines = [
|
|
164
|
+
"# Playwright AI Tools Summary",
|
|
165
|
+
"",
|
|
166
|
+
`Run: ${report.run.id}`,
|
|
167
|
+
`Failures: ${report.failures.length}`,
|
|
168
|
+
""
|
|
169
|
+
];
|
|
170
|
+
if (artifactBaseUrl) {
|
|
171
|
+
lines.push(`[Open HTML report](${artifactBaseUrl}/ai-analysis-report.html)`, "");
|
|
172
|
+
}
|
|
173
|
+
report.failures.forEach((failure, index) => {
|
|
174
|
+
const analysis = report.analyses[index];
|
|
175
|
+
lines.push(
|
|
176
|
+
`- **${failure.title}**: ${analysis?.category || "unknown"} / ${analysis?.risk || "high"} / confidence ${analysis?.confidence ?? 0}`
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
await mkdir(path.dirname(output), { recursive: true });
|
|
180
|
+
await writeFile(output, `${lines.join("\n")}
|
|
181
|
+
`, "utf8");
|
|
182
|
+
console.log(`Wrote GitHub summary to ${output}`);
|
|
183
|
+
}
|
|
184
|
+
async function commandAnalyze(args) {
|
|
185
|
+
const input = argValue(args, "--playwright-json");
|
|
186
|
+
if (!input) {
|
|
187
|
+
throw new Error("Missing --playwright-json <path>");
|
|
188
|
+
}
|
|
189
|
+
const output = argValue(args, "--output") || "ai-analysis-report.json";
|
|
190
|
+
const database = argValue(args, "--database") || path.join(".playwright-ai-tools", "ai-tools.sqlite");
|
|
191
|
+
const report = JSON.parse(await readFile(input, "utf8"));
|
|
192
|
+
const failures = normalizePlaywrightJsonReport(report, { runId: `cli-${Date.now()}` });
|
|
193
|
+
const store = new SQLiteStore({ path: database });
|
|
194
|
+
await store.migrate();
|
|
195
|
+
const analyses = await analyzeFailures(failures, { store });
|
|
196
|
+
const portableReport = {
|
|
197
|
+
run: { id: failures[0]?.run?.id || `cli-${Date.now()}`, total: failures.length, failed: failures.length },
|
|
198
|
+
failures,
|
|
199
|
+
analyses
|
|
200
|
+
};
|
|
201
|
+
await mkdir(path.dirname(output), { recursive: true });
|
|
202
|
+
await writeFile(output, `${JSON.stringify(portableReport, null, 2)}
|
|
203
|
+
`, "utf8");
|
|
204
|
+
await writeFile(output.replace(/\.json$/i, ".md"), markdownReport(portableReport), "utf8");
|
|
205
|
+
await writeFile(output.replace(/\.json$/i, ".html"), htmlReport(portableReport), "utf8");
|
|
206
|
+
await store.close();
|
|
207
|
+
console.log(`Analyzed ${failures.length} failure${failures.length === 1 ? "" : "s"}`);
|
|
208
|
+
}
|
|
209
|
+
async function commandSuggestFix(args) {
|
|
210
|
+
const input = argValue(args, "--report");
|
|
211
|
+
if (!input) {
|
|
212
|
+
throw new Error("Missing --report <path>");
|
|
213
|
+
}
|
|
214
|
+
const output = argValue(args, "--output") || "ai-fix-suggestions.json";
|
|
215
|
+
const report = JSON.parse(await readFile(input, "utf8"));
|
|
216
|
+
const suggestions = report.suggestions || report.failures.map(
|
|
217
|
+
(failure, index) => report.analyses[index] ? generatePatchSuggestions(failure, report.analyses[index]) : []
|
|
218
|
+
);
|
|
219
|
+
await mkdir(path.dirname(output), { recursive: true });
|
|
220
|
+
await writeFile(output, `${JSON.stringify({ suggestions }, null, 2)}
|
|
221
|
+
`, "utf8");
|
|
222
|
+
console.log(`Wrote suggestions for ${suggestions.length} failure${suggestions.length === 1 ? "" : "s"}`);
|
|
223
|
+
}
|
|
224
|
+
async function commandBenchmark(args) {
|
|
225
|
+
const input = argValue(args, "--fixtures");
|
|
226
|
+
if (!input) {
|
|
227
|
+
throw new Error("Missing --fixtures <path>");
|
|
228
|
+
}
|
|
229
|
+
const output = argValue(args, "--output");
|
|
230
|
+
const payload = JSON.parse(await readFile(input, "utf8"));
|
|
231
|
+
const fixtures = Array.isArray(payload) ? payload : payload.failures || [];
|
|
232
|
+
const benchmark = benchmarkFailures(fixtures);
|
|
233
|
+
if (output) {
|
|
234
|
+
await mkdir(path.dirname(output), { recursive: true });
|
|
235
|
+
await writeFile(output, `${JSON.stringify(benchmark, null, 2)}
|
|
236
|
+
`, "utf8");
|
|
237
|
+
}
|
|
238
|
+
console.log(
|
|
239
|
+
`Benchmarked ${benchmark.metrics.total} failure${benchmark.metrics.total === 1 ? "" : "s"}: accuracy=${benchmark.metrics.accuracy}, unknownRate=${benchmark.metrics.unknownRate}`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
function generationProvider() {
|
|
243
|
+
return process.env.OPENAI_API_KEY ? new OpenAIResponsesProvider() : new TemplateTestGenerationProvider();
|
|
244
|
+
}
|
|
245
|
+
async function commandGenerateTest(args) {
|
|
246
|
+
const manualCasePath = argValue(args, "--manual-case");
|
|
247
|
+
const url = argValue(args, "--url");
|
|
248
|
+
if (!manualCasePath) throw new Error("Missing --manual-case <json>");
|
|
249
|
+
if (!url) throw new Error("Missing --url <url>");
|
|
250
|
+
const outputDir = argValue(args, "--output-dir") || "generated-tests";
|
|
251
|
+
const manualCase = JSON.parse(await readFile(manualCasePath, "utf8"));
|
|
252
|
+
const draft = await generatePlaywrightTestDraft({
|
|
253
|
+
manualCase,
|
|
254
|
+
url,
|
|
255
|
+
outputDir,
|
|
256
|
+
provider: generationProvider(),
|
|
257
|
+
confirmBrowserRun: hasFlag(args, "--confirm-browser-run"),
|
|
258
|
+
allowDestructiveActions: hasFlag(args, "--allow-destructive-actions"),
|
|
259
|
+
storageState: argValue(args, "--storage-state"),
|
|
260
|
+
sourceName: argValue(args, "--source-name"),
|
|
261
|
+
outputLanguage: argValue(args, "--output-language") || process.env.PW_AI_OUTPUT_LANGUAGE || "ru"
|
|
262
|
+
});
|
|
263
|
+
console.log(`Wrote generated test draft to ${draft.filePath}`);
|
|
264
|
+
console.log(`Wrote generation report to ${draft.reportPath}`);
|
|
265
|
+
}
|
|
266
|
+
async function commandMigrate(args) {
|
|
267
|
+
const database = argValue(args, "--database") || path.join(".playwright-ai-tools", "ai-tools.sqlite");
|
|
268
|
+
const store = new SQLiteStore({ path: database });
|
|
269
|
+
await store.migrate();
|
|
270
|
+
await store.close();
|
|
271
|
+
console.log(`Migrated store at ${database}`);
|
|
272
|
+
}
|
|
273
|
+
async function commandStats(args) {
|
|
274
|
+
const database = argValue(args, "--database") || path.join(".playwright-ai-tools", "ai-tools.sqlite");
|
|
275
|
+
const store = new SQLiteStore({ path: database });
|
|
276
|
+
await store.migrate();
|
|
277
|
+
console.log(JSON.stringify(await store.stats?.(), null, 2));
|
|
278
|
+
await store.close();
|
|
279
|
+
}
|
|
280
|
+
async function main(argv = process.argv.slice(2)) {
|
|
281
|
+
const [command, subcommand, ...rest] = argv;
|
|
282
|
+
if (command === "init") return commandInit();
|
|
283
|
+
if (command === "doctor") return commandDoctor([subcommand, ...rest].filter(Boolean));
|
|
284
|
+
if (command === "analyze") return commandAnalyze([subcommand, ...rest].filter(Boolean));
|
|
285
|
+
if (command === "generate-test") return commandGenerateTest([subcommand, ...rest].filter(Boolean));
|
|
286
|
+
if (command === "suggest-fix") return commandSuggestFix([subcommand, ...rest].filter(Boolean));
|
|
287
|
+
if (command === "benchmark") return commandBenchmark([subcommand, ...rest].filter(Boolean));
|
|
288
|
+
if (command === "migrate") return commandMigrate([subcommand, ...rest].filter(Boolean));
|
|
289
|
+
if (command === "db" && subcommand === "stats") return commandStats(rest);
|
|
290
|
+
if (command === "knowledge" && subcommand === "import") return commandKnowledgeImport(rest);
|
|
291
|
+
if (command === "feedback" && subcommand === "record") return commandFeedbackRecord(rest);
|
|
292
|
+
if (command === "history" && subcommand === "find") return commandHistoryFind(rest);
|
|
293
|
+
if (command === "ci" && subcommand === "github-summary") return commandGithubSummary(rest);
|
|
294
|
+
if (hasFlag(argv, "--help")) {
|
|
295
|
+
console.log("Usage: playwright-ai-tools <init|doctor|analyze|generate-test|suggest-fix|benchmark|migrate|db stats|knowledge import|feedback record|history find|ci github-summary>");
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
throw new Error("Usage: playwright-ai-tools <init|doctor|analyze|generate-test|suggest-fix|benchmark|migrate|db stats|knowledge import|feedback record|history find|ci github-summary>");
|
|
299
|
+
}
|
|
300
|
+
if (process.argv[1] && /(?:playwright-ai-tools|cli\.(?:cjs|js|ts))$/.test(path.basename(process.argv[1]))) {
|
|
301
|
+
main().catch((error) => {
|
|
302
|
+
console.error(error.message);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
export {
|
|
307
|
+
main
|
|
308
|
+
};
|