playwright-spec-doc-reporter 0.1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +493 -0
  3. package/dist/ai/analysisService.d.ts +4 -0
  4. package/dist/ai/analysisService.js +80 -0
  5. package/dist/ai/prompt.d.ts +2 -0
  6. package/dist/ai/prompt.js +22 -0
  7. package/dist/ai/providers/anthropicProvider.d.ts +5 -0
  8. package/dist/ai/providers/anthropicProvider.js +47 -0
  9. package/dist/ai/providers/openaiProvider.d.ts +5 -0
  10. package/dist/ai/providers/openaiProvider.js +49 -0
  11. package/dist/annotations.d.ts +60 -0
  12. package/dist/annotations.js +76 -0
  13. package/dist/config/defaults.d.ts +2 -0
  14. package/dist/config/defaults.js +8 -0
  15. package/dist/generator/htmlTemplate.d.ts +6 -0
  16. package/dist/generator/htmlTemplate.js +78 -0
  17. package/dist/generator/reportGenerator.d.ts +6 -0
  18. package/dist/generator/reportGenerator.js +142 -0
  19. package/dist/generator/template/index.d.ts +6 -0
  20. package/dist/generator/template/index.js +6 -0
  21. package/dist/generator/template/markup.d.ts +1 -0
  22. package/dist/generator/template/markup.js +221 -0
  23. package/dist/generator/template/scriptInit.d.ts +1 -0
  24. package/dist/generator/template/scriptInit.js +29 -0
  25. package/dist/generator/template/scriptInteractions.d.ts +1 -0
  26. package/dist/generator/template/scriptInteractions.js +545 -0
  27. package/dist/generator/template/scriptRenderers.d.ts +1 -0
  28. package/dist/generator/template/scriptRenderers.js +951 -0
  29. package/dist/generator/template/scriptUtils.d.ts +1 -0
  30. package/dist/generator/template/scriptUtils.js +298 -0
  31. package/dist/generator/template/styles.d.ts +1 -0
  32. package/dist/generator/template/styles.js +755 -0
  33. package/dist/healing/healingAgent.d.ts +11 -0
  34. package/dist/healing/healingAgent.js +295 -0
  35. package/dist/healing/payload.d.ts +4 -0
  36. package/dist/healing/payload.js +47 -0
  37. package/dist/index.d.ts +9 -0
  38. package/dist/index.js +7 -0
  39. package/dist/reporter/glossyReporter.d.ts +16 -0
  40. package/dist/reporter/glossyReporter.js +252 -0
  41. package/dist/types/index.d.ts +213 -0
  42. package/dist/types/index.js +1 -0
  43. package/dist/utils/fs.d.ts +3 -0
  44. package/dist/utils/fs.js +13 -0
  45. package/dist/utils/report.d.ts +9 -0
  46. package/dist/utils/report.js +69 -0
  47. package/package.json +66 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 playwright-glossy-ai-reporter contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,493 @@
1
+ # playwright-spec-doc-reporter
2
+
3
+ A beautiful, production-ready Playwright reporter with BDD-style annotations, inline API request/response display, AI-powered failure analysis, test history trends, and self-healing payload exports.
4
+
5
+ ---
6
+
7
+ ## Screenshots
8
+
9
+ ### Dashboard Overview
10
+ ![Dashboard](docs/screenshots/dashboard.png)
11
+
12
+ ### Tests & BDD View (with browser badges)
13
+ ![Tests View](docs/screenshots/tests-view.png)
14
+
15
+ ### Inline API Request / Response Viewer
16
+ ![API Viewer](docs/screenshots/api-viewer.png)
17
+
18
+ ### Failure Detail with AI Analysis
19
+ ![Failure Detail](docs/screenshots/failure-detail.png)
20
+
21
+ ### AI Insights Tab
22
+ ![AI Insights](docs/screenshots/ai-insights.png)
23
+
24
+ ### Run History & Trends
25
+ ![Trends](docs/screenshots/trends.png)
26
+
27
+ ---
28
+
29
+ ## Features
30
+
31
+ - **Interactive HTML dashboard** — dark-themed report with filter, sort, search, and failure drill-down
32
+ - **BDD annotations** — add Feature, Scenario, and Behaviour metadata directly in your tests
33
+ - **Inline API viewer** — attach request/response JSON directly to test results with syntax highlighting
34
+ - **AI failure analysis** — automatic root-cause analysis for failed tests (OpenAI, Anthropic, or custom)
35
+ - **Healing payloads** — structured JSON + Markdown export of suggested locator fixes and patches
36
+ - **History & trends** — pass-rate and duration charts across runs via `spec-doc-history.json`
37
+ - **Zero runtime dependencies** — single self-contained HTML file output
38
+
39
+ ---
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm install -D playwright-spec-doc-reporter
45
+ ```
46
+
47
+ `@playwright/test >= 1.44.0` is a peer dependency.
48
+
49
+ ---
50
+
51
+ ## Quick start
52
+
53
+ In `playwright.config.ts`:
54
+
55
+ ```ts
56
+ import { defineConfig } from "@playwright/test";
57
+
58
+ export default defineConfig({
59
+ reporter: [
60
+ ["list"],
61
+ [
62
+ "playwright-spec-doc-reporter",
63
+ {
64
+ outputDir: "spec-doc-report",
65
+ reportTitle: "E2E Quality Report",
66
+ includeScreenshots: true,
67
+ includeVideos: true,
68
+ includeTraces: true
69
+ }
70
+ ]
71
+ ]
72
+ });
73
+ ```
74
+
75
+ After each test run, `spec-doc-report/` will contain:
76
+
77
+ | File | Description |
78
+ |------|-------------|
79
+ | `index.html` | Self-contained interactive HTML report |
80
+ | `results.json` | Full normalized JSON for CI/CD processing |
81
+ | `spec-doc-history.json` | Per-run history for trend charts |
82
+ | `healing.json` | AI-suggested locator fixes (optional) |
83
+ | `healing.md` | Human-readable healing summary (optional) |
84
+
85
+ ---
86
+
87
+ ## BDD Annotations
88
+
89
+ Import annotation helpers from the `/annotations` sub-path and call them inside `test()` bodies.
90
+
91
+ ```ts
92
+ import { addFeature, addScenario, addBehaviour } from "playwright-spec-doc-reporter/annotations";
93
+ ```
94
+
95
+ ### `addFeature(name, description?)`
96
+
97
+ Sets the Feature name and optional Gherkin-style narrative. Call once per `describe` block via `beforeEach`.
98
+
99
+ ```ts
100
+ test.describe("Shopping Cart", () => {
101
+ test.beforeEach(() => {
102
+ addFeature(
103
+ "Shopping Cart",
104
+ "As a customer I want to add products to my cart so I can purchase them"
105
+ );
106
+ });
107
+
108
+ test("add item to cart", async ({ page }) => { /* ... */ });
109
+ });
110
+ ```
111
+
112
+ ### `addScenario(description)`
113
+
114
+ Sets a scenario-level description (context / acceptance criteria) for the current test.
115
+
116
+ ```ts
117
+ test("standard user can login and add item to cart", async ({ page }) => {
118
+ addScenario("Verifies the happy-path for a standard user adding one item");
119
+ // ...
120
+ });
121
+ ```
122
+
123
+ ### `addBehaviour(description)`
124
+
125
+ Adds a high-level human-readable behaviour step. These replace low-level Playwright step names in the BDD view and exported documentation.
126
+
127
+ ```ts
128
+ test("login flow", async ({ page }) => {
129
+ addBehaviour("User submits valid credentials on the login page");
130
+ await page.goto("/login");
131
+ await page.fill("#email", "user@example.com");
132
+ await page.click("button[type=submit]");
133
+
134
+ addBehaviour("User is redirected to the dashboard");
135
+ await expect(page).toHaveURL("/dashboard");
136
+ });
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Inline API Request / Response
142
+
143
+ Attach request and response data so they appear inline in the report with syntax-highlighted JSON.
144
+
145
+ ```ts
146
+ import {
147
+ addFeature, addScenario, addBehaviour,
148
+ addApiRequest, addApiResponse
149
+ } from "playwright-spec-doc-reporter/annotations";
150
+
151
+ test.describe("Posts API", () => {
152
+ test.beforeEach(() => {
153
+ addFeature("Posts API", "As a developer I want to validate the posts endpoints");
154
+ });
155
+
156
+ test("POST /posts creates a resource", async ({ request, baseURL }) => {
157
+ addScenario("Verifies a new post is created and returned with an id");
158
+
159
+ const payload = { title: "Hello", body: "World", userId: 1 };
160
+
161
+ addBehaviour("Client sends POST request with post data");
162
+ addApiRequest("POST", `${baseURL}/posts`, payload);
163
+ const res = await request.post(`${baseURL}/posts`, { data: payload });
164
+ const body = await res.json();
165
+ addApiResponse(res.status(), body);
166
+
167
+ addBehaviour("Response is 201 with the new resource including an id");
168
+ expect(res.status()).toBe(201);
169
+ expect(body).toMatchObject(payload);
170
+ });
171
+ });
172
+ ```
173
+
174
+ The report shows each request/response pair with method badge (colour-coded), URL, collapsible body with JSON syntax highlighting, and HTTP status badge.
175
+
176
+ ### `addApiRequest(method, url, body?, headers?)`
177
+
178
+ | Param | Type | Description |
179
+ |-------|------|-------------|
180
+ | `method` | `string` | HTTP method (`GET`, `POST`, etc.) |
181
+ | `url` | `string` | Full request URL |
182
+ | `body` | `unknown` | Request body (JSON-serialized in the report) |
183
+ | `headers` | `Record<string, string>` | Request headers (shown collapsed) |
184
+
185
+ ### `addApiResponse(status, body?, headers?)`
186
+
187
+ | Param | Type | Description |
188
+ |-------|------|-------------|
189
+ | `status` | `number` | HTTP status code |
190
+ | `body` | `unknown` | Response body (JSON-serialized in the report) |
191
+ | `headers` | `Record<string, string>` | Response headers (shown collapsed) |
192
+
193
+ ---
194
+
195
+ ## Reporter configuration
196
+
197
+ ```ts
198
+ type SpecDocReporterConfig = {
199
+ /** Output directory. Default: "spec-doc-report" */
200
+ outputDir?: string;
201
+
202
+ /** Report title shown in the dashboard header. */
203
+ reportTitle?: string;
204
+
205
+ /** Include screenshots in the report. Default: true */
206
+ includeScreenshots?: boolean;
207
+
208
+ /** Include video recordings. Default: true */
209
+ includeVideos?: boolean;
210
+
211
+ /** Include Playwright traces. Default: true */
212
+ includeTraces?: boolean;
213
+
214
+ /** Visual theme. Currently only "dark-glossy". */
215
+ theme?: "dark-glossy";
216
+
217
+ /** AI failure analysis configuration. */
218
+ ai?: {
219
+ enabled: boolean;
220
+ provider: "openai" | "anthropic" | "custom";
221
+ model: string;
222
+ apiKey?: string;
223
+ baseURL?: string;
224
+ maxTokens?: number;
225
+ rateLimitPerMinute?: number;
226
+ maxFailuresToAnalyze?: number;
227
+ customPrompt?: string;
228
+ };
229
+
230
+ /** Healing payload export configuration. */
231
+ healing?: {
232
+ enabled: boolean;
233
+ exportPath?: string;
234
+ exportMarkdownPath?: string;
235
+ analysisOnly?: boolean;
236
+ };
237
+
238
+ /** Factory for a custom AI provider. */
239
+ providerFactory?: (config: AIProviderConfig) => AIProvider;
240
+ };
241
+ ```
242
+
243
+ ---
244
+
245
+ ## AI failure analysis
246
+
247
+ When a test fails, the reporter automatically calls your configured AI provider to analyse the error, stack trace, and screenshot. Results appear inline in the report on the **AI Insights** tab and next to each failing test.
248
+
249
+ ![AI Insights tab](docs/screenshots/ai-insights.png)
250
+
251
+ ### What the AI analyses
252
+
253
+ For each failing test the AI receives:
254
+ - Full error message and stack trace
255
+ - Source code snippet around the failure line
256
+ - Screenshot path (if captured)
257
+ - Trace path (if captured)
258
+ - Console logs
259
+
260
+ ### Analysis output schema
261
+
262
+ ```ts
263
+ interface AIAnalysisResult {
264
+ testName: string;
265
+ file: string;
266
+
267
+ /** One-sentence summary of the failure. */
268
+ summary: string;
269
+
270
+ /** Root cause hypothesis. */
271
+ likelyRootCause: string;
272
+
273
+ /** 0–1 confidence score. */
274
+ confidence: number;
275
+
276
+ /** Concrete suggested fix. */
277
+ suggestedRemediation: string;
278
+
279
+ /** Failure category for grouping/filtering. */
280
+ issueCategory:
281
+ | "locator_drift" // selector no longer matches
282
+ | "timing_issue" // race condition / missing wait
283
+ | "environment_issue"// env var, network, dependency
284
+ | "test_data_issue" // stale fixture or test data
285
+ | "assertion_issue" // wrong expected value
286
+ | "app_bug" // genuine regression
287
+ | "unknown";
288
+
289
+ structuredFeedback: {
290
+ actionType:
291
+ | "locator_update" // replace the failing selector
292
+ | "wait_strategy" // add/change a wait
293
+ | "data_fix" // fix test data
294
+ | "assertion_update"// update expected value
295
+ | "infra_fix" // environment-level fix
296
+ | "investigate"; // needs manual investigation
297
+
298
+ reasoning: string;
299
+
300
+ /** Ready-to-paste code patch (when actionType = locator_update). */
301
+ suggestedPatch?: string;
302
+
303
+ /** Ranked alternative selectors (when actionType = locator_update). */
304
+ candidateLocators?: string[];
305
+ };
306
+ }
307
+ ```
308
+
309
+ ### OpenAI
310
+
311
+ ```ts
312
+ ai: {
313
+ enabled: true,
314
+ provider: "openai",
315
+ model: "gpt-4.1", // or "gpt-4o", "gpt-4o-mini"
316
+ apiKey: process.env.OPENAI_API_KEY,
317
+ maxFailuresToAnalyze: 10,
318
+ maxTokens: 1200,
319
+ rateLimitPerMinute: 20 // avoid 429s on free tiers
320
+ }
321
+ ```
322
+
323
+ ### Anthropic
324
+
325
+ ```ts
326
+ ai: {
327
+ enabled: true,
328
+ provider: "anthropic",
329
+ model: "claude-sonnet-4-6", // or "claude-opus-4-6", "claude-haiku-4-5"
330
+ apiKey: process.env.ANTHROPIC_API_KEY,
331
+ maxFailuresToAnalyze: 10
332
+ }
333
+ ```
334
+
335
+ ### Custom prompt
336
+
337
+ Override the system prompt to focus analysis on your stack, framework, or naming conventions:
338
+
339
+ ```ts
340
+ ai: {
341
+ enabled: true,
342
+ provider: "anthropic",
343
+ model: "claude-sonnet-4-6",
344
+ apiKey: process.env.ANTHROPIC_API_KEY,
345
+ customPrompt: `
346
+ You are an expert in Playwright + React testing.
347
+ Prioritise data-testid selectors over CSS classes.
348
+ Suggest fixes that are compatible with our design system (MUI v5).
349
+ Always provide a ready-to-paste code patch when the issue is a locator.
350
+ `
351
+ }
352
+ ```
353
+
354
+ ### Custom provider
355
+
356
+ Bring your own LLM or internal AI service:
357
+
358
+ ```ts
359
+ import type { AIProvider, AIProviderConfig } from "playwright-spec-doc-reporter";
360
+
361
+ const providerFactory = (_cfg: AIProviderConfig): AIProvider => ({
362
+ name: "internal-llm",
363
+ async analyzeFailure(input, cfg) {
364
+ const response = await fetch("https://ai.internal/analyze", {
365
+ method: "POST",
366
+ headers: { Authorization: `Bearer ${cfg.apiKey}` },
367
+ body: JSON.stringify({ error: input.errorMessage, stack: input.stackTrace })
368
+ });
369
+ const data = await response.json();
370
+ return {
371
+ testName: input.testName,
372
+ file: input.file,
373
+ summary: data.summary,
374
+ likelyRootCause: data.rootCause,
375
+ confidence: data.confidence,
376
+ suggestedRemediation: data.fix,
377
+ issueCategory: data.category ?? "unknown",
378
+ structuredFeedback: {
379
+ actionType: data.actionType ?? "investigate",
380
+ reasoning: data.reasoning,
381
+ suggestedPatch: data.patch
382
+ }
383
+ };
384
+ }
385
+ });
386
+ ```
387
+
388
+ Pass the factory to the reporter config:
389
+
390
+ ```ts
391
+ // playwright.config.ts
392
+ import { providerFactory } from "./my-ai-provider.js";
393
+
394
+ reporter: [["playwright-spec-doc-reporter", { ai: { enabled: true }, providerFactory }]]
395
+ ```
396
+
397
+ ### CI/CD environment variables
398
+
399
+ ```bash
400
+ # .env or CI secrets
401
+ OPENAI_API_KEY=sk-...
402
+ ANTHROPIC_API_KEY=sk-ant-...
403
+ ```
404
+
405
+ The `apiKey` field in config falls back to `process.env.OPENAI_API_KEY` / `process.env.ANTHROPIC_API_KEY` automatically when not set explicitly.
406
+
407
+ ---
408
+
409
+ ## Healing payloads
410
+
411
+ When AI analysis identifies locator issues (`issueCategory: "locator_drift"`), structured healing payloads are generated alongside the report.
412
+
413
+ ```ts
414
+ healing: {
415
+ enabled: true,
416
+ exportPath: "spec-doc-report/healing.json",
417
+ exportMarkdownPath: "spec-doc-report/healing.md",
418
+ analysisOnly: true // never auto-modifies test files
419
+ }
420
+ ```
421
+
422
+ **Payload schema:**
423
+
424
+ ```ts
425
+ interface HealingPayload {
426
+ testName: string;
427
+ file: string;
428
+ stepName?: string;
429
+ failedLocator?: string;
430
+ candidateLocators: string[]; // ranked alternatives
431
+ domContext?: string; // surrounding HTML snippet
432
+ errorMessage?: string;
433
+ suggestedPatch?: string; // ready-to-apply code change
434
+ reasoning: string;
435
+ confidence: number; // 0–1
436
+ actionType: string;
437
+ }
438
+ ```
439
+
440
+ The `healing.md` export is human-readable and CI-comment-friendly.
441
+
442
+ ---
443
+
444
+ ## History & trends
445
+
446
+ The reporter automatically maintains `spec-doc-history.json` and records each run's pass rate, duration, and per-test status. The dashboard's **Trends** tab shows pass-rate charts and per-test stability indicators across runs.
447
+
448
+ ---
449
+
450
+ ## SDK
451
+
452
+ Use the internals directly without running Playwright:
453
+
454
+ ```ts
455
+ import {
456
+ buildSpecDocHtml,
457
+ generateReport,
458
+ analyzeFailures,
459
+ createHealingPayloads,
460
+ healingPayloadsToMarkdown
461
+ } from "playwright-spec-doc-reporter";
462
+
463
+ // Render an HTML report from a saved results.json
464
+ const html = buildSpecDocHtml(reportData, { outputDir: "." });
465
+ ```
466
+
467
+ ---
468
+
469
+ ## TypeScript types
470
+
471
+ ```ts
472
+ import type {
473
+ SpecDocReporterConfig,
474
+ NormalizedTestResult,
475
+ ApiEntry,
476
+ AIAnalysisResult,
477
+ HealingPayload,
478
+ ReportData
479
+ } from "playwright-spec-doc-reporter";
480
+ ```
481
+
482
+ ---
483
+
484
+ ## Requirements
485
+
486
+ - Node.js >= 18
487
+ - `@playwright/test` >= 1.44.0 (peer dependency)
488
+
489
+ ---
490
+
491
+ ## License
492
+
493
+ [MIT](LICENSE)
@@ -0,0 +1,4 @@
1
+ import type { AIAnalysisInput, AIAnalysisResult, AIProvider, AIProviderConfig, NormalizedTestResult } from "../types/index.js";
2
+ export declare function resolveProvider(config: AIProviderConfig, customFactory?: (cfg: AIProviderConfig) => AIProvider): AIProvider;
3
+ export declare function toAIInput(test: NormalizedTestResult): AIAnalysisInput;
4
+ export declare function analyzeFailures(failedTests: NormalizedTestResult[], config: AIProviderConfig, customFactory?: (cfg: AIProviderConfig) => AIProvider): Promise<AIAnalysisResult[]>;
@@ -0,0 +1,80 @@
1
+ import { AnthropicProvider } from "./providers/anthropicProvider.js";
2
+ import { OpenAIProvider } from "./providers/openaiProvider.js";
3
+ function toIssueCategory(raw) {
4
+ const normalized = (raw ?? "unknown").toLowerCase();
5
+ if (normalized === "locator_drift" ||
6
+ normalized === "timing_issue" ||
7
+ normalized === "environment_issue" ||
8
+ normalized === "test_data_issue" ||
9
+ normalized === "assertion_issue" ||
10
+ normalized === "app_bug") {
11
+ return normalized;
12
+ }
13
+ return "unknown";
14
+ }
15
+ function normalizeResult(result) {
16
+ return {
17
+ ...result,
18
+ confidence: Math.max(0, Math.min(1, Number.isFinite(result.confidence) ? result.confidence : 0.25)),
19
+ issueCategory: toIssueCategory(result.issueCategory)
20
+ };
21
+ }
22
+ export function resolveProvider(config, customFactory) {
23
+ if (config.provider === "custom") {
24
+ if (!customFactory) {
25
+ throw new Error("Custom AI provider selected but no providerFactory supplied.");
26
+ }
27
+ return customFactory(config);
28
+ }
29
+ if (config.provider === "openai")
30
+ return new OpenAIProvider();
31
+ if (config.provider === "anthropic")
32
+ return new AnthropicProvider();
33
+ throw new Error(`Unsupported AI provider: ${config.provider}`);
34
+ }
35
+ export function toAIInput(test) {
36
+ const failedLocator = test.errorMessage?.match(/locator\((.*?)\)/)?.[1];
37
+ return {
38
+ testName: test.fullName,
39
+ file: test.file,
40
+ errorMessage: test.errorMessage,
41
+ stackTrace: test.stackTrace,
42
+ failedLocator,
43
+ screenshotPath: test.artifacts.screenshots[0],
44
+ tracePath: test.artifacts.traces[0],
45
+ additionalContext: {
46
+ retries: test.retries,
47
+ projectName: test.projectName,
48
+ browser: test.browser,
49
+ durationMs: test.durationMs,
50
+ consoleLogs: test.consoleLogs.slice(-5)
51
+ }
52
+ };
53
+ }
54
+ export async function analyzeFailures(failedTests, config, customFactory) {
55
+ const provider = resolveProvider(config, customFactory);
56
+ const analyses = [];
57
+ for (const test of failedTests) {
58
+ const input = toAIInput(test);
59
+ try {
60
+ const result = await provider.analyzeFailure(input, config);
61
+ analyses.push(normalizeResult(result));
62
+ }
63
+ catch (error) {
64
+ analyses.push({
65
+ testName: input.testName,
66
+ file: input.file,
67
+ summary: "AI analysis unavailable for this test.",
68
+ likelyRootCause: error instanceof Error ? error.message : "Unknown provider error",
69
+ confidence: 0,
70
+ suggestedRemediation: "Inspect test logs and rerun with trace enabled.",
71
+ issueCategory: "unknown",
72
+ structuredFeedback: {
73
+ actionType: "investigate",
74
+ reasoning: "Provider failed to return a valid analysis."
75
+ }
76
+ });
77
+ }
78
+ }
79
+ return analyses;
80
+ }
@@ -0,0 +1,2 @@
1
+ import type { AIAnalysisInput } from "../types/index.js";
2
+ export declare function buildFailurePrompt(input: AIAnalysisInput, customPrompt?: string): string;
@@ -0,0 +1,22 @@
1
+ export function buildFailurePrompt(input, customPrompt) {
2
+ const base = [
3
+ "You are an expert Playwright test reliability engineer.",
4
+ "Return ONLY strict JSON with keys:",
5
+ "summary, likelyRootCause, confidence, suggestedRemediation, issueCategory, structuredFeedback.",
6
+ "structuredFeedback keys: actionType, reasoning, suggestedPatch, candidateLocators.",
7
+ "Issue categories allowed: locator_drift, timing_issue, environment_issue, test_data_issue, assertion_issue, app_bug, unknown.",
8
+ "Confidence must be a number 0-1."
9
+ ].join("\n");
10
+ const context = {
11
+ testName: input.testName,
12
+ file: input.file,
13
+ errorMessage: input.errorMessage,
14
+ stackTrace: input.stackTrace,
15
+ domSnippet: input.domSnippet,
16
+ failedLocator: input.failedLocator,
17
+ screenshotPath: input.screenshotPath,
18
+ tracePath: input.tracePath,
19
+ additionalContext: input.additionalContext
20
+ };
21
+ return `${base}\n${customPrompt ?? ""}\nAnalyze this failure context:\n${JSON.stringify(context, null, 2)}`;
22
+ }
@@ -0,0 +1,5 @@
1
+ import type { AIAnalysisInput, AIAnalysisResult, AIProvider, AIProviderConfig } from "../../types/index.js";
2
+ export declare class AnthropicProvider implements AIProvider {
3
+ name: string;
4
+ analyzeFailure(input: AIAnalysisInput, config: AIProviderConfig): Promise<AIAnalysisResult>;
5
+ }
@@ -0,0 +1,47 @@
1
+ import { buildFailurePrompt } from "../prompt.js";
2
+ function parseJson(raw) {
3
+ const fenced = raw.match(/```json([\s\S]*?)```/i);
4
+ const candidate = fenced?.[1] ?? raw;
5
+ return JSON.parse(candidate.trim());
6
+ }
7
+ export class AnthropicProvider {
8
+ name = "anthropic";
9
+ async analyzeFailure(input, config) {
10
+ const apiKey = config.apiKey ?? process.env.ANTHROPIC_API_KEY;
11
+ if (!apiKey) {
12
+ throw new Error("Anthropic API key missing. Set ai.apiKey or ANTHROPIC_API_KEY.");
13
+ }
14
+ const prompt = buildFailurePrompt(input, config.customPrompt);
15
+ const baseURL = config.baseURL ?? "https://api.anthropic.com";
16
+ const response = await fetch(`${baseURL}/v1/messages`, {
17
+ method: "POST",
18
+ headers: {
19
+ "Content-Type": "application/json",
20
+ "x-api-key": apiKey,
21
+ "anthropic-version": "2023-06-01"
22
+ },
23
+ body: JSON.stringify({
24
+ model: config.model,
25
+ max_tokens: config.maxTokens ?? 800,
26
+ temperature: 0.2,
27
+ messages: [{ role: "user", content: prompt }]
28
+ })
29
+ });
30
+ if (!response.ok) {
31
+ const errorText = await response.text();
32
+ throw new Error(`Anthropic request failed (${response.status}): ${errorText}`);
33
+ }
34
+ const data = (await response.json());
35
+ const content = data.content?.find((item) => item.type === "text")?.text;
36
+ if (!content) {
37
+ throw new Error("Anthropic response did not include text content.");
38
+ }
39
+ const parsed = parseJson(content);
40
+ return {
41
+ testName: input.testName,
42
+ file: input.file,
43
+ ...parsed,
44
+ rawModelResponse: data
45
+ };
46
+ }
47
+ }
@@ -0,0 +1,5 @@
1
+ import type { AIAnalysisInput, AIAnalysisResult, AIProvider, AIProviderConfig } from "../../types/index.js";
2
+ export declare class OpenAIProvider implements AIProvider {
3
+ name: string;
4
+ analyzeFailure(input: AIAnalysisInput, config: AIProviderConfig): Promise<AIAnalysisResult>;
5
+ }