@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/index.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OpenAIResponsesProvider,
|
|
3
|
+
TemplateTestGenerationProvider,
|
|
4
|
+
assertBrowserGenerationAllowed,
|
|
5
|
+
benchmarkFailures,
|
|
6
|
+
generatePlaywrightTestDraft,
|
|
7
|
+
isDestructiveManualCase,
|
|
8
|
+
normalizePlaywrightJsonReport
|
|
9
|
+
} from "./chunk-GSXFV7YQ.js";
|
|
10
|
+
import {
|
|
11
|
+
enrichFailureWithArtifacts,
|
|
12
|
+
parseTraceArtifact
|
|
13
|
+
} from "./chunk-QL3XW2UN.js";
|
|
14
|
+
import {
|
|
15
|
+
DeterministicAnalyzer,
|
|
16
|
+
FailureGrouper,
|
|
17
|
+
SQLiteStore,
|
|
18
|
+
analyzeFailures,
|
|
19
|
+
classifyFailure,
|
|
20
|
+
evaluatePatchPolicy,
|
|
21
|
+
failureSignature,
|
|
22
|
+
generatePatchSuggestions,
|
|
23
|
+
htmlReport,
|
|
24
|
+
markdownReport,
|
|
25
|
+
mcpSummary,
|
|
26
|
+
writePortableReport
|
|
27
|
+
} from "./chunk-3VDDPPGG.js";
|
|
28
|
+
|
|
29
|
+
// src/stores/memory.ts
|
|
30
|
+
function now() {
|
|
31
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
32
|
+
}
|
|
33
|
+
function normalizeTags(tags) {
|
|
34
|
+
return [...new Set(tags || [])].filter(Boolean).sort();
|
|
35
|
+
}
|
|
36
|
+
function recordText(record) {
|
|
37
|
+
return JSON.stringify([record.key, record.kind, record.source, record.tags, record.value]).toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
var MemoryStore = class {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.runs = /* @__PURE__ */ new Map();
|
|
42
|
+
this.failures = [];
|
|
43
|
+
this.analyses = /* @__PURE__ */ new Map();
|
|
44
|
+
this.memory = /* @__PURE__ */ new Map();
|
|
45
|
+
this.feedback = [];
|
|
46
|
+
}
|
|
47
|
+
async migrate() {
|
|
48
|
+
}
|
|
49
|
+
async saveRun(run) {
|
|
50
|
+
this.runs.set(run.id, run);
|
|
51
|
+
}
|
|
52
|
+
async saveFailure(failure) {
|
|
53
|
+
this.failures.push(failure);
|
|
54
|
+
}
|
|
55
|
+
async saveAnalysis(signature, analysis) {
|
|
56
|
+
this.analyses.set(signature, analysis);
|
|
57
|
+
}
|
|
58
|
+
async getCachedAnalysis(signature) {
|
|
59
|
+
const cached = this.analyses.get(signature);
|
|
60
|
+
return cached ? { ...cached, source: "cache" } : null;
|
|
61
|
+
}
|
|
62
|
+
async upsertMemory(input) {
|
|
63
|
+
const key = `${input.namespace}:${input.kind}:${input.key}`;
|
|
64
|
+
const existing = this.memory.get(key);
|
|
65
|
+
this.memory.set(key, {
|
|
66
|
+
namespace: input.namespace,
|
|
67
|
+
kind: input.kind,
|
|
68
|
+
key: input.key,
|
|
69
|
+
value: input.value,
|
|
70
|
+
tags: normalizeTags(input.tags),
|
|
71
|
+
confidence: input.confidence ?? 1,
|
|
72
|
+
source: input.source,
|
|
73
|
+
supersededBy: existing?.supersededBy ?? null,
|
|
74
|
+
createdAt: existing?.createdAt ?? now(),
|
|
75
|
+
updatedAt: now()
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async searchMemory(input) {
|
|
79
|
+
const query = input.query?.toLowerCase().trim();
|
|
80
|
+
const tags = new Set(input.tags || []);
|
|
81
|
+
const matches = [...this.memory.values()].filter((record) => {
|
|
82
|
+
if (input.namespace && record.namespace !== input.namespace) return false;
|
|
83
|
+
if (input.kind && record.kind !== input.kind) return false;
|
|
84
|
+
if (tags.size > 0 && !record.tags.some((tag) => tags.has(tag))) return false;
|
|
85
|
+
if (query && !recordText(record).includes(query)) return false;
|
|
86
|
+
return !record.supersededBy;
|
|
87
|
+
});
|
|
88
|
+
return matches.slice(0, input.limit ?? 20);
|
|
89
|
+
}
|
|
90
|
+
async recordFeedback(input) {
|
|
91
|
+
this.feedback.push({
|
|
92
|
+
signature: input.signature,
|
|
93
|
+
decision: input.decision,
|
|
94
|
+
reason: input.reason || "",
|
|
95
|
+
metadata: input.metadata || {},
|
|
96
|
+
createdAt: now()
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async searchFeedback(input) {
|
|
100
|
+
const query = input.query?.toLowerCase().trim();
|
|
101
|
+
return this.feedback.filter((record) => {
|
|
102
|
+
if (input.signature && record.signature !== input.signature) return false;
|
|
103
|
+
if (input.decision && record.decision !== input.decision) return false;
|
|
104
|
+
if (query && !JSON.stringify(record).toLowerCase().includes(query)) return false;
|
|
105
|
+
return true;
|
|
106
|
+
}).slice(0, input.limit ?? 20);
|
|
107
|
+
}
|
|
108
|
+
async findFailures(input) {
|
|
109
|
+
const query = input.query?.toLowerCase().trim();
|
|
110
|
+
return this.failures.filter((failure) => {
|
|
111
|
+
if (input.signature && failureSignature(failure) !== input.signature) return false;
|
|
112
|
+
if (input.file && failure.file !== input.file) return false;
|
|
113
|
+
if (query && !JSON.stringify(failure).toLowerCase().includes(query)) return false;
|
|
114
|
+
return true;
|
|
115
|
+
}).slice(0, input.limit ?? 20);
|
|
116
|
+
}
|
|
117
|
+
async getRun(id) {
|
|
118
|
+
return this.runs.get(id) || null;
|
|
119
|
+
}
|
|
120
|
+
async stats() {
|
|
121
|
+
return {
|
|
122
|
+
runs: this.runs.size,
|
|
123
|
+
failures: this.failures.length,
|
|
124
|
+
analyses: this.analyses.size,
|
|
125
|
+
memoryRecords: this.memory.size,
|
|
126
|
+
feedbackRecords: this.feedback.length
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async close() {
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/stores/postgres.ts
|
|
134
|
+
var PostgresStore = class extends MemoryStore {
|
|
135
|
+
constructor(executor) {
|
|
136
|
+
super();
|
|
137
|
+
this.executor = executor;
|
|
138
|
+
}
|
|
139
|
+
async migrate() {
|
|
140
|
+
if (!this.executor) return;
|
|
141
|
+
await this.executor.query(`
|
|
142
|
+
CREATE TABLE IF NOT EXISTS pw_ai_runs (id text PRIMARY KEY, payload jsonb NOT NULL, created_at timestamptz DEFAULT now());
|
|
143
|
+
CREATE TABLE IF NOT EXISTS pw_ai_failures (id bigserial PRIMARY KEY, signature text, file text, title text, run_id text, payload jsonb NOT NULL, created_at timestamptz DEFAULT now());
|
|
144
|
+
CREATE TABLE IF NOT EXISTS pw_ai_analyses (signature text PRIMARY KEY, payload jsonb NOT NULL, updated_at timestamptz DEFAULT now());
|
|
145
|
+
CREATE TABLE IF NOT EXISTS pw_ai_memory (id text PRIMARY KEY, namespace text NOT NULL, kind text NOT NULL, key text NOT NULL, tags text[] DEFAULT '{}', payload jsonb NOT NULL, updated_at timestamptz DEFAULT now());
|
|
146
|
+
CREATE TABLE IF NOT EXISTS pw_ai_feedback (id bigserial PRIMARY KEY, signature text NOT NULL, decision text NOT NULL, reason text, payload jsonb NOT NULL, created_at timestamptz DEFAULT now());
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_pw_ai_failures_signature ON pw_ai_failures(signature);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS idx_pw_ai_memory_namespace_kind ON pw_ai_memory(namespace, kind);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_pw_ai_feedback_signature ON pw_ai_feedback(signature);
|
|
150
|
+
`);
|
|
151
|
+
}
|
|
152
|
+
async saveRun(run) {
|
|
153
|
+
if (!this.executor) return super.saveRun(run);
|
|
154
|
+
await this.executor.query(
|
|
155
|
+
"INSERT INTO pw_ai_runs (id, payload) VALUES ($1, $2::jsonb) ON CONFLICT (id) DO UPDATE SET payload = EXCLUDED.payload",
|
|
156
|
+
[run.id, JSON.stringify(run)]
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
async saveFailure(failure) {
|
|
160
|
+
if (!this.executor) return super.saveFailure(failure);
|
|
161
|
+
await this.executor.query(
|
|
162
|
+
"INSERT INTO pw_ai_failures (signature, file, title, run_id, payload) VALUES ($1, $2, $3, $4, $5::jsonb)",
|
|
163
|
+
[failureSignature(failure), failure.file, failure.title, typeof failure.run?.id === "string" ? failure.run.id : null, JSON.stringify(failure)]
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
async saveAnalysis(signature, analysis) {
|
|
167
|
+
if (!this.executor) return super.saveAnalysis(signature, analysis);
|
|
168
|
+
await this.executor.query(
|
|
169
|
+
"INSERT INTO pw_ai_analyses (signature, payload) VALUES ($1, $2::jsonb) ON CONFLICT (signature) DO UPDATE SET payload = EXCLUDED.payload",
|
|
170
|
+
[signature, JSON.stringify(analysis)]
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
async getCachedAnalysis(signature) {
|
|
174
|
+
if (!this.executor) return super.getCachedAnalysis(signature);
|
|
175
|
+
const result = await this.executor.query("SELECT payload FROM pw_ai_analyses WHERE signature = $1 LIMIT 1", [
|
|
176
|
+
signature
|
|
177
|
+
]);
|
|
178
|
+
return result.rows[0]?.payload ? { ...result.rows[0].payload, source: "cache" } : null;
|
|
179
|
+
}
|
|
180
|
+
async upsertMemory(record) {
|
|
181
|
+
if (!this.executor) return super.upsertMemory(record);
|
|
182
|
+
const id = `${record.namespace}:${record.kind}:${record.key}`;
|
|
183
|
+
const tags = [...new Set(record.tags || [])].filter(Boolean).sort();
|
|
184
|
+
const payload = {
|
|
185
|
+
namespace: record.namespace,
|
|
186
|
+
kind: record.kind,
|
|
187
|
+
key: record.key,
|
|
188
|
+
value: record.value,
|
|
189
|
+
tags,
|
|
190
|
+
confidence: record.confidence ?? 1,
|
|
191
|
+
source: record.source,
|
|
192
|
+
supersededBy: null,
|
|
193
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
194
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
195
|
+
};
|
|
196
|
+
await this.executor.query(
|
|
197
|
+
`
|
|
198
|
+
INSERT INTO pw_ai_memory (id, namespace, kind, key, tags, payload)
|
|
199
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb)
|
|
200
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
201
|
+
namespace = EXCLUDED.namespace,
|
|
202
|
+
kind = EXCLUDED.kind,
|
|
203
|
+
key = EXCLUDED.key,
|
|
204
|
+
tags = EXCLUDED.tags,
|
|
205
|
+
payload = EXCLUDED.payload,
|
|
206
|
+
updated_at = now()
|
|
207
|
+
`,
|
|
208
|
+
[id, payload.namespace, payload.kind, payload.key, payload.tags, JSON.stringify(payload)]
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
async searchMemory(input) {
|
|
212
|
+
if (!this.executor) return super.searchMemory(input);
|
|
213
|
+
const result = await this.executor.query("SELECT payload FROM pw_ai_memory ORDER BY updated_at DESC LIMIT $1", [
|
|
214
|
+
input.limit ?? 100
|
|
215
|
+
]);
|
|
216
|
+
const query = input.query?.toLowerCase().trim();
|
|
217
|
+
const tags = new Set(input.tags || []);
|
|
218
|
+
return result.rows.map((row) => row.payload).filter((record) => {
|
|
219
|
+
if (input.namespace && record.namespace !== input.namespace) return false;
|
|
220
|
+
if (input.kind && record.kind !== input.kind) return false;
|
|
221
|
+
if (tags.size > 0 && !record.tags.some((tag) => tags.has(tag))) return false;
|
|
222
|
+
if (query && !JSON.stringify(record).toLowerCase().includes(query)) return false;
|
|
223
|
+
return !record.supersededBy;
|
|
224
|
+
}).slice(0, input.limit ?? 20);
|
|
225
|
+
}
|
|
226
|
+
async recordFeedback(record) {
|
|
227
|
+
if (!this.executor) return super.recordFeedback(record);
|
|
228
|
+
const payload = {
|
|
229
|
+
signature: record.signature,
|
|
230
|
+
decision: record.decision,
|
|
231
|
+
reason: record.reason || "",
|
|
232
|
+
metadata: record.metadata || {},
|
|
233
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
234
|
+
};
|
|
235
|
+
await this.executor.query(
|
|
236
|
+
"INSERT INTO pw_ai_feedback (signature, decision, reason, payload) VALUES ($1, $2, $3, $4::jsonb)",
|
|
237
|
+
[payload.signature, payload.decision, payload.reason, JSON.stringify(payload)]
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
async searchFeedback(input) {
|
|
241
|
+
if (!this.executor) return super.searchFeedback(input);
|
|
242
|
+
const result = await this.executor.query("SELECT payload FROM pw_ai_feedback ORDER BY created_at DESC LIMIT $1", [
|
|
243
|
+
input.limit ?? 100
|
|
244
|
+
]);
|
|
245
|
+
const query = input.query?.toLowerCase().trim();
|
|
246
|
+
return result.rows.map((row) => row.payload).filter((record) => {
|
|
247
|
+
if (input.signature && record.signature !== input.signature) return false;
|
|
248
|
+
if (input.decision && record.decision !== input.decision) return false;
|
|
249
|
+
if (query && !JSON.stringify(record).toLowerCase().includes(query)) return false;
|
|
250
|
+
return true;
|
|
251
|
+
}).slice(0, input.limit ?? 20);
|
|
252
|
+
}
|
|
253
|
+
async findFailures(input) {
|
|
254
|
+
if (!this.executor) return super.findFailures(input);
|
|
255
|
+
const result = await this.executor.query("SELECT payload FROM pw_ai_failures ORDER BY id DESC LIMIT $1", [
|
|
256
|
+
input.limit ?? 100
|
|
257
|
+
]);
|
|
258
|
+
const query = input.query?.toLowerCase().trim();
|
|
259
|
+
return result.rows.map((row) => row.payload).filter((failure) => {
|
|
260
|
+
if (input.signature && failureSignature(failure) !== input.signature) return false;
|
|
261
|
+
if (input.file && failure.file !== input.file) return false;
|
|
262
|
+
if (query && !JSON.stringify(failure).toLowerCase().includes(query)) return false;
|
|
263
|
+
return true;
|
|
264
|
+
}).slice(0, input.limit ?? 20);
|
|
265
|
+
}
|
|
266
|
+
async close() {
|
|
267
|
+
await this.executor?.close?.();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// src/legacy.ts
|
|
272
|
+
function parseJsonLike(value) {
|
|
273
|
+
if (!value) return {};
|
|
274
|
+
if (typeof value === "string") {
|
|
275
|
+
try {
|
|
276
|
+
return JSON.parse(value);
|
|
277
|
+
} catch {
|
|
278
|
+
return {};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
function normalizeLegacyAnalysis(row) {
|
|
284
|
+
const analysis = parseJsonLike(row.analysis_result);
|
|
285
|
+
return {
|
|
286
|
+
signature: row.error_cache_key,
|
|
287
|
+
analysis: {
|
|
288
|
+
side: row.side || analysis.side || "unknown",
|
|
289
|
+
rootCause: row.root_cause || analysis.rootCause || analysis.root_cause || "No root cause recorded",
|
|
290
|
+
evidence: analysis.evidence || [],
|
|
291
|
+
reproductionSteps: analysis.reproductionSteps || analysis.reproduction_steps || [],
|
|
292
|
+
suspects: analysis.suspects || { selectors: [], pages: [], endpoints: [] },
|
|
293
|
+
fixSuggestions: row.fix_suggestions || analysis.fixSuggestions || analysis.fix || [],
|
|
294
|
+
confidence: Number(row.confidence ?? analysis.confidence ?? 0),
|
|
295
|
+
category: analysis.category || "unknown",
|
|
296
|
+
risk: analysis.risk || "high",
|
|
297
|
+
evidenceRefs: analysis.evidenceRefs || [],
|
|
298
|
+
confidenceReasons: analysis.confidenceReasons || ["Imported cached analysis."],
|
|
299
|
+
needsHumanReview: analysis.needsHumanReview ?? true,
|
|
300
|
+
source: "cache"
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function importLegacyRows(input) {
|
|
305
|
+
const artifactsByResultId = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const artifact of input.artifacts || []) {
|
|
307
|
+
const id = Number(artifact.test_result_id);
|
|
308
|
+
artifactsByResultId.set(id, [...artifactsByResultId.get(id) || [], artifact]);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
failures: (input.testResults || []).map((row) => ({
|
|
312
|
+
title: row.test_title,
|
|
313
|
+
file: row.test_file,
|
|
314
|
+
line: row.test_line,
|
|
315
|
+
project: row.project_name,
|
|
316
|
+
status: row.status,
|
|
317
|
+
retry: row.retry_index || 0,
|
|
318
|
+
durationMs: row.duration_ms || 0,
|
|
319
|
+
error: row.error_message || row.error_stack || "Unknown error",
|
|
320
|
+
artifacts: (artifactsByResultId.get(Number(row.id)) || []).map((artifact) => ({
|
|
321
|
+
type: artifact.artifact_type || "artifact",
|
|
322
|
+
path: artifact.file_path,
|
|
323
|
+
contentType: artifact.mime_type
|
|
324
|
+
})),
|
|
325
|
+
run: row.run_id ? { id: row.run_id } : void 0
|
|
326
|
+
})),
|
|
327
|
+
analyses: (input.analysisCache || []).filter((row) => row.error_cache_key).map(normalizeLegacyAnalysis)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
export {
|
|
331
|
+
DeterministicAnalyzer,
|
|
332
|
+
FailureGrouper,
|
|
333
|
+
MemoryStore,
|
|
334
|
+
OpenAIResponsesProvider,
|
|
335
|
+
PostgresStore,
|
|
336
|
+
SQLiteStore,
|
|
337
|
+
TemplateTestGenerationProvider,
|
|
338
|
+
analyzeFailures,
|
|
339
|
+
assertBrowserGenerationAllowed,
|
|
340
|
+
benchmarkFailures,
|
|
341
|
+
classifyFailure,
|
|
342
|
+
enrichFailureWithArtifacts,
|
|
343
|
+
evaluatePatchPolicy,
|
|
344
|
+
generatePatchSuggestions,
|
|
345
|
+
generatePlaywrightTestDraft,
|
|
346
|
+
htmlReport,
|
|
347
|
+
importLegacyRows,
|
|
348
|
+
isDestructiveManualCase,
|
|
349
|
+
markdownReport,
|
|
350
|
+
mcpSummary,
|
|
351
|
+
normalizePlaywrightJsonReport,
|
|
352
|
+
parseTraceArtifact,
|
|
353
|
+
writePortableReport
|
|
354
|
+
};
|