@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
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyFailure,
|
|
3
|
+
failureSignature,
|
|
4
|
+
generatePatchSuggestions
|
|
5
|
+
} from "./chunk-3VDDPPGG.js";
|
|
6
|
+
|
|
7
|
+
// src/benchmark.ts
|
|
8
|
+
function roundMetric(value) {
|
|
9
|
+
return Number(value.toFixed(4));
|
|
10
|
+
}
|
|
11
|
+
function benchmarkFailures(fixtures) {
|
|
12
|
+
const seenSignatures = /* @__PURE__ */ new Set();
|
|
13
|
+
const failures = fixtures.map((fixture) => {
|
|
14
|
+
const analysis = classifyFailure(fixture);
|
|
15
|
+
const suggestions = generatePatchSuggestions(fixture, analysis);
|
|
16
|
+
const signature = failureSignature(fixture);
|
|
17
|
+
const cacheHit = seenSignatures.has(signature);
|
|
18
|
+
seenSignatures.add(signature);
|
|
19
|
+
const expectedCategory = fixture.expectedCategory || "unknown";
|
|
20
|
+
return {
|
|
21
|
+
title: fixture.title,
|
|
22
|
+
file: fixture.file,
|
|
23
|
+
expectedCategory,
|
|
24
|
+
actualCategory: analysis.category,
|
|
25
|
+
correct: expectedCategory === analysis.category,
|
|
26
|
+
signature,
|
|
27
|
+
cacheHit,
|
|
28
|
+
analysis,
|
|
29
|
+
suggestions
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
const total = failures.length;
|
|
33
|
+
const correct = failures.filter((failure) => failure.correct).length;
|
|
34
|
+
const unknown = failures.filter((failure) => failure.actualCategory === "unknown").length;
|
|
35
|
+
const cacheHits = failures.filter((failure) => failure.cacheHit).length;
|
|
36
|
+
const blocked = failures.reduce(
|
|
37
|
+
(count, failure) => count + failure.suggestions.filter((suggestion) => suggestion.policyDecision === "blocked").length,
|
|
38
|
+
0
|
|
39
|
+
);
|
|
40
|
+
const review = failures.reduce(
|
|
41
|
+
(count, failure) => count + failure.suggestions.filter((suggestion) => suggestion.policyDecision === "review").length,
|
|
42
|
+
0
|
|
43
|
+
);
|
|
44
|
+
return {
|
|
45
|
+
metrics: {
|
|
46
|
+
total,
|
|
47
|
+
correct,
|
|
48
|
+
accuracy: total ? roundMetric(correct / total) : 0,
|
|
49
|
+
unknown,
|
|
50
|
+
unknownRate: total ? roundMetric(unknown / total) : 0,
|
|
51
|
+
cacheHits,
|
|
52
|
+
cacheHitRate: total ? roundMetric(cacheHits / total) : 0,
|
|
53
|
+
suggestions: { blocked, review }
|
|
54
|
+
},
|
|
55
|
+
failures
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/generation.ts
|
|
60
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
61
|
+
import path from "path";
|
|
62
|
+
import { chromium } from "@playwright/test";
|
|
63
|
+
var DESTRUCTIVE_PATTERNS = [
|
|
64
|
+
/\bdelete\b/i,
|
|
65
|
+
/\bremove\b/i,
|
|
66
|
+
/\bdestroy\b/i,
|
|
67
|
+
/\bpay\b/i,
|
|
68
|
+
/\bpayment\b/i,
|
|
69
|
+
/\bpurchase\b/i,
|
|
70
|
+
/\bsubmit\s+order\b/i,
|
|
71
|
+
/\bplace\s+order\b/i,
|
|
72
|
+
/\brefund\b/i,
|
|
73
|
+
/\bcancel\s+subscription\b/i
|
|
74
|
+
];
|
|
75
|
+
function safeSegment(value, fallback) {
|
|
76
|
+
const normalized = (value || fallback).trim().replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 100);
|
|
77
|
+
return normalized || fallback;
|
|
78
|
+
}
|
|
79
|
+
function isLocalUrl(rawUrl) {
|
|
80
|
+
try {
|
|
81
|
+
const url = new URL(rawUrl);
|
|
82
|
+
return ["localhost", "127.0.0.1", "::1"].includes(url.hostname);
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function destructiveText(manualCase) {
|
|
88
|
+
return [
|
|
89
|
+
manualCase.title,
|
|
90
|
+
manualCase.description,
|
|
91
|
+
manualCase.preconditions,
|
|
92
|
+
...manualCase.steps.flatMap((step) => [step.action, step.expected])
|
|
93
|
+
].filter(Boolean).join("\n");
|
|
94
|
+
}
|
|
95
|
+
function isDestructiveManualCase(manualCase) {
|
|
96
|
+
const text = destructiveText(manualCase);
|
|
97
|
+
return DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(text));
|
|
98
|
+
}
|
|
99
|
+
function assertBrowserGenerationAllowed(request) {
|
|
100
|
+
if (!isLocalUrl(request.url) && !request.confirmBrowserRun) {
|
|
101
|
+
throw new Error(`Refusing to open external URL ${request.url}. Pass --confirm-browser-run to allow browser exploration.`);
|
|
102
|
+
}
|
|
103
|
+
if (isDestructiveManualCase(request.manualCase) && !request.allowDestructiveActions) {
|
|
104
|
+
throw new Error("Manual test case appears to contain destructive actions. Pass --allow-destructive-actions to override.");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function truncate(value, maxLength) {
|
|
108
|
+
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
109
|
+
}
|
|
110
|
+
function defaultSource(manualCase) {
|
|
111
|
+
return manualCase.source?.provider || manualCase.source?.project || "manual";
|
|
112
|
+
}
|
|
113
|
+
var TemplateTestGenerationProvider = class {
|
|
114
|
+
async generateTest(request) {
|
|
115
|
+
const title = request.manualCase.title.replace(/'/g, "\\'");
|
|
116
|
+
const steps = request.manualCase.steps.map((step) => ` // Step ${step.index ?? ""}: ${step.action}${step.expected ? ` -> ${step.expected}` : ""}`.trimEnd()).join("\n");
|
|
117
|
+
return {
|
|
118
|
+
code: [
|
|
119
|
+
"import { test, expect } from '@playwright/test';",
|
|
120
|
+
"",
|
|
121
|
+
`test('${title}', async ({ page }) => {`,
|
|
122
|
+
` await page.goto('${request.url}');`,
|
|
123
|
+
steps || " // No manual steps were supplied.",
|
|
124
|
+
" await expect(page).toHaveURL(/./);",
|
|
125
|
+
"});"
|
|
126
|
+
].join("\n"),
|
|
127
|
+
locators: [],
|
|
128
|
+
coverage: request.manualCase.steps.map((step) => ({
|
|
129
|
+
stepIndex: step.index,
|
|
130
|
+
status: "partial",
|
|
131
|
+
notes: "Template provider preserved the manual step as a draft comment."
|
|
132
|
+
})),
|
|
133
|
+
warnings: ["Template provider generated a draft without AI locator reasoning. Review before committing."]
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
async function generatePlaywrightTestDraft(request) {
|
|
138
|
+
assertBrowserGenerationAllowed(request);
|
|
139
|
+
const outputDir = request.outputDir || "generated-tests";
|
|
140
|
+
const source = safeSegment(request.sourceName || defaultSource(request.manualCase), "manual");
|
|
141
|
+
const caseKey = safeSegment(request.manualCase.key || request.manualCase.id, "case");
|
|
142
|
+
const caseDir = path.join(outputDir, source);
|
|
143
|
+
const filePath = path.join(caseDir, `${caseKey}.spec.ts`);
|
|
144
|
+
const reportPath = path.join(caseDir, `${caseKey}.generation-report.json`);
|
|
145
|
+
const screenshotPath = path.join(caseDir, `${caseKey}.png`);
|
|
146
|
+
await mkdir(caseDir, { recursive: true });
|
|
147
|
+
const browser = await chromium.launch({ headless: true });
|
|
148
|
+
try {
|
|
149
|
+
const context = await browser.newContext(request.storageState ? { storageState: request.storageState } : {});
|
|
150
|
+
try {
|
|
151
|
+
const page = await context.newPage();
|
|
152
|
+
await page.goto(request.url, { waitUntil: "domcontentloaded" });
|
|
153
|
+
const screenshot = await page.screenshot({ fullPage: true });
|
|
154
|
+
const [title, html, bodyText] = await Promise.all([
|
|
155
|
+
page.title(),
|
|
156
|
+
page.content(),
|
|
157
|
+
page.locator("body").innerText({ timeout: 2e3 }).catch(() => "")
|
|
158
|
+
]);
|
|
159
|
+
await writeFile(screenshotPath, screenshot);
|
|
160
|
+
const browserEvidence = {
|
|
161
|
+
requestedUrl: request.url,
|
|
162
|
+
currentUrl: page.url(),
|
|
163
|
+
title,
|
|
164
|
+
domSummary: truncate(bodyText, 8e3),
|
|
165
|
+
htmlSnippet: truncate(html, 12e3),
|
|
166
|
+
screenshotBase64: screenshot.toString("base64")
|
|
167
|
+
};
|
|
168
|
+
const generated = await request.provider.generateTest({
|
|
169
|
+
manualCase: request.manualCase,
|
|
170
|
+
url: request.url,
|
|
171
|
+
browser: browserEvidence,
|
|
172
|
+
outputLanguage: request.outputLanguage
|
|
173
|
+
});
|
|
174
|
+
const draft = {
|
|
175
|
+
manualCase: request.manualCase,
|
|
176
|
+
filePath,
|
|
177
|
+
reportPath,
|
|
178
|
+
screenshotPath,
|
|
179
|
+
code: generated.code,
|
|
180
|
+
locators: generated.locators || [],
|
|
181
|
+
coverage: generated.coverage || [],
|
|
182
|
+
warnings: generated.warnings || [],
|
|
183
|
+
browser: {
|
|
184
|
+
requestedUrl: browserEvidence.requestedUrl,
|
|
185
|
+
currentUrl: browserEvidence.currentUrl,
|
|
186
|
+
title: browserEvidence.title,
|
|
187
|
+
domSummary: browserEvidence.domSummary,
|
|
188
|
+
htmlSnippet: browserEvidence.htmlSnippet
|
|
189
|
+
},
|
|
190
|
+
needsHumanReview: true
|
|
191
|
+
};
|
|
192
|
+
const report = {
|
|
193
|
+
manualCase: request.manualCase,
|
|
194
|
+
browser: draft.browser,
|
|
195
|
+
locators: draft.locators,
|
|
196
|
+
coverage: draft.coverage,
|
|
197
|
+
warnings: draft.warnings,
|
|
198
|
+
needsHumanReview: draft.needsHumanReview,
|
|
199
|
+
artifacts: { filePath, reportPath, screenshotPath }
|
|
200
|
+
};
|
|
201
|
+
await writeFile(filePath, `${generated.code.trimEnd()}
|
|
202
|
+
`, "utf8");
|
|
203
|
+
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}
|
|
204
|
+
`, "utf8");
|
|
205
|
+
return draft;
|
|
206
|
+
} finally {
|
|
207
|
+
await context.close();
|
|
208
|
+
}
|
|
209
|
+
} finally {
|
|
210
|
+
await browser.close();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/normalize.ts
|
|
215
|
+
function walkSuites(suites, visitor) {
|
|
216
|
+
for (const suite of suites || []) {
|
|
217
|
+
for (const spec of suite.specs || []) {
|
|
218
|
+
visitor(spec, suite);
|
|
219
|
+
}
|
|
220
|
+
walkSuites(suite.suites || [], visitor);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function resultError(result) {
|
|
224
|
+
return result?.error?.message || result?.error?.stack || result?.errors?.[0]?.message || result?.errors?.[0]?.stack || "Unknown error";
|
|
225
|
+
}
|
|
226
|
+
function normalizeSteps(result) {
|
|
227
|
+
const collected = [];
|
|
228
|
+
const visit = (step, insideUserStep) => {
|
|
229
|
+
if (!step) return;
|
|
230
|
+
const category = step.category;
|
|
231
|
+
const isUserStep = category === "test.step";
|
|
232
|
+
const isExpect = category === "expect";
|
|
233
|
+
if ((isUserStep || isExpect) && !insideUserStep && step.title) {
|
|
234
|
+
collected.push(String(step.title));
|
|
235
|
+
}
|
|
236
|
+
for (const child of step.steps || []) {
|
|
237
|
+
visit(child, insideUserStep || isUserStep);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
for (const step of result?.steps || []) {
|
|
241
|
+
visit(step, false);
|
|
242
|
+
}
|
|
243
|
+
return collected;
|
|
244
|
+
}
|
|
245
|
+
function attachmentType(contentType, name) {
|
|
246
|
+
if (contentType?.startsWith("image/")) return "screenshot";
|
|
247
|
+
if (contentType?.includes("zip")) return "trace";
|
|
248
|
+
if (contentType?.includes("video")) return "video";
|
|
249
|
+
if (name?.toLowerCase().includes("trace")) return "trace";
|
|
250
|
+
return name || "artifact";
|
|
251
|
+
}
|
|
252
|
+
function normalizeArtifacts(result) {
|
|
253
|
+
return (result?.attachments || []).filter((attachment) => attachment?.path).map((attachment) => ({
|
|
254
|
+
type: attachmentType(attachment.contentType, attachment.name),
|
|
255
|
+
path: String(attachment.path),
|
|
256
|
+
contentType: attachment.contentType
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
function normalizePlaywrightJsonReport(report, options = {}) {
|
|
260
|
+
const failures = [];
|
|
261
|
+
const run = options.runId ? { id: options.runId } : void 0;
|
|
262
|
+
walkSuites(report?.suites || [], (spec) => {
|
|
263
|
+
for (const testCase of spec.tests || []) {
|
|
264
|
+
for (const result of testCase.results || []) {
|
|
265
|
+
if (!["failed", "timedOut", "interrupted"].includes(result.status)) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
failures.push({
|
|
269
|
+
title: String(spec.title || testCase.title || "unnamed test"),
|
|
270
|
+
file: String(spec.file || testCase.location?.file || "unknown"),
|
|
271
|
+
line: spec.line || testCase.location?.line,
|
|
272
|
+
project: testCase.projectName,
|
|
273
|
+
status: result.status,
|
|
274
|
+
retry: result.retry || 0,
|
|
275
|
+
durationMs: result.duration || 0,
|
|
276
|
+
error: resultError(result),
|
|
277
|
+
steps: normalizeSteps(result),
|
|
278
|
+
artifacts: normalizeArtifacts(result),
|
|
279
|
+
annotations: (testCase.annotations || []).map((annotation) => ({
|
|
280
|
+
type: String(annotation.type),
|
|
281
|
+
description: annotation.description
|
|
282
|
+
})),
|
|
283
|
+
run
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return failures;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/providers/openai.ts
|
|
292
|
+
var analysisSchema = {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
side: { type: "string", enum: ["app_bug", "test_bug", "flaky_test", "env_issue", "unknown"] },
|
|
296
|
+
rootCause: { type: "string" },
|
|
297
|
+
evidence: { type: "array", items: { type: "string" } },
|
|
298
|
+
reproductionSteps: { type: "array", items: { type: "string" } },
|
|
299
|
+
suspects: {
|
|
300
|
+
type: "object",
|
|
301
|
+
properties: {
|
|
302
|
+
selectors: { type: "array", items: { type: "string" } },
|
|
303
|
+
pages: { type: "array", items: { type: "string" } },
|
|
304
|
+
endpoints: { type: "array", items: { type: "string" } }
|
|
305
|
+
},
|
|
306
|
+
required: ["selectors", "pages", "endpoints"],
|
|
307
|
+
additionalProperties: false
|
|
308
|
+
},
|
|
309
|
+
fixSuggestions: { type: "array", items: { type: "string" } },
|
|
310
|
+
confidence: { type: "number", minimum: 0, maximum: 100 },
|
|
311
|
+
category: {
|
|
312
|
+
type: "string",
|
|
313
|
+
enum: ["selector", "timing", "runtime", "test_data", "visual", "interaction", "environment", "unknown"]
|
|
314
|
+
},
|
|
315
|
+
risk: { type: "string", enum: ["low", "medium", "high"] },
|
|
316
|
+
evidenceRefs: { type: "array", items: { type: "string" } },
|
|
317
|
+
confidenceReasons: { type: "array", items: { type: "string" } },
|
|
318
|
+
needsHumanReview: { type: "boolean" }
|
|
319
|
+
},
|
|
320
|
+
required: [
|
|
321
|
+
"side",
|
|
322
|
+
"rootCause",
|
|
323
|
+
"evidence",
|
|
324
|
+
"reproductionSteps",
|
|
325
|
+
"suspects",
|
|
326
|
+
"fixSuggestions",
|
|
327
|
+
"confidence",
|
|
328
|
+
"category",
|
|
329
|
+
"risk",
|
|
330
|
+
"evidenceRefs",
|
|
331
|
+
"confidenceReasons",
|
|
332
|
+
"needsHumanReview"
|
|
333
|
+
],
|
|
334
|
+
additionalProperties: false
|
|
335
|
+
};
|
|
336
|
+
var generationSchema = {
|
|
337
|
+
type: "object",
|
|
338
|
+
properties: {
|
|
339
|
+
code: { type: "string" },
|
|
340
|
+
locators: {
|
|
341
|
+
type: "array",
|
|
342
|
+
items: {
|
|
343
|
+
type: "object",
|
|
344
|
+
properties: {
|
|
345
|
+
stepIndex: { type: "number" },
|
|
346
|
+
selector: { type: "string" },
|
|
347
|
+
reason: { type: "string" }
|
|
348
|
+
},
|
|
349
|
+
required: ["selector", "reason"],
|
|
350
|
+
additionalProperties: false
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
coverage: {
|
|
354
|
+
type: "array",
|
|
355
|
+
items: {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
stepIndex: { type: "number" },
|
|
359
|
+
status: { type: "string", enum: ["covered", "partial", "missing"] },
|
|
360
|
+
notes: { type: "string" }
|
|
361
|
+
},
|
|
362
|
+
required: ["status", "notes"],
|
|
363
|
+
additionalProperties: false
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
warnings: { type: "array", items: { type: "string" } }
|
|
367
|
+
},
|
|
368
|
+
required: ["code", "locators", "coverage", "warnings"],
|
|
369
|
+
additionalProperties: false
|
|
370
|
+
};
|
|
371
|
+
function normalizeAnalysis(data, usage) {
|
|
372
|
+
return {
|
|
373
|
+
side: data.side || "unknown",
|
|
374
|
+
rootCause: data.rootCause || data.root_cause || "No root cause returned",
|
|
375
|
+
evidence: data.evidence || [],
|
|
376
|
+
reproductionSteps: data.reproductionSteps || data.reproduction_steps || [],
|
|
377
|
+
suspects: data.suspects || { selectors: [], pages: [], endpoints: [] },
|
|
378
|
+
fixSuggestions: data.fixSuggestions || data.fix || [],
|
|
379
|
+
confidence: Number(data.confidence ?? 0),
|
|
380
|
+
category: data.category || "unknown",
|
|
381
|
+
risk: data.risk || "high",
|
|
382
|
+
evidenceRefs: data.evidenceRefs || [],
|
|
383
|
+
confidenceReasons: data.confidenceReasons || [],
|
|
384
|
+
needsHumanReview: data.needsHumanReview ?? true,
|
|
385
|
+
source: "ai",
|
|
386
|
+
tokenUsage: usage ? {
|
|
387
|
+
inputTokens: usage.input_tokens || usage.inputTokens || 0,
|
|
388
|
+
outputTokens: usage.output_tokens || usage.outputTokens || 0,
|
|
389
|
+
totalTokens: usage.total_tokens || usage.totalTokens || 0
|
|
390
|
+
} : void 0
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
function normalizeGeneratedTest(data) {
|
|
394
|
+
return {
|
|
395
|
+
code: String(data.code || ""),
|
|
396
|
+
locators: Array.isArray(data.locators) ? data.locators : [],
|
|
397
|
+
coverage: Array.isArray(data.coverage) ? data.coverage : [],
|
|
398
|
+
warnings: Array.isArray(data.warnings) ? data.warnings : []
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function outputText(response) {
|
|
402
|
+
if (response.output_text) return response.output_text;
|
|
403
|
+
for (const item of response.output || []) {
|
|
404
|
+
for (const content of item.content || []) {
|
|
405
|
+
if (content.text) return content.text;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return "";
|
|
409
|
+
}
|
|
410
|
+
function compactFailure(failure) {
|
|
411
|
+
return {
|
|
412
|
+
title: failure.title,
|
|
413
|
+
file: failure.file,
|
|
414
|
+
line: failure.line,
|
|
415
|
+
project: failure.project,
|
|
416
|
+
status: failure.status,
|
|
417
|
+
retry: failure.retry,
|
|
418
|
+
durationMs: failure.durationMs,
|
|
419
|
+
error: failure.error,
|
|
420
|
+
steps: failure.steps || [],
|
|
421
|
+
artifacts: failure.artifacts || [],
|
|
422
|
+
annotations: failure.annotations || [],
|
|
423
|
+
browserContext: failure.browserContext,
|
|
424
|
+
page: failure.page,
|
|
425
|
+
consoleErrors: failure.consoleErrors || [],
|
|
426
|
+
networkFailures: failure.networkFailures || [],
|
|
427
|
+
lastActions: failure.lastActions || [],
|
|
428
|
+
artifactRefs: failure.artifactRefs || failure.artifacts || []
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
var OpenAIResponsesProvider = class {
|
|
432
|
+
constructor(options = {}) {
|
|
433
|
+
this.apiKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? "";
|
|
434
|
+
this.model = options.model ?? process.env.PW_AI_MODEL ?? process.env.AI_MODEL ?? "gpt-4.1-mini";
|
|
435
|
+
this.baseUrl = options.baseUrl ?? "https://api.openai.com/v1/responses";
|
|
436
|
+
this.transport = options.transport ?? ((url, init) => fetch(url, init));
|
|
437
|
+
}
|
|
438
|
+
async analyzeFailure(request) {
|
|
439
|
+
if (!this.apiKey) {
|
|
440
|
+
throw new Error("OPENAI_API_KEY is required for OpenAIResponsesProvider");
|
|
441
|
+
}
|
|
442
|
+
const payload = {
|
|
443
|
+
model: this.model,
|
|
444
|
+
instructions: `You are a generic Playwright failure analyzer. Return concise ${request.outputLanguage} JSON only. Use only the supplied test failure, trace evidence, steps, artifacts, and similar analyses. Every conclusion must point to supplied evidenceRefs.`,
|
|
445
|
+
input: JSON.stringify({
|
|
446
|
+
failure: compactFailure(request.failure),
|
|
447
|
+
similarAnalyses: request.similarAnalyses || []
|
|
448
|
+
}),
|
|
449
|
+
text: {
|
|
450
|
+
format: {
|
|
451
|
+
type: "json_schema",
|
|
452
|
+
name: "playwright_failure_analysis",
|
|
453
|
+
strict: true,
|
|
454
|
+
schema: analysisSchema
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
const response = await this.transport(this.baseUrl, {
|
|
459
|
+
method: "POST",
|
|
460
|
+
headers: {
|
|
461
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
462
|
+
"content-type": "application/json"
|
|
463
|
+
},
|
|
464
|
+
body: JSON.stringify(payload)
|
|
465
|
+
});
|
|
466
|
+
if (!response.ok) {
|
|
467
|
+
throw new Error(`OpenAI Responses request failed with status ${response.status}`);
|
|
468
|
+
}
|
|
469
|
+
const body = await response.json();
|
|
470
|
+
const rawText = outputText(body);
|
|
471
|
+
if (!rawText) {
|
|
472
|
+
throw new Error("OpenAI Responses payload did not contain output text");
|
|
473
|
+
}
|
|
474
|
+
return normalizeAnalysis(JSON.parse(rawText), body.usage);
|
|
475
|
+
}
|
|
476
|
+
async generateTest(request) {
|
|
477
|
+
if (!this.apiKey) {
|
|
478
|
+
throw new Error("OPENAI_API_KEY is required for OpenAIResponsesProvider");
|
|
479
|
+
}
|
|
480
|
+
const browser = {
|
|
481
|
+
requestedUrl: request.browser.requestedUrl,
|
|
482
|
+
currentUrl: request.browser.currentUrl,
|
|
483
|
+
title: request.browser.title,
|
|
484
|
+
domSummary: request.browser.domSummary,
|
|
485
|
+
htmlSnippet: request.browser.htmlSnippet
|
|
486
|
+
};
|
|
487
|
+
const content = [
|
|
488
|
+
{
|
|
489
|
+
type: "input_text",
|
|
490
|
+
text: JSON.stringify({
|
|
491
|
+
manualCase: request.manualCase,
|
|
492
|
+
url: request.url,
|
|
493
|
+
browser
|
|
494
|
+
})
|
|
495
|
+
}
|
|
496
|
+
];
|
|
497
|
+
if (request.browser.screenshotBase64) {
|
|
498
|
+
content.push({
|
|
499
|
+
type: "input_image",
|
|
500
|
+
image_url: `data:image/png;base64,${request.browser.screenshotBase64}`
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
const payload = {
|
|
504
|
+
model: this.model,
|
|
505
|
+
instructions: `You generate review-only Playwright tests from manual test cases. Return concise ${request.outputLanguage || "ru"} JSON only. Use only the supplied manual steps, browser DOM evidence, URL, and screenshot. Prefer user-facing locators and data-testid selectors when the evidence supports them. Do not weaken assertions, do not invent credentials, and include warnings for any uncertain step.`,
|
|
506
|
+
input: [{ role: "user", content }],
|
|
507
|
+
text: {
|
|
508
|
+
format: {
|
|
509
|
+
type: "json_schema",
|
|
510
|
+
name: "playwright_test_generation",
|
|
511
|
+
strict: true,
|
|
512
|
+
schema: generationSchema
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
const response = await this.transport(this.baseUrl, {
|
|
517
|
+
method: "POST",
|
|
518
|
+
headers: {
|
|
519
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
520
|
+
"content-type": "application/json"
|
|
521
|
+
},
|
|
522
|
+
body: JSON.stringify(payload)
|
|
523
|
+
});
|
|
524
|
+
if (!response.ok) {
|
|
525
|
+
throw new Error(`OpenAI Responses request failed with status ${response.status}`);
|
|
526
|
+
}
|
|
527
|
+
const body = await response.json();
|
|
528
|
+
const rawText = outputText(body);
|
|
529
|
+
if (!rawText) {
|
|
530
|
+
throw new Error("OpenAI Responses payload did not contain output text");
|
|
531
|
+
}
|
|
532
|
+
return normalizeGeneratedTest(JSON.parse(rawText));
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export {
|
|
537
|
+
benchmarkFailures,
|
|
538
|
+
isDestructiveManualCase,
|
|
539
|
+
assertBrowserGenerationAllowed,
|
|
540
|
+
TemplateTestGenerationProvider,
|
|
541
|
+
generatePlaywrightTestDraft,
|
|
542
|
+
normalizePlaywrightJsonReport,
|
|
543
|
+
OpenAIResponsesProvider
|
|
544
|
+
};
|