qa-engineer 0.9.2 → 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/COMPATIBILITY.md +26 -12
- package/README.md +182 -84
- package/package.json +11 -20
- package/packages/engine/bin/qa-engine.mjs +560 -0
- package/packages/engine/lib/analysis/branding.mjs +187 -0
- package/packages/engine/lib/analysis/context.mjs +294 -0
- package/packages/engine/lib/analysis/contracts.mjs +149 -0
- package/packages/engine/lib/analysis/diff-guard.mjs +425 -0
- package/packages/engine/lib/analysis/discovery.mjs +220 -0
- package/packages/engine/lib/analysis/evidence.mjs +116 -0
- package/packages/engine/lib/analysis/har.mjs +124 -0
- package/packages/engine/lib/analysis/junit.mjs +126 -0
- package/packages/engine/lib/analysis/network.mjs +237 -0
- package/packages/engine/lib/analysis/redaction.mjs +127 -0
- package/packages/engine/lib/analysis/report-html.mjs +76 -0
- package/packages/engine/lib/analysis/taxonomy.mjs +109 -0
- package/packages/engine/lib/analysis/xml.mjs +153 -0
- package/packages/engine/lib/analysis/zip.mjs +107 -0
- 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/diagnostics/engine.mjs +165 -0
- package/packages/engine/lib/diagnostics/internal-contracts.mjs +50 -0
- package/packages/engine/lib/diagnostics/prioritization.mjs +99 -0
- package/packages/engine/lib/diagnostics/repair.mjs +73 -0
- package/packages/engine/lib/diagnostics/root-cause.mjs +92 -0
- package/packages/engine/lib/diagnostics/timeline.mjs +101 -0
- package/packages/engine/lib/frameworks/junit-frameworks.mjs +48 -0
- package/packages/engine/lib/frameworks/playwright.mjs +158 -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 +14 -0
- 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 +13 -20
- package/packages/installer/lib/commands/install.mjs +160 -91
- 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/bundle.mjs +69 -92
- 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/manifest.mjs +48 -57
- 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 +49 -31
- package/packages/installer/package.json +1 -1
- package/packages/installer/schemas/qa-lock.schema.json +119 -21
- package/shared/tooling/qa-tool.mjs +161 -0
- package/skills/qa-api/SKILL.md +5 -5
- package/skills/qa-api/references/deterministic-tooling.md +34 -32
- package/skills/qa-api/references/evidence-and-reporting.md +5 -5
- package/skills/qa-api/scripts/qa-tool.mjs +162 -0
- package/skills/qa-audit/SKILL.md +5 -5
- package/skills/qa-audit/references/deterministic-tooling.md +34 -32
- package/skills/qa-audit/references/evidence-and-reporting.md +5 -5
- package/skills/qa-audit/scripts/qa-tool.mjs +162 -0
- package/skills/qa-debug/SKILL.md +6 -6
- package/skills/qa-debug/references/deterministic-tooling.md +34 -32
- package/skills/qa-debug/references/diagnostic-engine.md +3 -3
- package/skills/qa-debug/references/evidence-and-reporting.md +5 -5
- package/skills/qa-debug/scripts/qa-tool.mjs +162 -0
- package/skills/qa-explore/SKILL.md +31 -15
- 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/deterministic-tooling.md +34 -32
- package/skills/qa-explore/references/evidence-and-reporting.md +5 -5
- package/skills/qa-explore/references/report-pipeline.md +266 -96
- package/skills/qa-explore/scripts/qa-tool.mjs +162 -0
- package/skills/qa-fix/SKILL.md +4 -4
- package/skills/qa-fix/references/deterministic-tooling.md +34 -32
- package/skills/qa-fix/references/diagnostic-engine.md +3 -3
- package/skills/qa-fix/references/evidence-and-reporting.md +5 -5
- package/skills/qa-fix/scripts/qa-tool.mjs +162 -0
- package/skills/qa-flaky/SKILL.md +4 -4
- package/skills/qa-flaky/references/deterministic-tooling.md +34 -32
- package/skills/qa-flaky/references/evidence-and-reporting.md +5 -5
- package/skills/qa-flaky/scripts/qa-tool.mjs +162 -0
- package/skills/qa-generate/references/evidence-and-reporting.md +5 -5
- package/skills/qa-init/SKILL.md +4 -4
- package/skills/qa-init/references/deterministic-tooling.md +34 -32
- package/skills/qa-init/references/evidence-and-reporting.md +5 -5
- package/skills/qa-init/scripts/qa-tool.mjs +162 -0
- package/skills/qa-report/SKILL.md +7 -7
- package/skills/qa-report/references/deterministic-tooling.md +34 -32
- package/skills/qa-report/references/diagnostic-engine.md +3 -3
- package/skills/qa-report/references/evidence-and-reporting.md +5 -5
- package/skills/qa-report/scripts/qa-tool.mjs +162 -0
- package/skills/qa-review/references/evidence-and-reporting.md +5 -5
- package/skills/qa-run/SKILL.md +6 -6
- package/skills/qa-run/references/deterministic-tooling.md +34 -32
- package/skills/qa-run/references/evidence-and-reporting.md +5 -5
- package/skills/qa-run/scripts/qa-tool.mjs +162 -0
- package/shared/analysis/lib/qa_analysis/__init__.py +0 -11
- package/shared/analysis/lib/qa_analysis/branding.py +0 -175
- package/shared/analysis/lib/qa_analysis/cli.py +0 -144
- package/shared/analysis/lib/qa_analysis/context.py +0 -233
- package/shared/analysis/lib/qa_analysis/contracts.py +0 -158
- package/shared/analysis/lib/qa_analysis/diff_guard.py +0 -327
- package/shared/analysis/lib/qa_analysis/discovery.py +0 -113
- package/shared/analysis/lib/qa_analysis/evidence.py +0 -128
- package/shared/analysis/lib/qa_analysis/har.py +0 -87
- package/shared/analysis/lib/qa_analysis/junit.py +0 -104
- package/shared/analysis/lib/qa_analysis/redaction.py +0 -107
- package/shared/analysis/lib/qa_analysis/report_html.py +0 -781
- package/shared/analysis/lib/qa_analysis/taxonomy.py +0 -98
- package/shared/diagnostics/lib/qa_diagnostics/__init__.py +0 -13
- package/shared/diagnostics/lib/qa_diagnostics/cli.py +0 -124
- package/shared/diagnostics/lib/qa_diagnostics/engine.py +0 -143
- package/shared/diagnostics/lib/qa_diagnostics/internal_contracts.py +0 -75
- package/shared/diagnostics/lib/qa_diagnostics/prioritization.py +0 -101
- package/shared/diagnostics/lib/qa_diagnostics/repair.py +0 -72
- package/shared/diagnostics/lib/qa_diagnostics/root_cause.py +0 -89
- package/shared/diagnostics/lib/qa_diagnostics/timeline.py +0 -71
- package/shared/frameworks/cypress/lib/cypress_analysis.py +0 -27
- package/shared/frameworks/playwright/lib/playwright_analysis.py +0 -167
- package/shared/frameworks/selenium/lib/selenium_analysis.py +0 -28
- package/shared/frameworks/webdriverio/lib/webdriverio_analysis.py +0 -24
- package/shared/tooling/qa_tool.py +0 -127
- /package/{shared/analysis/lib/qa_analysis → packages/engine/lib/analysis}/branding.json +0 -0
- /package/{shared → packages/engine/lib}/analysis/schemas/context.schema.json +0 -0
- /package/{shared → packages/engine/lib}/diagnostics/schemas/internal/analysis-result.schema.json +0 -0
- /package/{shared → packages/engine/lib}/diagnostics/schemas/internal/diagnosis.schema.json +0 -0
- /package/{shared → packages/engine/lib}/diagnostics/schemas/internal/execution-result-min.schema.json +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Every input shape the renderer accepts, folded into one internal shape.
|
|
2
|
+
//
|
|
3
|
+
// ## Why this seam exists
|
|
4
|
+
//
|
|
5
|
+
// The promise is that a report looks the same whoever produced it — Claude Code,
|
|
6
|
+
// Cursor, Codex, a future agent nobody has written yet. That promise is only keepable
|
|
7
|
+
// if *no producer ever touches presentation*, which in turn is only enforceable if
|
|
8
|
+
// every producer hands over the same kind of thing: structured data, validated, and
|
|
9
|
+
// then rendered by code none of them can influence.
|
|
10
|
+
//
|
|
11
|
+
// This module is where "the same kind of thing" is defined. It accepts:
|
|
12
|
+
//
|
|
13
|
+
// qa-engineer/qa-report the canonical, producer-neutral report (schema 2.0)
|
|
14
|
+
// qa-explore/explore-result the exploratory contract (schema 1.x)
|
|
15
|
+
// qa-report/report-result the release rollup (schema 1.x)
|
|
16
|
+
//
|
|
17
|
+
// and returns one internal shape. Adding a fourth producer means adding a normalizer
|
|
18
|
+
// here — not a stylesheet, not a template, and certainly not a second renderer.
|
|
19
|
+
//
|
|
20
|
+
// ## Why the older contracts are not simply migrated
|
|
21
|
+
//
|
|
22
|
+
// Reports are archived. A result written a year ago must still render, and an
|
|
23
|
+
// installed skill emitting schema 1.1 must not break when the pack updates. So the
|
|
24
|
+
// older shapes are translated on read rather than deprecated on write, and the
|
|
25
|
+
// canonical shape is what new producers are pointed at.
|
|
26
|
+
//
|
|
27
|
+
// ## What a producer may not do
|
|
28
|
+
//
|
|
29
|
+
// Nothing here reads a colour, a class name, a font, or a layout hint, and the
|
|
30
|
+
// canonical schema has no field to carry one. An agent that wants its report to look
|
|
31
|
+
// different has no mechanism to make it so — which is the point.
|
|
32
|
+
|
|
33
|
+
import { schemaVersionOf, SUPPORTED_SCHEMA_VERSIONS, isSupportedSchema } from '../version.mjs';
|
|
34
|
+
|
|
35
|
+
export class SchemaError extends Error {
|
|
36
|
+
name = 'SchemaError';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The canonical report, unwrapped into the internal shape.
|
|
41
|
+
*
|
|
42
|
+
* The canonical form groups fields by audience — `summary` for the decision,
|
|
43
|
+
* `coverage` for what was looked at, `issues` for the detail — because that is how a
|
|
44
|
+
* producer thinks about filling it in. The internal shape is flat because that is how
|
|
45
|
+
* fifteen sections read from it. Neither is wrong; this function is the join.
|
|
46
|
+
*/
|
|
47
|
+
function fromCanonical(report) {
|
|
48
|
+
const metadata = report.metadata ?? {};
|
|
49
|
+
const summary = report.summary ?? {};
|
|
50
|
+
const coverage = report.coverage ?? {};
|
|
51
|
+
const recommendations = report.recommendations ?? {};
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
contract: { name: 'qa-engineer/qa-report', version: report.schemaVersion ?? '2.0' },
|
|
55
|
+
skill: metadata.producer
|
|
56
|
+
? { name: metadata.producer.skill ?? metadata.producer.agent ?? 'unknown', version: metadata.producer.version ?? '' }
|
|
57
|
+
: undefined,
|
|
58
|
+
producer: metadata.producer ?? null,
|
|
59
|
+
|
|
60
|
+
title: metadata.title,
|
|
61
|
+
url: metadata.url,
|
|
62
|
+
environment: metadata.environment,
|
|
63
|
+
generatedAt: metadata.generatedAt,
|
|
64
|
+
durationMs: metadata.durationMs,
|
|
65
|
+
reportVersion: metadata.reportVersion,
|
|
66
|
+
browserAdapter: metadata.browserAdapter,
|
|
67
|
+
authentication: metadata.authentication,
|
|
68
|
+
|
|
69
|
+
summary: summary.text ?? '',
|
|
70
|
+
classification: summary.classification ?? null,
|
|
71
|
+
executive: {
|
|
72
|
+
verdict: summary.verdict,
|
|
73
|
+
headline: summary.headline,
|
|
74
|
+
health: summary.health,
|
|
75
|
+
risks: summary.risks,
|
|
76
|
+
recommendedAction: summary.recommendedAction,
|
|
77
|
+
estimatedFixHours: summary.estimatedFixHours,
|
|
78
|
+
confidence: summary.confidence,
|
|
79
|
+
},
|
|
80
|
+
scores: summary.scores,
|
|
81
|
+
severityCounts: summary.severityCounts ?? countSeverities(report.issues ?? []),
|
|
82
|
+
|
|
83
|
+
scope: {
|
|
84
|
+
objective: coverage.objective,
|
|
85
|
+
covered: coverage.covered,
|
|
86
|
+
notCovered: coverage.notCovered,
|
|
87
|
+
},
|
|
88
|
+
dimensionsRun: coverage.dimensionsRun,
|
|
89
|
+
pages: coverage.pages,
|
|
90
|
+
testCases: coverage.testCases,
|
|
91
|
+
timeline: coverage.timeline,
|
|
92
|
+
|
|
93
|
+
// `issues` is the canonical name; `findings` is what fifteen sections already call
|
|
94
|
+
// them. Renaming the sections to match would be churn with no reader benefit.
|
|
95
|
+
findings: report.issues ?? [],
|
|
96
|
+
artifacts: report.artifacts,
|
|
97
|
+
evidence: report.evidence ?? [],
|
|
98
|
+
|
|
99
|
+
performance: report.performance,
|
|
100
|
+
security: report.security,
|
|
101
|
+
accessibility: report.accessibility,
|
|
102
|
+
console: report.console,
|
|
103
|
+
network: report.network,
|
|
104
|
+
dbValidation: report.dataValidation,
|
|
105
|
+
|
|
106
|
+
fixOrder: recommendations.fixOrder,
|
|
107
|
+
recommendations: recommendations.actions,
|
|
108
|
+
whatWorksWell: recommendations.whatWorksWell,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Severity totals, when the producer did not count them itself. */
|
|
113
|
+
function countSeverities(issues) {
|
|
114
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
115
|
+
for (const issue of issues) {
|
|
116
|
+
if (counts[issue.severity] !== undefined) counts[issue.severity] += 1;
|
|
117
|
+
}
|
|
118
|
+
return counts;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fold `qa-report/report-result` — the release rollup — into the same shape.
|
|
123
|
+
*
|
|
124
|
+
* The rollup describes the same event from further away: one summary of many runs
|
|
125
|
+
* rather than one run of a product. Its readiness verdict becomes the executive
|
|
126
|
+
* verdict and its test summary becomes the test counts. Its failure list stays a list
|
|
127
|
+
* of failures rather than being forced into `findings` with an invented severity for
|
|
128
|
+
* each row — a failing assertion is not a severity-ranked defect, and pretending
|
|
129
|
+
* otherwise would put fabricated numbers in the summary tiles.
|
|
130
|
+
*/
|
|
131
|
+
function fromRollup(result) {
|
|
132
|
+
const readiness = result.releaseReadiness ?? {};
|
|
133
|
+
const tests = result.testSummary ?? {};
|
|
134
|
+
const summaries = result.summaries ?? {};
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
...result,
|
|
138
|
+
executive: result.executive ?? {
|
|
139
|
+
verdict: readiness.verdict ?? result.classification,
|
|
140
|
+
headline: readiness.rationale ?? result.summary,
|
|
141
|
+
health: summaries.executive ?? null,
|
|
142
|
+
},
|
|
143
|
+
severityCounts: result.severityCounts ?? { critical: 0, high: 0, medium: 0, low: 0 },
|
|
144
|
+
testCases: result.testCases ?? (Object.keys(tests).length > 0
|
|
145
|
+
? {
|
|
146
|
+
total: tests.total ?? 0,
|
|
147
|
+
passed: tests.passed ?? 0,
|
|
148
|
+
failed: tests.failed ?? 0,
|
|
149
|
+
blocked: tests.blocked ?? 0,
|
|
150
|
+
skipped: tests.skipped ?? 0,
|
|
151
|
+
cases: [],
|
|
152
|
+
}
|
|
153
|
+
: null),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The exploratory contract is already the internal shape; nothing to translate. */
|
|
158
|
+
function fromExplore(result) {
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const NORMALIZERS = Object.freeze({
|
|
163
|
+
'qa-engineer/qa-report': fromCanonical,
|
|
164
|
+
'qa-explore/explore-result': fromExplore,
|
|
165
|
+
'qa-report/report-result': fromRollup,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
/** Contract names this renderer accepts, for an error message and for `--help`. */
|
|
169
|
+
export function supportedContracts() {
|
|
170
|
+
return Object.keys(NORMALIZERS).sort();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Which producer made this report, for the provenance block.
|
|
175
|
+
*
|
|
176
|
+
* Recorded and displayed, never acted on: the renderer's behaviour must not vary by
|
|
177
|
+
* producer, or the guarantee that two agents produce identical output is only true
|
|
178
|
+
* until someone adds a special case.
|
|
179
|
+
*/
|
|
180
|
+
export function producerOf(result) {
|
|
181
|
+
const producer = result?.metadata?.producer ?? result?.producer;
|
|
182
|
+
if (producer) {
|
|
183
|
+
return {
|
|
184
|
+
agent: producer.agent ?? null,
|
|
185
|
+
model: producer.model ?? null,
|
|
186
|
+
skill: producer.skill ?? null,
|
|
187
|
+
version: producer.version ?? null,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (result?.skill) {
|
|
191
|
+
return { agent: null, model: null, skill: result.skill.name, version: result.skill.version };
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Normalize any accepted contract into the internal shape.
|
|
198
|
+
*
|
|
199
|
+
* Throws rather than guessing. A result whose contract is unknown, or whose schema
|
|
200
|
+
* version this renderer has never been tested against, is refused by name — rendering
|
|
201
|
+
* it half-understood would produce a document that looks authoritative and is missing
|
|
202
|
+
* whatever the unknown version added.
|
|
203
|
+
*/
|
|
204
|
+
export function normalize(result) {
|
|
205
|
+
const name = result?.contract?.name;
|
|
206
|
+
const normalizer = NORMALIZERS[name];
|
|
207
|
+
|
|
208
|
+
if (!normalizer) {
|
|
209
|
+
throw new SchemaError(
|
|
210
|
+
`no renderer for contract ${name === undefined ? 'None' : `'${name}'`}; supported: ` +
|
|
211
|
+
`${supportedContracts().join(', ')}`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!isSupportedSchema(result)) {
|
|
216
|
+
throw new SchemaError(
|
|
217
|
+
`report declares schema version ${schemaVersionOf(result)}, which this renderer ` +
|
|
218
|
+
`does not support; supported: ${SUPPORTED_SCHEMA_VERSIONS.join(', ')}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const normalized = normalizer(result);
|
|
223
|
+
// Provenance survives normalization so the appendix can state who produced the
|
|
224
|
+
// report even when the contract carried it somewhere else.
|
|
225
|
+
return { ...normalized, producer: producerOf(result) };
|
|
226
|
+
}
|