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.
- package/LICENSE +21 -0
- package/README.md +493 -0
- package/dist/ai/analysisService.d.ts +4 -0
- package/dist/ai/analysisService.js +80 -0
- package/dist/ai/prompt.d.ts +2 -0
- package/dist/ai/prompt.js +22 -0
- package/dist/ai/providers/anthropicProvider.d.ts +5 -0
- package/dist/ai/providers/anthropicProvider.js +47 -0
- package/dist/ai/providers/openaiProvider.d.ts +5 -0
- package/dist/ai/providers/openaiProvider.js +49 -0
- package/dist/annotations.d.ts +60 -0
- package/dist/annotations.js +76 -0
- package/dist/config/defaults.d.ts +2 -0
- package/dist/config/defaults.js +8 -0
- package/dist/generator/htmlTemplate.d.ts +6 -0
- package/dist/generator/htmlTemplate.js +78 -0
- package/dist/generator/reportGenerator.d.ts +6 -0
- package/dist/generator/reportGenerator.js +142 -0
- package/dist/generator/template/index.d.ts +6 -0
- package/dist/generator/template/index.js +6 -0
- package/dist/generator/template/markup.d.ts +1 -0
- package/dist/generator/template/markup.js +221 -0
- package/dist/generator/template/scriptInit.d.ts +1 -0
- package/dist/generator/template/scriptInit.js +29 -0
- package/dist/generator/template/scriptInteractions.d.ts +1 -0
- package/dist/generator/template/scriptInteractions.js +545 -0
- package/dist/generator/template/scriptRenderers.d.ts +1 -0
- package/dist/generator/template/scriptRenderers.js +951 -0
- package/dist/generator/template/scriptUtils.d.ts +1 -0
- package/dist/generator/template/scriptUtils.js +298 -0
- package/dist/generator/template/styles.d.ts +1 -0
- package/dist/generator/template/styles.js +755 -0
- package/dist/healing/healingAgent.d.ts +11 -0
- package/dist/healing/healingAgent.js +295 -0
- package/dist/healing/payload.d.ts +4 -0
- package/dist/healing/payload.js +47 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +7 -0
- package/dist/reporter/glossyReporter.d.ts +16 -0
- package/dist/reporter/glossyReporter.js +252 -0
- package/dist/types/index.d.ts +213 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/fs.d.ts +3 -0
- package/dist/utils/fs.js +13 -0
- package/dist/utils/report.d.ts +9 -0
- package/dist/utils/report.js +69 -0
- 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
|
+

|
|
11
|
+
|
|
12
|
+
### Tests & BDD View (with browser badges)
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
### Inline API Request / Response Viewer
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
### Failure Detail with AI Analysis
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
### AI Insights Tab
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
### Run History & Trends
|
|
25
|
+

|
|
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
|
+

|
|
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,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
|
+
}
|