qa-engineer 0.10.0 → 0.11.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/README.md +167 -59
- package/package.json +1 -1
- package/packages/engine/bin/qa-engine.mjs +232 -8
- package/packages/engine/lib/analysis/har.mjs +34 -0
- package/packages/engine/lib/analysis/network.mjs +237 -0
- package/packages/engine/lib/analysis/report-html.mjs +47 -734
- package/packages/engine/lib/artifacts/manager.mjs +453 -0
- package/packages/engine/lib/artifacts/mime.mjs +109 -0
- package/packages/engine/lib/artifacts/zip-write.mjs +143 -0
- package/packages/engine/lib/report/components/charts.mjs +424 -0
- package/packages/engine/lib/report/components/evidence.mjs +207 -0
- package/packages/engine/lib/report/components/findings.mjs +258 -0
- package/packages/engine/lib/report/components/nav.mjs +99 -0
- package/packages/engine/lib/report/components/primitives.mjs +246 -0
- package/packages/engine/lib/report/components/runtime.mjs +246 -0
- package/packages/engine/lib/report/components/timeline.mjs +65 -0
- package/packages/engine/lib/report/core/model.mjs +270 -0
- package/packages/engine/lib/report/core/normalize.mjs +226 -0
- package/packages/engine/lib/report/core/sections.mjs +978 -0
- package/packages/engine/lib/report/export/bundle.mjs +293 -0
- package/packages/engine/lib/report/export/html.mjs +183 -0
- package/packages/engine/lib/report/export/machine.mjs +290 -0
- package/packages/engine/lib/report/export/markdown.mjs +323 -0
- package/packages/engine/lib/report/schemas/qa-report.schema.json +555 -0
- package/packages/engine/lib/report/theme/css.mjs +529 -0
- package/packages/engine/lib/report/theme/tokens.mjs +137 -0
- package/packages/engine/lib/report/version.mjs +78 -0
- package/packages/engine/package.json +2 -2
- package/packages/installer/lib/agents/targets.mjs +90 -0
- package/packages/installer/lib/agents/user-level.mjs +80 -0
- package/packages/installer/lib/cli/flags.mjs +20 -2
- package/packages/installer/lib/commands/doctor.mjs +6 -4
- package/packages/installer/lib/commands/install.mjs +134 -93
- package/packages/installer/lib/commands/repair.mjs +10 -6
- package/packages/installer/lib/commands/self-test.mjs +4 -3
- package/packages/installer/lib/commands/uninstall.mjs +14 -8
- package/packages/installer/lib/commands/update.mjs +9 -4
- package/packages/installer/lib/commands/verify.mjs +13 -12
- package/packages/installer/lib/constants.mjs +13 -0
- package/packages/installer/lib/core/conflict.mjs +5 -4
- package/packages/installer/lib/core/fs-safe.mjs +146 -6
- package/packages/installer/lib/core/integrity.mjs +59 -0
- package/packages/installer/lib/core/lockfile.mjs +19 -3
- package/packages/installer/lib/core/plan.mjs +213 -0
- package/packages/installer/lib/core/qa-home.mjs +145 -0
- package/packages/installer/lib/core/scope.mjs +274 -0
- package/packages/installer/lib/core/validate-install.mjs +37 -12
- package/packages/installer/package.json +1 -1
- package/packages/installer/schemas/qa-lock.schema.json +119 -21
- package/shared/tooling/qa-tool.mjs +41 -5
- package/skills/qa-api/scripts/qa-tool.mjs +41 -5
- package/skills/qa-audit/scripts/qa-tool.mjs +41 -5
- package/skills/qa-debug/scripts/qa-tool.mjs +41 -5
- package/skills/qa-explore/SKILL.md +26 -10
- package/skills/qa-explore/contracts/explore-result.schema.json +517 -11
- package/skills/qa-explore/references/api-replay.md +40 -1
- package/skills/qa-explore/references/report-pipeline.md +265 -95
- package/skills/qa-explore/scripts/qa-tool.mjs +41 -5
- package/skills/qa-fix/scripts/qa-tool.mjs +41 -5
- package/skills/qa-flaky/scripts/qa-tool.mjs +41 -5
- package/skills/qa-init/scripts/qa-tool.mjs +41 -5
- package/skills/qa-report/scripts/qa-tool.mjs +41 -5
- package/skills/qa-run/scripts/qa-tool.mjs +41 -5
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "urn:qa-pack:contract:qa-explore:explore-result:1",
|
|
4
4
|
"title": "qa-explore explore result",
|
|
5
|
-
"description": "Full-spectrum product QA against a live URL: findings with severity and proof, optional test-case coverage and DB validation. classification is the overall explore outcome.",
|
|
5
|
+
"description": "Full-spectrum product QA against a live URL: findings with severity and proof, optional test-case coverage and DB validation. classification is the overall explore outcome. Version 1.1 adds the evidence and analysis blocks the enterprise report renders — artifacts, executive verdict, scores, pages, timeline, network, performance, accessibility, security, console — all optional, so a 1.0 producer stays valid.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
@@ -42,6 +42,21 @@
|
|
|
42
42
|
},
|
|
43
43
|
"url": { "type": "string", "minLength": 1 },
|
|
44
44
|
"reportVersion": { "type": "string" },
|
|
45
|
+
"title": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"minLength": 1,
|
|
48
|
+
"description": "The product or application name, for the report masthead. The URL is the fallback when this is absent."
|
|
49
|
+
},
|
|
50
|
+
"environment": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"minLength": 1,
|
|
53
|
+
"description": "Which deployment was exercised — production, staging, local. A finding's severity is read differently depending on where it was found."
|
|
54
|
+
},
|
|
55
|
+
"durationMs": {
|
|
56
|
+
"type": "integer",
|
|
57
|
+
"minimum": 0,
|
|
58
|
+
"description": "Wall-clock length of the whole run."
|
|
59
|
+
},
|
|
45
60
|
"browserAdapter": {
|
|
46
61
|
"enum": [
|
|
47
62
|
"playwright-mcp",
|
|
@@ -52,6 +67,49 @@
|
|
|
52
67
|
"unavailable"
|
|
53
68
|
]
|
|
54
69
|
},
|
|
70
|
+
"authentication": {
|
|
71
|
+
"description": "How the run got past the login wall, if it had to. Absent means no authentication was involved.",
|
|
72
|
+
"type": "object",
|
|
73
|
+
"additionalProperties": false,
|
|
74
|
+
"properties": {
|
|
75
|
+
"required": { "type": "boolean" },
|
|
76
|
+
"method": {
|
|
77
|
+
"enum": [
|
|
78
|
+
"none",
|
|
79
|
+
"form",
|
|
80
|
+
"sso",
|
|
81
|
+
"oauth",
|
|
82
|
+
"saml",
|
|
83
|
+
"azure-ad",
|
|
84
|
+
"okta",
|
|
85
|
+
"auth0",
|
|
86
|
+
"google",
|
|
87
|
+
"microsoft",
|
|
88
|
+
"magic-link",
|
|
89
|
+
"jwt",
|
|
90
|
+
"cookie",
|
|
91
|
+
"session",
|
|
92
|
+
"basic",
|
|
93
|
+
"api-key",
|
|
94
|
+
"unknown"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"detectedBy": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"minLength": 1,
|
|
100
|
+
"description": "The signal that proved it — a 401, a redirect to an identity provider, a login form."
|
|
101
|
+
},
|
|
102
|
+
"sessionSource": {
|
|
103
|
+
"enum": ["manual-login", "reused-session", "not-required", "failed"]
|
|
104
|
+
},
|
|
105
|
+
"profile": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"minLength": 1,
|
|
108
|
+
"description": "Which stored role this run used — admin, manager, read-only."
|
|
109
|
+
},
|
|
110
|
+
"note": { "type": "string", "minLength": 1 }
|
|
111
|
+
}
|
|
112
|
+
},
|
|
55
113
|
"dimensionsRun": {
|
|
56
114
|
"type": "array",
|
|
57
115
|
"items": {
|
|
@@ -62,7 +120,9 @@
|
|
|
62
120
|
"security",
|
|
63
121
|
"ui",
|
|
64
122
|
"ux",
|
|
65
|
-
"data"
|
|
123
|
+
"data",
|
|
124
|
+
"accessibility",
|
|
125
|
+
"console"
|
|
66
126
|
]
|
|
67
127
|
}
|
|
68
128
|
},
|
|
@@ -88,6 +148,61 @@
|
|
|
88
148
|
}
|
|
89
149
|
}
|
|
90
150
|
},
|
|
151
|
+
"executive": {
|
|
152
|
+
"description": "The release decision, stated once at the top for a reader who will not scroll. Everything here must be derivable from the findings below it.",
|
|
153
|
+
"type": "object",
|
|
154
|
+
"additionalProperties": false,
|
|
155
|
+
"required": ["verdict", "headline"],
|
|
156
|
+
"properties": {
|
|
157
|
+
"verdict": {
|
|
158
|
+
"enum": ["ship", "ship-with-risks", "do-not-ship", "insufficient-data"]
|
|
159
|
+
},
|
|
160
|
+
"headline": {
|
|
161
|
+
"type": "string",
|
|
162
|
+
"minLength": 1,
|
|
163
|
+
"description": "One sentence a CEO can act on."
|
|
164
|
+
},
|
|
165
|
+
"health": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"minLength": 1,
|
|
168
|
+
"description": "Current state of the application in two or three sentences."
|
|
169
|
+
},
|
|
170
|
+
"risks": {
|
|
171
|
+
"type": "array",
|
|
172
|
+
"items": { "type": "string", "minLength": 1 },
|
|
173
|
+
"description": "The reasons behind the verdict, worst first."
|
|
174
|
+
},
|
|
175
|
+
"recommendedAction": { "type": "string", "minLength": 1 },
|
|
176
|
+
"estimatedFixHours": {
|
|
177
|
+
"type": "object",
|
|
178
|
+
"additionalProperties": false,
|
|
179
|
+
"required": ["low", "high"],
|
|
180
|
+
"properties": {
|
|
181
|
+
"low": { "type": "number", "minimum": 0 },
|
|
182
|
+
"high": { "type": "number", "minimum": 0 }
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"confidence": {
|
|
186
|
+
"type": "number",
|
|
187
|
+
"minimum": 0,
|
|
188
|
+
"maximum": 1,
|
|
189
|
+
"description": "How much the run trusts its own verdict. Below 0.6 the report says so in the header."
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
"scores": {
|
|
194
|
+
"description": "Zero to one hundred, higher is better. Omit any dimension the run did not measure — a fabricated score is worse than a missing one.",
|
|
195
|
+
"type": "object",
|
|
196
|
+
"additionalProperties": false,
|
|
197
|
+
"properties": {
|
|
198
|
+
"overall": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
199
|
+
"functional": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
200
|
+
"performance": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
201
|
+
"accessibility": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
202
|
+
"security": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
203
|
+
"ux": { "type": "integer", "minimum": 0, "maximum": 100 }
|
|
204
|
+
}
|
|
205
|
+
},
|
|
91
206
|
"severityCounts": {
|
|
92
207
|
"type": "object",
|
|
93
208
|
"additionalProperties": false,
|
|
@@ -99,6 +214,258 @@
|
|
|
99
214
|
"low": { "type": "integer", "minimum": 0 }
|
|
100
215
|
}
|
|
101
216
|
},
|
|
217
|
+
"artifacts": {
|
|
218
|
+
"description": "Every file this run produced, registered once. Findings and evidence entries point at these by id, so a path exists in exactly one place and the renderer can tell a missing file from a present one instead of emitting a broken image.",
|
|
219
|
+
"type": "array",
|
|
220
|
+
"items": {
|
|
221
|
+
"type": "object",
|
|
222
|
+
"additionalProperties": false,
|
|
223
|
+
"required": ["id", "kind", "path"],
|
|
224
|
+
"properties": {
|
|
225
|
+
"id": {
|
|
226
|
+
"type": "string",
|
|
227
|
+
"minLength": 1,
|
|
228
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
|
229
|
+
},
|
|
230
|
+
"kind": {
|
|
231
|
+
"enum": [
|
|
232
|
+
"screenshot",
|
|
233
|
+
"video",
|
|
234
|
+
"trace",
|
|
235
|
+
"har",
|
|
236
|
+
"dom",
|
|
237
|
+
"console",
|
|
238
|
+
"network",
|
|
239
|
+
"performance",
|
|
240
|
+
"accessibility",
|
|
241
|
+
"security",
|
|
242
|
+
"api",
|
|
243
|
+
"coverage",
|
|
244
|
+
"log",
|
|
245
|
+
"diff",
|
|
246
|
+
"report",
|
|
247
|
+
"file"
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
"path": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"minLength": 1,
|
|
253
|
+
"description": "Location of the file, relative to the directory holding this result JSON. A path that escapes that directory is rejected by the artifact manager."
|
|
254
|
+
},
|
|
255
|
+
"label": { "type": "string", "minLength": 1 },
|
|
256
|
+
"mimeType": { "type": "string", "minLength": 1 },
|
|
257
|
+
"bytes": { "type": "integer", "minimum": 0 },
|
|
258
|
+
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
259
|
+
"capturedAt": { "type": "string", "format": "date-time" },
|
|
260
|
+
"width": { "type": "integer", "minimum": 1 },
|
|
261
|
+
"height": { "type": "integer", "minimum": 1 },
|
|
262
|
+
"thumbnail": {
|
|
263
|
+
"type": "string",
|
|
264
|
+
"minLength": 1,
|
|
265
|
+
"description": "Relative path to a smaller stand-in used for the inline preview, when one was generated."
|
|
266
|
+
},
|
|
267
|
+
"compares": {
|
|
268
|
+
"type": "string",
|
|
269
|
+
"minLength": 1,
|
|
270
|
+
"description": "Artifact id this one should be shown against, for before/after pairs."
|
|
271
|
+
},
|
|
272
|
+
"note": { "type": "string", "minLength": 1 }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
"pages": {
|
|
277
|
+
"description": "One entry per page the run visited. A multi-page run is one report, not several.",
|
|
278
|
+
"type": "array",
|
|
279
|
+
"items": {
|
|
280
|
+
"type": "object",
|
|
281
|
+
"additionalProperties": false,
|
|
282
|
+
"required": ["url", "status"],
|
|
283
|
+
"properties": {
|
|
284
|
+
"url": { "type": "string", "minLength": 1 },
|
|
285
|
+
"title": { "type": "string", "minLength": 1 },
|
|
286
|
+
"status": { "enum": ["ok", "issues", "failed", "blocked", "skipped"] },
|
|
287
|
+
"httpStatus": { "type": "integer", "minimum": 100, "maximum": 599 },
|
|
288
|
+
"findingIds": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
289
|
+
"loadMs": { "type": "integer", "minimum": 0 },
|
|
290
|
+
"requests": { "type": "integer", "minimum": 0 },
|
|
291
|
+
"consoleErrors": { "type": "integer", "minimum": 0 },
|
|
292
|
+
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
293
|
+
"note": { "type": "string", "minLength": 1 }
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
"timeline": {
|
|
298
|
+
"description": "What the run did, in order, so a reader can see where the time went and what was actually exercised.",
|
|
299
|
+
"type": "array",
|
|
300
|
+
"items": {
|
|
301
|
+
"type": "object",
|
|
302
|
+
"additionalProperties": false,
|
|
303
|
+
"required": ["phase", "label"],
|
|
304
|
+
"properties": {
|
|
305
|
+
"phase": {
|
|
306
|
+
"enum": [
|
|
307
|
+
"launch",
|
|
308
|
+
"authentication",
|
|
309
|
+
"navigation",
|
|
310
|
+
"functional",
|
|
311
|
+
"api",
|
|
312
|
+
"performance",
|
|
313
|
+
"security",
|
|
314
|
+
"accessibility",
|
|
315
|
+
"ui",
|
|
316
|
+
"data",
|
|
317
|
+
"analysis",
|
|
318
|
+
"reporting"
|
|
319
|
+
]
|
|
320
|
+
},
|
|
321
|
+
"label": { "type": "string", "minLength": 1 },
|
|
322
|
+
"startedAt": { "type": "string", "format": "date-time" },
|
|
323
|
+
"durationMs": { "type": "integer", "minimum": 0 },
|
|
324
|
+
"status": { "enum": ["ok", "warned", "failed", "skipped"] },
|
|
325
|
+
"detail": { "type": "string", "minLength": 1 }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
"network": {
|
|
330
|
+
"description": "What the page asked the network for, and what came back.",
|
|
331
|
+
"type": "object",
|
|
332
|
+
"additionalProperties": false,
|
|
333
|
+
"properties": {
|
|
334
|
+
"totalRequests": { "type": "integer", "minimum": 0 },
|
|
335
|
+
"failedRequests": { "type": "integer", "minimum": 0 },
|
|
336
|
+
"slowRequests": { "type": "integer", "minimum": 0 },
|
|
337
|
+
"duplicateRequests": { "type": "integer", "minimum": 0 },
|
|
338
|
+
"totalBytes": { "type": "integer", "minimum": 0 },
|
|
339
|
+
"slowThresholdMs": { "type": "integer", "minimum": 0 },
|
|
340
|
+
"endpoints": {
|
|
341
|
+
"type": "array",
|
|
342
|
+
"items": {
|
|
343
|
+
"type": "object",
|
|
344
|
+
"additionalProperties": false,
|
|
345
|
+
"required": ["method", "url"],
|
|
346
|
+
"properties": {
|
|
347
|
+
"method": { "type": "string", "minLength": 1 },
|
|
348
|
+
"url": { "type": "string", "minLength": 1 },
|
|
349
|
+
"status": { "type": "integer", "minimum": 0, "maximum": 599 },
|
|
350
|
+
"durationMs": { "type": "integer", "minimum": 0 },
|
|
351
|
+
"bytes": { "type": "integer", "minimum": 0 },
|
|
352
|
+
"count": { "type": "integer", "minimum": 1 },
|
|
353
|
+
"startedMs": {
|
|
354
|
+
"type": "integer",
|
|
355
|
+
"minimum": 0,
|
|
356
|
+
"description": "Offset from the first request, for the waterfall."
|
|
357
|
+
},
|
|
358
|
+
"issue": {
|
|
359
|
+
"enum": [
|
|
360
|
+
"failed",
|
|
361
|
+
"slow",
|
|
362
|
+
"duplicate",
|
|
363
|
+
"large-payload",
|
|
364
|
+
"uncached",
|
|
365
|
+
"polling",
|
|
366
|
+
"n-plus-one",
|
|
367
|
+
"race",
|
|
368
|
+
"retry"
|
|
369
|
+
]
|
|
370
|
+
},
|
|
371
|
+
"findingId": { "type": "string", "minLength": 1 }
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
"performance": {
|
|
378
|
+
"description": "Field measurements only. Every number here was observed in this run; nothing is estimated.",
|
|
379
|
+
"type": "object",
|
|
380
|
+
"additionalProperties": false,
|
|
381
|
+
"properties": {
|
|
382
|
+
"lcpMs": { "type": "number", "minimum": 0 },
|
|
383
|
+
"fcpMs": { "type": "number", "minimum": 0 },
|
|
384
|
+
"ttfbMs": { "type": "number", "minimum": 0 },
|
|
385
|
+
"inpMs": { "type": "number", "minimum": 0 },
|
|
386
|
+
"cls": { "type": "number", "minimum": 0 },
|
|
387
|
+
"longTasks": { "type": "integer", "minimum": 0 },
|
|
388
|
+
"longTaskMs": { "type": "number", "minimum": 0 },
|
|
389
|
+
"domNodes": { "type": "integer", "minimum": 0 },
|
|
390
|
+
"jsBytes": { "type": "integer", "minimum": 0 },
|
|
391
|
+
"cssBytes": { "type": "integer", "minimum": 0 },
|
|
392
|
+
"imageBytes": { "type": "integer", "minimum": 0 },
|
|
393
|
+
"totalBytes": { "type": "integer", "minimum": 0 },
|
|
394
|
+
"jsHeapBytes": { "type": "integer", "minimum": 0 },
|
|
395
|
+
"note": { "type": "string", "minLength": 1 }
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
"accessibility": {
|
|
399
|
+
"type": "object",
|
|
400
|
+
"additionalProperties": false,
|
|
401
|
+
"properties": {
|
|
402
|
+
"checked": { "type": "boolean" },
|
|
403
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
404
|
+
"violations": {
|
|
405
|
+
"type": "array",
|
|
406
|
+
"items": {
|
|
407
|
+
"type": "object",
|
|
408
|
+
"additionalProperties": false,
|
|
409
|
+
"required": ["rule", "impact", "count"],
|
|
410
|
+
"properties": {
|
|
411
|
+
"rule": { "type": "string", "minLength": 1 },
|
|
412
|
+
"impact": { "enum": ["critical", "serious", "moderate", "minor"] },
|
|
413
|
+
"count": { "type": "integer", "minimum": 1 },
|
|
414
|
+
"description": { "type": "string", "minLength": 1 },
|
|
415
|
+
"selector": { "type": "string", "minLength": 1 },
|
|
416
|
+
"findingId": { "type": "string", "minLength": 1 }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
"security": {
|
|
423
|
+
"type": "object",
|
|
424
|
+
"additionalProperties": false,
|
|
425
|
+
"properties": {
|
|
426
|
+
"checked": { "type": "boolean" },
|
|
427
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
428
|
+
"checks": {
|
|
429
|
+
"type": "array",
|
|
430
|
+
"items": {
|
|
431
|
+
"type": "object",
|
|
432
|
+
"additionalProperties": false,
|
|
433
|
+
"required": ["check", "status"],
|
|
434
|
+
"properties": {
|
|
435
|
+
"check": { "type": "string", "minLength": 1 },
|
|
436
|
+
"status": { "enum": ["pass", "fail", "warn", "not-checked"] },
|
|
437
|
+
"detail": { "type": "string", "minLength": 1 },
|
|
438
|
+
"findingId": { "type": "string", "minLength": 1 }
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
"console": {
|
|
445
|
+
"type": "object",
|
|
446
|
+
"additionalProperties": false,
|
|
447
|
+
"properties": {
|
|
448
|
+
"errors": { "type": "integer", "minimum": 0 },
|
|
449
|
+
"warnings": { "type": "integer", "minimum": 0 },
|
|
450
|
+
"unhandledRejections": { "type": "integer", "minimum": 0 },
|
|
451
|
+
"deprecations": { "type": "integer", "minimum": 0 },
|
|
452
|
+
"entries": {
|
|
453
|
+
"type": "array",
|
|
454
|
+
"items": {
|
|
455
|
+
"type": "object",
|
|
456
|
+
"additionalProperties": false,
|
|
457
|
+
"required": ["level", "message"],
|
|
458
|
+
"properties": {
|
|
459
|
+
"level": { "enum": ["error", "warning", "info", "debug"] },
|
|
460
|
+
"message": { "type": "string", "minLength": 1 },
|
|
461
|
+
"count": { "type": "integer", "minimum": 1 },
|
|
462
|
+
"source": { "type": "string", "minLength": 1 },
|
|
463
|
+
"findingId": { "type": "string", "minLength": 1 }
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
102
469
|
"evidence": {
|
|
103
470
|
"type": "array",
|
|
104
471
|
"minItems": 1,
|
|
@@ -117,11 +484,22 @@
|
|
|
117
484
|
"db",
|
|
118
485
|
"file",
|
|
119
486
|
"report",
|
|
120
|
-
"command"
|
|
487
|
+
"command",
|
|
488
|
+
"video",
|
|
489
|
+
"trace",
|
|
490
|
+
"performance",
|
|
491
|
+
"accessibility",
|
|
492
|
+
"security",
|
|
493
|
+
"log"
|
|
121
494
|
]
|
|
122
495
|
},
|
|
123
496
|
"description": { "type": "string", "minLength": 1 },
|
|
124
497
|
"source": { "type": "string", "minLength": 1 },
|
|
498
|
+
"artifactId": {
|
|
499
|
+
"type": "string",
|
|
500
|
+
"minLength": 1,
|
|
501
|
+
"description": "Preferred over source when the file is registered in artifacts[] — the renderer then knows its size, type, and whether it is actually there."
|
|
502
|
+
},
|
|
125
503
|
"excerpt": { "type": "string" }
|
|
126
504
|
}
|
|
127
505
|
}
|
|
@@ -154,7 +532,9 @@
|
|
|
154
532
|
"security",
|
|
155
533
|
"ui",
|
|
156
534
|
"ux",
|
|
157
|
-
"data"
|
|
535
|
+
"data",
|
|
536
|
+
"accessibility",
|
|
537
|
+
"console"
|
|
158
538
|
]
|
|
159
539
|
},
|
|
160
540
|
"title": { "type": "string", "minLength": 1 },
|
|
@@ -167,16 +547,86 @@
|
|
|
167
547
|
"confirmed",
|
|
168
548
|
"validated-user-report",
|
|
169
549
|
"could-not-reproduce",
|
|
170
|
-
"partial"
|
|
550
|
+
"partial",
|
|
551
|
+
"fixed-in-run"
|
|
171
552
|
]
|
|
172
553
|
},
|
|
554
|
+
"businessImpact": {
|
|
555
|
+
"type": "string",
|
|
556
|
+
"minLength": 1,
|
|
557
|
+
"description": "What it costs the business if this ships — in money, trust, compliance, or support load. Not a restatement of the defect."
|
|
558
|
+
},
|
|
559
|
+
"rootCause": {
|
|
560
|
+
"description": "The investigation, not the observation. Present only when the run actually established a cause.",
|
|
561
|
+
"type": "object",
|
|
562
|
+
"additionalProperties": false,
|
|
563
|
+
"required": ["summary"],
|
|
564
|
+
"properties": {
|
|
565
|
+
"summary": { "type": "string", "minLength": 1 },
|
|
566
|
+
"chain": {
|
|
567
|
+
"type": "array",
|
|
568
|
+
"items": { "type": "string", "minLength": 1 },
|
|
569
|
+
"description": "The causal steps, in order: what the click did, what the request returned, why, and what the UI failed to do about it."
|
|
570
|
+
},
|
|
571
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
572
|
+
"layer": {
|
|
573
|
+
"enum": ["frontend", "backend", "network", "data", "infrastructure", "third-party", "unknown"]
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
},
|
|
577
|
+
"regressionRisk": {
|
|
578
|
+
"type": "object",
|
|
579
|
+
"additionalProperties": false,
|
|
580
|
+
"required": ["level"],
|
|
581
|
+
"properties": {
|
|
582
|
+
"level": { "enum": ["high", "medium", "low"] },
|
|
583
|
+
"note": { "type": "string", "minLength": 1 },
|
|
584
|
+
"retest": {
|
|
585
|
+
"type": "array",
|
|
586
|
+
"items": { "type": "string", "minLength": 1 },
|
|
587
|
+
"description": "What QA should re-run once this is fixed."
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
"developerNotes": { "type": "string", "minLength": 1 },
|
|
592
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
593
|
+
"page": { "type": "string", "minLength": 1 },
|
|
594
|
+
"tags": {
|
|
595
|
+
"type": "array",
|
|
596
|
+
"items": { "type": "string", "minLength": 1 }
|
|
597
|
+
},
|
|
598
|
+
"affectedApis": {
|
|
599
|
+
"type": "array",
|
|
600
|
+
"items": { "type": "string", "minLength": 1 }
|
|
601
|
+
},
|
|
602
|
+
"metrics": {
|
|
603
|
+
"description": "Numbers that make the finding concrete, rendered as a small table on the card.",
|
|
604
|
+
"type": "array",
|
|
605
|
+
"items": {
|
|
606
|
+
"type": "object",
|
|
607
|
+
"additionalProperties": false,
|
|
608
|
+
"required": ["label", "value"],
|
|
609
|
+
"properties": {
|
|
610
|
+
"label": { "type": "string", "minLength": 1 },
|
|
611
|
+
"value": { "type": "string", "minLength": 1 },
|
|
612
|
+
"budget": { "type": "string", "minLength": 1 },
|
|
613
|
+
"breached": { "type": "boolean" }
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
"steps": {
|
|
618
|
+
"type": "array",
|
|
619
|
+
"items": { "type": "string", "minLength": 1 },
|
|
620
|
+
"description": "Reproduction as discrete steps. Preferred over repro prose when the run recorded them separately; repro stays required so a 1.0 reader keeps working."
|
|
621
|
+
},
|
|
173
622
|
"evidence": {
|
|
174
623
|
"type": "array",
|
|
175
624
|
"minItems": 1,
|
|
176
625
|
"items": {
|
|
177
626
|
"type": "object",
|
|
178
627
|
"additionalProperties": false,
|
|
179
|
-
"
|
|
628
|
+
"description": "Every entry must locate its file, by artifactId when the artifact is registered or by source when it is not. One or the other is required — an evidence entry that names neither is a claim with nothing behind it.",
|
|
629
|
+
"required": ["type"],
|
|
180
630
|
"properties": {
|
|
181
631
|
"type": {
|
|
182
632
|
"enum": [
|
|
@@ -186,12 +636,27 @@
|
|
|
186
636
|
"dom",
|
|
187
637
|
"har",
|
|
188
638
|
"db",
|
|
189
|
-
"command"
|
|
639
|
+
"command",
|
|
640
|
+
"video",
|
|
641
|
+
"trace",
|
|
642
|
+
"performance",
|
|
643
|
+
"accessibility",
|
|
644
|
+
"security",
|
|
645
|
+
"log"
|
|
190
646
|
]
|
|
191
647
|
},
|
|
192
648
|
"source": { "type": "string", "minLength": 1 },
|
|
649
|
+
"artifactId": { "type": "string", "minLength": 1 },
|
|
650
|
+
"caption": { "type": "string", "minLength": 1 },
|
|
193
651
|
"excerpt": { "type": "string" }
|
|
194
|
-
}
|
|
652
|
+
},
|
|
653
|
+
"allOf": [
|
|
654
|
+
{
|
|
655
|
+
"if": { "required": ["artifactId"] },
|
|
656
|
+
"then": {},
|
|
657
|
+
"else": { "required": ["source"] }
|
|
658
|
+
}
|
|
659
|
+
]
|
|
195
660
|
}
|
|
196
661
|
}
|
|
197
662
|
}
|
|
@@ -219,7 +684,13 @@
|
|
|
219
684
|
"status": {
|
|
220
685
|
"enum": ["pass", "fail", "blocked", "skipped"]
|
|
221
686
|
},
|
|
222
|
-
"findingId": { "type": "string" }
|
|
687
|
+
"findingId": { "type": "string" },
|
|
688
|
+
"source": {
|
|
689
|
+
"enum": ["user-supplied", "derived", "exploratory"],
|
|
690
|
+
"description": "Where the case came from. A user-supplied case carries different weight than one the run invented."
|
|
691
|
+
},
|
|
692
|
+
"page": { "type": "string", "minLength": 1 },
|
|
693
|
+
"note": { "type": "string", "minLength": 1 }
|
|
223
694
|
}
|
|
224
695
|
}
|
|
225
696
|
}
|
|
@@ -264,10 +735,45 @@
|
|
|
264
735
|
"required": ["action", "priority"],
|
|
265
736
|
"properties": {
|
|
266
737
|
"action": { "type": "string", "minLength": 1 },
|
|
267
|
-
"priority": { "enum": ["high", "medium", "low"] }
|
|
738
|
+
"priority": { "enum": ["high", "medium", "low"] },
|
|
739
|
+
"rationale": { "type": "string", "minLength": 1 },
|
|
740
|
+
"owner": {
|
|
741
|
+
"enum": ["frontend", "backend", "platform", "design", "qa", "product", "security"]
|
|
742
|
+
},
|
|
743
|
+
"effort": { "enum": ["small", "medium", "large"] },
|
|
744
|
+
"findingIds": {
|
|
745
|
+
"type": "array",
|
|
746
|
+
"items": { "type": "string", "minLength": 1 }
|
|
747
|
+
}
|
|
268
748
|
}
|
|
269
749
|
}
|
|
270
750
|
},
|
|
271
751
|
"metadata": { "type": "object" }
|
|
272
|
-
}
|
|
752
|
+
},
|
|
753
|
+
"allOf": [
|
|
754
|
+
{
|
|
755
|
+
"description": "A `ship` verdict cannot stand over a critical or high finding. The report's own severity legend says critical blocks release and high must be fixed before it, so a run that counts one and then recommends shipping is contradicting itself — and the contradiction is invisible to a reader who only reads the banner. `ship-with-risks` is the honest verdict for knowingly releasing over a high; there is no honest verdict for releasing over a critical.",
|
|
756
|
+
"if": {
|
|
757
|
+
"type": "object",
|
|
758
|
+
"required": ["executive"],
|
|
759
|
+
"properties": {
|
|
760
|
+
"executive": {
|
|
761
|
+
"type": "object",
|
|
762
|
+
"required": ["verdict"],
|
|
763
|
+
"properties": { "verdict": { "const": "ship" } }
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
},
|
|
767
|
+
"then": {
|
|
768
|
+
"properties": {
|
|
769
|
+
"severityCounts": {
|
|
770
|
+
"properties": {
|
|
771
|
+
"critical": { "maximum": 0 },
|
|
772
|
+
"high": { "maximum": 0 }
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
]
|
|
273
779
|
}
|
|
@@ -3,11 +3,50 @@
|
|
|
3
3
|
|
|
4
4
|
Live API audit during a browser explore session: use the app's own network traffic as the source of truth, then replay exact URLs. Consumed by the explore skill. Complements the REST domain (which covers API *test* quality).
|
|
5
5
|
|
|
6
|
+
## Count with the parser, judge with your eyes
|
|
7
|
+
|
|
8
|
+
**Do not count requests, time them, or spot duplicates by reading a network panel.**
|
|
9
|
+
Capture a HAR and let the engine do it:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
node <SKILL_DIR>/scripts/qa-tool.mjs analysis network <capture.har> \
|
|
13
|
+
[--slow-ms 1000] [--large-bytes 524288]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
What comes back is the report contract's `network` block, ready to drop into the result
|
|
17
|
+
and validate: `totalRequests`, `failedRequests`, `slowRequests`, `duplicateRequests`,
|
|
18
|
+
`totalBytes`, and one entry per endpoint carrying its method, URL, status, duration,
|
|
19
|
+
size, call count, start offset, and an `issue` flag.
|
|
20
|
+
|
|
21
|
+
The flags are assigned by stated rules, worst first:
|
|
22
|
+
|
|
23
|
+
| Flag | Rule |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `failed` | status 0, 4xx, or 5xx |
|
|
26
|
+
| `slow` | duration at or over the threshold (1000 ms by default) |
|
|
27
|
+
| `polling` | three or more identical calls at an even cadence — a timer, not a burst |
|
|
28
|
+
| `duplicate` | the same method and URL called more than once |
|
|
29
|
+
| `n-plus-one` | four or more distinct ids on one path shape inside two seconds |
|
|
30
|
+
| `large-payload` | response at or over the size threshold (512 KB by default) |
|
|
31
|
+
| `uncached` | a 200 for a static asset with neither `Cache-Control` nor `ETag` |
|
|
32
|
+
|
|
33
|
+
URLs and headers are redacted on the way through, so a token in a query string never
|
|
34
|
+
reaches the report.
|
|
35
|
+
|
|
36
|
+
**What is still yours.** The parser says *what happened*; only you can say whether it
|
|
37
|
+
matters. A duplicated analytics beacon and a duplicated payment request are identical in
|
|
38
|
+
a HAR and wildly different findings. Read the flags, then write findings for the ones
|
|
39
|
+
that affect this product — and set each finding's `affectedApis` to the endpoints the
|
|
40
|
+
block already named, so the report links them.
|
|
41
|
+
|
|
42
|
+
If no HAR is available, fall back to `performance.getEntriesByType('resource')` and say
|
|
43
|
+
in the report that the counts were observed rather than parsed.
|
|
44
|
+
|
|
6
45
|
## Best practices
|
|
7
46
|
|
|
8
47
|
- **Best practice:** prefer `performance.getEntriesByType('resource')` (or an equivalent HAR) over agent network monitors that miss XHR/fetch.
|
|
9
48
|
- **Best practice:** replay the application's exact URLs and methods via in-page fetch (using the page's existing auth), not guessed GET probes against POST endpoints.
|
|
10
|
-
- **
|
|
49
|
+
- **Best practice:** run `analysis network` on a HAR for the counts and flags; classify each flagged call as relevant or not, rather than counting by hand.
|
|
11
50
|
- **Recommendation:** capture UI values with an ISO timestamp when comparing to API or DB responses; match within a stated freshness window.
|
|
12
51
|
- **Best practice:** redact Authorization headers, cookies, tokens, and PII from every excerpt written to artifacts.
|
|
13
52
|
|