qa-engineer 0.9.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 +71 -0
- package/LICENSE +21 -0
- package/README.md +508 -0
- package/package.json +90 -0
- package/packages/installer/README.md +46 -0
- package/packages/installer/bin/qa.mjs +136 -0
- package/packages/installer/lib/agents/registry.mjs +209 -0
- package/packages/installer/lib/cli/commands.mjs +29 -0
- package/packages/installer/lib/cli/flags.mjs +64 -0
- package/packages/installer/lib/commands/doctor.mjs +190 -0
- package/packages/installer/lib/commands/install.mjs +291 -0
- package/packages/installer/lib/commands/onboard.mjs +163 -0
- package/packages/installer/lib/commands/repair.mjs +68 -0
- package/packages/installer/lib/commands/self-test.mjs +45 -0
- package/packages/installer/lib/commands/uninstall.mjs +167 -0
- package/packages/installer/lib/commands/update.mjs +69 -0
- package/packages/installer/lib/commands/verify.mjs +62 -0
- package/packages/installer/lib/constants.mjs +34 -0
- package/packages/installer/lib/core/bundle.mjs +124 -0
- package/packages/installer/lib/core/config.mjs +73 -0
- package/packages/installer/lib/core/conflict.mjs +29 -0
- package/packages/installer/lib/core/errors.mjs +29 -0
- package/packages/installer/lib/core/fs-safe.mjs +236 -0
- package/packages/installer/lib/core/hash.mjs +19 -0
- package/packages/installer/lib/core/lockfile.mjs +75 -0
- package/packages/installer/lib/core/logger.mjs +46 -0
- package/packages/installer/lib/core/manifest.mjs +89 -0
- package/packages/installer/lib/core/paths.mjs +74 -0
- package/packages/installer/lib/core/schema-validate.mjs +142 -0
- package/packages/installer/lib/core/skill-meta.mjs +75 -0
- package/packages/installer/lib/core/validate-install.mjs +152 -0
- package/packages/installer/lib/core/wrappers.mjs +91 -0
- package/packages/installer/lib/detect/environment.mjs +54 -0
- package/packages/installer/lib/detect/frameworks.mjs +94 -0
- package/packages/installer/lib/detect/project.mjs +126 -0
- package/packages/installer/lib/detect/recommend.mjs +85 -0
- package/packages/installer/lib/detect/scan.mjs +48 -0
- package/packages/installer/lib/ui/progress.mjs +32 -0
- package/packages/installer/lib/ui/theme.mjs +81 -0
- package/packages/installer/lib/version.mjs +47 -0
- package/packages/installer/package.json +28 -0
- package/packages/installer/schemas/qa-lock.schema.json +89 -0
- package/packages/installer/schemas/qa.config.schema.json +91 -0
- package/shared/analysis/lib/qa_analysis/__init__.py +11 -0
- package/shared/analysis/lib/qa_analysis/branding.json +9 -0
- package/shared/analysis/lib/qa_analysis/branding.py +175 -0
- package/shared/analysis/lib/qa_analysis/cli.py +129 -0
- package/shared/analysis/lib/qa_analysis/context.py +233 -0
- package/shared/analysis/lib/qa_analysis/contracts.py +158 -0
- package/shared/analysis/lib/qa_analysis/diff_guard.py +327 -0
- package/shared/analysis/lib/qa_analysis/discovery.py +113 -0
- package/shared/analysis/lib/qa_analysis/evidence.py +128 -0
- package/shared/analysis/lib/qa_analysis/har.py +58 -0
- package/shared/analysis/lib/qa_analysis/junit.py +80 -0
- package/shared/analysis/lib/qa_analysis/redaction.py +99 -0
- package/shared/analysis/lib/qa_analysis/taxonomy.py +98 -0
- package/shared/analysis/schemas/context.schema.json +82 -0
- package/shared/diagnostics/lib/qa_diagnostics/__init__.py +13 -0
- package/shared/diagnostics/lib/qa_diagnostics/cli.py +124 -0
- package/shared/diagnostics/lib/qa_diagnostics/engine.py +143 -0
- package/shared/diagnostics/lib/qa_diagnostics/internal_contracts.py +75 -0
- package/shared/diagnostics/lib/qa_diagnostics/prioritization.py +101 -0
- package/shared/diagnostics/lib/qa_diagnostics/repair.py +72 -0
- package/shared/diagnostics/lib/qa_diagnostics/root_cause.py +89 -0
- package/shared/diagnostics/lib/qa_diagnostics/timeline.py +71 -0
- package/shared/diagnostics/schemas/internal/analysis-result.schema.json +28 -0
- package/shared/diagnostics/schemas/internal/diagnosis.schema.json +55 -0
- package/shared/diagnostics/schemas/internal/execution-result-min.schema.json +34 -0
- package/shared/frameworks/cypress/lib/cypress_analysis.py +27 -0
- package/shared/frameworks/playwright/lib/playwright_analysis.py +167 -0
- package/shared/frameworks/registry.json +150 -0
- package/shared/frameworks/registry.mjs +37 -0
- package/shared/frameworks/registry.schema.json +74 -0
- package/shared/frameworks/selenium/lib/selenium_analysis.py +28 -0
- package/shared/frameworks/webdriverio/lib/webdriverio_analysis.py +24 -0
- package/shared/tooling/qa_tool.py +127 -0
- package/skills/README.md +31 -0
- package/skills/qa/README.md +19 -0
- package/skills/qa/SKILL.md +52 -0
- package/skills/qa/examples/routing.md +86 -0
- package/skills/qa/references/routing-map.md +33 -0
- package/skills/qa-api/README.md +19 -0
- package/skills/qa-api/SKILL.md +68 -0
- package/skills/qa-api/contracts/api-result.schema.json +51 -0
- package/skills/qa-api/examples/graphql-review.md +43 -0
- package/skills/qa-api/references/authentication.md +45 -0
- package/skills/qa-api/references/deterministic-tooling.md +128 -0
- package/skills/qa-api/references/evidence-and-reporting.md +66 -0
- package/skills/qa-api/references/graphql.md +44 -0
- package/skills/qa-api/references/rest.md +45 -0
- package/skills/qa-api/references/websocket.md +44 -0
- package/skills/qa-audit/README.md +19 -0
- package/skills/qa-audit/SKILL.md +70 -0
- package/skills/qa-audit/contracts/audit-result.schema.json +63 -0
- package/skills/qa-audit/examples/accessibility-audit.md +46 -0
- package/skills/qa-audit/references/accessibility.md +44 -0
- package/skills/qa-audit/references/deterministic-tooling.md +128 -0
- package/skills/qa-audit/references/evidence-and-reporting.md +66 -0
- package/skills/qa-audit/references/performance.md +44 -0
- package/skills/qa-audit/references/security.md +43 -0
- package/skills/qa-audit/references/visual-testing.md +44 -0
- package/skills/qa-debug/README.md +19 -0
- package/skills/qa-debug/SKILL.md +76 -0
- package/skills/qa-debug/contracts/debug-result.schema.json +121 -0
- package/skills/qa-debug/examples/failed-login.md +61 -0
- package/skills/qa-debug/examples/locator-break.md +59 -0
- package/skills/qa-debug/examples/network-timeout.md +61 -0
- package/skills/qa-debug/examples/successful-debug.md +62 -0
- package/skills/qa-debug/references/confidence-model.md +26 -0
- package/skills/qa-debug/references/deterministic-tooling.md +128 -0
- package/skills/qa-debug/references/diagnostic-engine.md +36 -0
- package/skills/qa-debug/references/evidence-and-reporting.md +66 -0
- package/skills/qa-debug/references/evidence-model.md +41 -0
- package/skills/qa-debug/references/failure-taxonomy.md +44 -0
- package/skills/qa-debug/references/finding-prioritization.md +43 -0
- package/skills/qa-debug/references/investigation-workflow.md +48 -0
- package/skills/qa-debug/references/recommendation-ranking.md +34 -0
- package/skills/qa-debug/references/root-cause-analysis.md +49 -0
- package/skills/qa-debug/references/timeline-builder.md +37 -0
- package/skills/qa-example/README.md +19 -0
- package/skills/qa-example/SKILL.md +60 -0
- package/skills/qa-example/contracts/self-check-report.schema.json +65 -0
- package/skills/qa-example/examples/self-check.md +53 -0
- package/skills/qa-example/references/example-domain.md +28 -0
- package/skills/qa-example/references/skill-format-notes.md +26 -0
- package/skills/qa-explore/README.md +22 -0
- package/skills/qa-explore/SKILL.md +87 -0
- package/skills/qa-explore/contracts/explore-result.schema.json +249 -0
- package/skills/qa-explore/examples/attached-test-cases.md +70 -0
- package/skills/qa-explore/examples/screenshot-proof-finding.md +50 -0
- package/skills/qa-explore/examples/url-smoke-explore.md +80 -0
- package/skills/qa-explore/references/accessibility.md +44 -0
- package/skills/qa-explore/references/api-replay.md +48 -0
- package/skills/qa-explore/references/browser-adapters.md +51 -0
- package/skills/qa-explore/references/evidence-and-reporting.md +66 -0
- package/skills/qa-explore/references/evidence-capture.md +54 -0
- package/skills/qa-explore/references/exploratory-qa.md +52 -0
- package/skills/qa-explore/references/finding-taxonomy.md +47 -0
- package/skills/qa-explore/references/performance.md +44 -0
- package/skills/qa-explore/references/pipeline.md +74 -0
- package/skills/qa-explore/references/report-pipeline.md +69 -0
- package/skills/qa-explore/references/security.md +43 -0
- package/skills/qa-explore/references/test-case-intake.md +44 -0
- package/skills/qa-fix/README.md +19 -0
- package/skills/qa-fix/SKILL.md +70 -0
- package/skills/qa-fix/contracts/fix-result.schema.json +132 -0
- package/skills/qa-fix/examples/repair-plan.md +56 -0
- package/skills/qa-fix/references/deterministic-tooling.md +128 -0
- package/skills/qa-fix/references/diagnostic-engine.md +36 -0
- package/skills/qa-fix/references/evidence-and-reporting.md +66 -0
- package/skills/qa-fix/references/repair-strategy.md +48 -0
- package/skills/qa-fix/references/root-cause-analysis.md +49 -0
- package/skills/qa-fix/references/suite-extension.md +73 -0
- package/skills/qa-flaky/README.md +19 -0
- package/skills/qa-flaky/SKILL.md +67 -0
- package/skills/qa-flaky/contracts/flaky-result.schema.json +62 -0
- package/skills/qa-flaky/examples/flaky-locator.md +46 -0
- package/skills/qa-flaky/references/deterministic-tooling.md +128 -0
- package/skills/qa-flaky/references/evidence-and-reporting.md +66 -0
- package/skills/qa-flaky/references/flakiness.md +48 -0
- package/skills/qa-flaky/references/retry.md +44 -0
- package/skills/qa-flaky/references/waiting-strategies.md +44 -0
- package/skills/qa-generate/README.md +42 -0
- package/skills/qa-generate/SKILL.md +83 -0
- package/skills/qa-generate/contracts/generation-result.schema.json +124 -0
- package/skills/qa-generate/examples/bootstrap-new-framework.md +24 -0
- package/skills/qa-generate/examples/extend-existing-suite.md +26 -0
- package/skills/qa-generate/references/code-style.md +29 -0
- package/skills/qa-generate/references/evidence-and-reporting.md +66 -0
- package/skills/qa-generate/references/framework-selection.md +88 -0
- package/skills/qa-generate/references/generation-strategy.md +48 -0
- package/skills/qa-generate/references/naming-conventions.md +27 -0
- package/skills/qa-generate/references/playwright-conventions.md +23 -0
- package/skills/qa-generate/references/playwright-generation.md +41 -0
- package/skills/qa-generate/references/playwright-project-discovery.md +42 -0
- package/skills/qa-generate/references/project-bootstrap.md +54 -0
- package/skills/qa-generate/references/repository-analysis.md +43 -0
- package/skills/qa-generate/references/suite-extension.md +73 -0
- package/skills/qa-generate/references/template-selection.md +48 -0
- package/skills/qa-generate/templates/playwright/api.ts +36 -0
- package/skills/qa-generate/templates/playwright/base-page.ts +14 -0
- package/skills/qa-generate/templates/playwright/data.ts +25 -0
- package/skills/qa-generate/templates/playwright/env.example +16 -0
- package/skills/qa-generate/templates/playwright/example.spec.ts +14 -0
- package/skills/qa-generate/templates/playwright/fixtures.ts +28 -0
- package/skills/qa-generate/templates/playwright/framework-readme.md +39 -0
- package/skills/qa-generate/templates/playwright/login.page.ts +26 -0
- package/skills/qa-generate/templates/playwright/playwright.config.ts +28 -0
- package/skills/qa-generate/templates/playwright/utils.ts +18 -0
- package/skills/qa-init/README.md +20 -0
- package/skills/qa-init/SKILL.md +72 -0
- package/skills/qa-init/examples/initialize-a-repo.md +91 -0
- package/skills/qa-init/references/detection-guide.md +76 -0
- package/skills/qa-init/references/deterministic-tooling.md +128 -0
- package/skills/qa-init/references/evidence-and-reporting.md +66 -0
- package/skills/qa-init/templates/context.md +68 -0
- package/skills/qa-report/README.md +19 -0
- package/skills/qa-report/SKILL.md +72 -0
- package/skills/qa-report/contracts/report-result.schema.json +186 -0
- package/skills/qa-report/examples/release-report.md +68 -0
- package/skills/qa-report/references/deterministic-tooling.md +128 -0
- package/skills/qa-report/references/diagnostic-engine.md +36 -0
- package/skills/qa-report/references/evidence-and-reporting.md +66 -0
- package/skills/qa-report/references/finding-prioritization.md +43 -0
- package/skills/qa-report/references/recommendation-ranking.md +34 -0
- package/skills/qa-report/references/report-aggregation.md +44 -0
- package/skills/qa-review/README.md +19 -0
- package/skills/qa-review/SKILL.md +58 -0
- package/skills/qa-review/contracts/review-result.schema.json +59 -0
- package/skills/qa-review/examples/suite-review.md +51 -0
- package/skills/qa-review/references/anti-patterns.md +49 -0
- package/skills/qa-review/references/assertion-patterns.md +45 -0
- package/skills/qa-review/references/evidence-and-reporting.md +66 -0
- package/skills/qa-review/references/fixtures.md +45 -0
- package/skills/qa-review/references/page-objects.md +45 -0
- package/skills/qa-run/README.md +22 -0
- package/skills/qa-run/SKILL.md +92 -0
- package/skills/qa-run/contracts/execution-plan.schema.json +132 -0
- package/skills/qa-run/contracts/execution-result.schema.json +204 -0
- package/skills/qa-run/examples/execute-playwright.md +89 -0
- package/skills/qa-run/examples/plan-a-run.md +83 -0
- package/skills/qa-run/references/artifact-collector.md +40 -0
- package/skills/qa-run/references/browser-launch.md +39 -0
- package/skills/qa-run/references/command-builder.md +39 -0
- package/skills/qa-run/references/deterministic-tooling.md +128 -0
- package/skills/qa-run/references/environment-detection.md +37 -0
- package/skills/qa-run/references/evidence-and-reporting.md +66 -0
- package/skills/qa-run/references/execution-strategy.md +54 -0
- package/skills/qa-run/references/playwright-artifacts.md +29 -0
- package/skills/qa-run/references/playwright-execution.md +48 -0
- package/skills/qa-run/references/playwright-project-discovery.md +42 -0
- package/skills/qa-run/references/report-normalization.md +49 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Deterministic timeline reconstruction.
|
|
2
|
+
|
|
3
|
+
Reconstructs the ordered sequence of a run — start, browser launch, navigation,
|
|
4
|
+
requests and responses, console errors, assertions, failure, cleanup — from the
|
|
5
|
+
execution result and the analysis findings. It records only stages for which
|
|
6
|
+
there is evidence; it never invents an event to fill the shape. Reusable by
|
|
7
|
+
qa-debug and qa-report.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Canonical phase order, used to sort events that share (or lack) a timestamp.
|
|
11
|
+
PHASE_ORDER = [
|
|
12
|
+
"execution-start", "browser-launch", "navigation", "request", "response",
|
|
13
|
+
"console-error", "assertion", "failure", "cleanup", "execution-finish",
|
|
14
|
+
]
|
|
15
|
+
_PHASE_INDEX = {phase: i for i, phase in enumerate(PHASE_ORDER)}
|
|
16
|
+
|
|
17
|
+
# Evidence type -> the timeline phase it contributes.
|
|
18
|
+
_EVIDENCE_PHASE = {
|
|
19
|
+
"network": "response",
|
|
20
|
+
"console": "console-error",
|
|
21
|
+
"trace": "navigation",
|
|
22
|
+
"junit": "assertion",
|
|
23
|
+
"report": "assertion",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_timeline(execution_result, findings=None):
|
|
28
|
+
"""Build an ordered timeline. Returns a list of event dicts:
|
|
29
|
+
{order, phase, detail, source, timestamp?}. Deterministic: same inputs,
|
|
30
|
+
same timeline. Only evidenced stages appear."""
|
|
31
|
+
events = []
|
|
32
|
+
execution = (execution_result or {}).get("execution", {})
|
|
33
|
+
|
|
34
|
+
started = execution.get("startedAt")
|
|
35
|
+
if started or execution_result:
|
|
36
|
+
events.append(_event("execution-start", "Run started", "execution-result", started))
|
|
37
|
+
|
|
38
|
+
# Contribute a stage per evidence entry we actually have.
|
|
39
|
+
for finding in findings or []:
|
|
40
|
+
for ev in finding.get("evidence", []):
|
|
41
|
+
phase = _EVIDENCE_PHASE.get(ev.get("type"))
|
|
42
|
+
if phase:
|
|
43
|
+
events.append(_event(phase, ev.get("description", ""), ev.get("source", "analysis"), None))
|
|
44
|
+
|
|
45
|
+
# A failure stage per failed test recorded in the result.
|
|
46
|
+
for test in (execution_result or {}).get("executed", []):
|
|
47
|
+
if test.get("status") == "failed":
|
|
48
|
+
events.append(_event("failure", f"Test failed: {test.get('title', '')}",
|
|
49
|
+
test.get("file", "execution-result"), None))
|
|
50
|
+
|
|
51
|
+
finished = execution.get("finishedAt")
|
|
52
|
+
if finished:
|
|
53
|
+
events.append(_event("cleanup", "Run cleaned up", "execution-result", None))
|
|
54
|
+
events.append(_event("execution-finish", "Run finished", "execution-result", finished))
|
|
55
|
+
|
|
56
|
+
# Deterministic order: by canonical phase first (it encodes the logical
|
|
57
|
+
# sequence), then by timestamp within a phase, then by insertion order. Phase
|
|
58
|
+
# order is primary because per-event wall-clock times are often unavailable.
|
|
59
|
+
for i, event in enumerate(events):
|
|
60
|
+
event["_seq"] = i
|
|
61
|
+
events.sort(key=lambda e: (_PHASE_INDEX.get(e["phase"], 99), e["timestamp"] or "", e["_seq"]))
|
|
62
|
+
for order, event in enumerate(events):
|
|
63
|
+
event["order"] = order
|
|
64
|
+
del event["_seq"]
|
|
65
|
+
if event["timestamp"] is None:
|
|
66
|
+
del event["timestamp"]
|
|
67
|
+
return events
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _event(phase, detail, source, timestamp):
|
|
71
|
+
return {"phase": phase, "detail": detail, "source": source, "timestamp": timestamp}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:qa-pack:internal:analysis-result:1",
|
|
4
|
+
"title": "Internal analysis_result consumed by diagnostics",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["findings"],
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"findings": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"items": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"required": ["classification", "reason"],
|
|
14
|
+
"additionalProperties": true,
|
|
15
|
+
"properties": {
|
|
16
|
+
"classification": { "type": "string" },
|
|
17
|
+
"reason": { "type": "string" },
|
|
18
|
+
"confidence": { "type": "number" },
|
|
19
|
+
"httpStatus": { "type": ["integer", "null"] },
|
|
20
|
+
"retries": { "type": "integer" },
|
|
21
|
+
"finalStatus": { "type": "string" },
|
|
22
|
+
"evidence": { "type": "array" },
|
|
23
|
+
"affectedTests": { "type": "array", "items": { "type": "string" } }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:qa-pack:internal:diagnosis:1",
|
|
4
|
+
"title": "Internal diagnosis (Analysis → Diagnostics output)",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["entries", "timeline", "recommendations"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"entries": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"items": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"required": ["rootCause", "priority", "affectedTests"],
|
|
14
|
+
"additionalProperties": true,
|
|
15
|
+
"properties": {
|
|
16
|
+
"rootCause": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"required": ["classification", "confidence"],
|
|
19
|
+
"properties": {
|
|
20
|
+
"classification": { "type": "string", "minLength": 1 },
|
|
21
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
22
|
+
"reason": { "type": "string" }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"priority": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"required": ["priority"],
|
|
28
|
+
"properties": {
|
|
29
|
+
"priority": { "type": "string", "enum": ["P1", "P2", "P3"] }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"affectedTests": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": { "type": "string" }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"timeline": { "type": "array" },
|
|
40
|
+
"recommendations": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["action", "priority"],
|
|
45
|
+
"additionalProperties": true,
|
|
46
|
+
"properties": {
|
|
47
|
+
"action": { "type": "string" },
|
|
48
|
+
"priority": { "type": "string" },
|
|
49
|
+
"owner": { "type": "string" },
|
|
50
|
+
"classification": { "type": "string" }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:qa-pack:internal:execution-result-min:1",
|
|
4
|
+
"title": "Minimal execution_result fields diagnostics and evals require",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": true,
|
|
7
|
+
"properties": {
|
|
8
|
+
"tests": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": true,
|
|
11
|
+
"properties": {
|
|
12
|
+
"passed": { "type": "integer" },
|
|
13
|
+
"failed": { "type": "integer" },
|
|
14
|
+
"flaky": { "type": "integer" },
|
|
15
|
+
"skipped": { "type": "integer" }
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"executed": {
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"required": ["status"],
|
|
23
|
+
"additionalProperties": true,
|
|
24
|
+
"properties": {
|
|
25
|
+
"title": { "type": "string" },
|
|
26
|
+
"status": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["passed", "failed", "flaky", "skipped", "timedOut"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Cypress analysis adapter.
|
|
2
|
+
|
|
3
|
+
Cypress emits JUnit XML (via a JUnit reporter), so Cypress normalization is the
|
|
4
|
+
framework-agnostic JUnit parser — the adapter only points at the artifact and
|
|
5
|
+
tags provenance. Like the Selenium adapter, its thinness is the proof of the
|
|
6
|
+
multi-framework boundary: the contract and parsing are shared; only the artifact
|
|
7
|
+
location differs.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from qa_analysis import junit, taxonomy
|
|
11
|
+
|
|
12
|
+
FRAMEWORK = "cypress"
|
|
13
|
+
|
|
14
|
+
# Where Cypress projects conventionally write JUnit results (see cypress-artifacts.md).
|
|
15
|
+
RESULT_GLOBS = ["**/results/*.xml", "**/cypress/results/*.xml", "**/junit*.xml"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def normalize(junit_path):
|
|
19
|
+
"""Normalize a Cypress run into the shared result shape via the agnostic
|
|
20
|
+
JUnit parser. Identical output shape to any other framework."""
|
|
21
|
+
return junit.parse_junit(junit_path)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def classify_failure(message, http_status=None):
|
|
25
|
+
"""Classify a Cypress failure using the shared taxonomy — no Cypress-specific
|
|
26
|
+
classifier, because failure classes are framework-agnostic."""
|
|
27
|
+
return taxonomy.classify(message, http_status=http_status)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Playwright-specific analyzers: trace and JSON report.
|
|
2
|
+
|
|
3
|
+
These are the Playwright *adapter* for the analysis platform. They depend on
|
|
4
|
+
the framework-agnostic qa_analysis core for the evidence model, redaction, and
|
|
5
|
+
the failure taxonomy; they add only what is Playwright-specific — the shape of
|
|
6
|
+
a Playwright trace.zip and of Playwright's JSON reporter. Everything they emit
|
|
7
|
+
is in the shared normalized shape, so nothing downstream knows it was Playwright.
|
|
8
|
+
|
|
9
|
+
The adapter carries its own CLI (`python -m playwright_analysis report|trace
|
|
10
|
+
<path>`) so a skill can reach it the same way it reaches every other
|
|
11
|
+
deterministic tool. Framework knowledge stays inside the adapter: the core CLI
|
|
12
|
+
never grows a `--framework` flag ([ADR-0013](../../../../docs/architecture/ADR-0013-framework-boundary.md)).
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python -m playwright_analysis report <results.json>
|
|
16
|
+
python -m playwright_analysis trace <trace.zip>
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
import zipfile
|
|
23
|
+
|
|
24
|
+
from qa_analysis.junit import MalformedArtifact
|
|
25
|
+
from qa_analysis.redaction import redact_text
|
|
26
|
+
from qa_analysis import taxonomy
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_report(path):
|
|
30
|
+
"""Parse Playwright's JSON reporter output into the normalized result shape.
|
|
31
|
+
|
|
32
|
+
Mirrors qa_analysis.junit's output ({tests, executed}) so a Playwright run
|
|
33
|
+
normalizes identically whether it emitted JSON or JUnit. Raises
|
|
34
|
+
MalformedArtifact on unreadable input.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
with open(path, "r", encoding="utf-8") as handle:
|
|
38
|
+
data = json.load(handle)
|
|
39
|
+
except (json.JSONDecodeError, OSError, UnicodeDecodeError) as exc:
|
|
40
|
+
raise MalformedArtifact(f"could not parse Playwright report at {path}: {exc}") from exc
|
|
41
|
+
|
|
42
|
+
executed = []
|
|
43
|
+
for suite in data.get("suites", []):
|
|
44
|
+
_walk_suite(suite, executed)
|
|
45
|
+
|
|
46
|
+
counts = {
|
|
47
|
+
"total": len(executed),
|
|
48
|
+
"passed": sum(1 for e in executed if e["status"] == "passed"),
|
|
49
|
+
"failed": sum(1 for e in executed if e["status"] == "failed"),
|
|
50
|
+
"skipped": sum(1 for e in executed if e["status"] == "skipped"),
|
|
51
|
+
}
|
|
52
|
+
return {"tests": counts, "executed": executed}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _walk_suite(suite, executed):
|
|
56
|
+
for spec in suite.get("specs", []):
|
|
57
|
+
for test in spec.get("tests", []):
|
|
58
|
+
results = test.get("results", [])
|
|
59
|
+
status = _status_of(results)
|
|
60
|
+
message = ""
|
|
61
|
+
if status == "failed" and results:
|
|
62
|
+
errors = results[-1].get("errors") or []
|
|
63
|
+
if errors:
|
|
64
|
+
message = redact_text((errors[0].get("message") or "").strip())
|
|
65
|
+
entry = {
|
|
66
|
+
"title": spec.get("title", ""),
|
|
67
|
+
"file": suite.get("file", ""),
|
|
68
|
+
"status": status,
|
|
69
|
+
"durationMs": results[-1].get("duration", 0) if results else 0,
|
|
70
|
+
"retries": max(0, len(results) - 1),
|
|
71
|
+
}
|
|
72
|
+
if message:
|
|
73
|
+
entry["message"] = message
|
|
74
|
+
executed.append(entry)
|
|
75
|
+
for child in suite.get("suites", []):
|
|
76
|
+
_walk_suite(child, executed)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _status_of(results):
|
|
80
|
+
if not results:
|
|
81
|
+
return "skipped"
|
|
82
|
+
final = results[-1].get("status")
|
|
83
|
+
if final in ("passed", "expected"):
|
|
84
|
+
return "flaky" if len(results) > 1 else "passed"
|
|
85
|
+
if final in ("skipped",):
|
|
86
|
+
return "skipped"
|
|
87
|
+
return "failed"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def analyze_trace(path):
|
|
91
|
+
"""Extract a deterministic summary from a Playwright trace.zip.
|
|
92
|
+
|
|
93
|
+
A trace is a zip of newline-delimited JSON event files. This lists the
|
|
94
|
+
actions, surfaces the last error, and counts console and network events —
|
|
95
|
+
enough for a diagnostic skill to reason over, without decoding the full
|
|
96
|
+
binary. Raises MalformedArtifact if the file is not a valid trace zip.
|
|
97
|
+
"""
|
|
98
|
+
if not zipfile.is_zipfile(path):
|
|
99
|
+
raise MalformedArtifact(f"not a valid trace zip: {path}")
|
|
100
|
+
|
|
101
|
+
actions, console, network, errors = [], 0, 0, []
|
|
102
|
+
try:
|
|
103
|
+
with zipfile.ZipFile(path) as archive:
|
|
104
|
+
names = [n for n in archive.namelist() if n.endswith(".trace") or n.endswith(".jsonl") or "trace" in n]
|
|
105
|
+
for name in names or archive.namelist():
|
|
106
|
+
with archive.open(name) as member:
|
|
107
|
+
for raw in member.read().decode("utf-8", "replace").splitlines():
|
|
108
|
+
raw = raw.strip()
|
|
109
|
+
if not raw or not raw.startswith("{"):
|
|
110
|
+
continue
|
|
111
|
+
try:
|
|
112
|
+
event = json.loads(raw)
|
|
113
|
+
except json.JSONDecodeError:
|
|
114
|
+
continue
|
|
115
|
+
kind = event.get("type") or event.get("kind")
|
|
116
|
+
if kind in ("action", "before"):
|
|
117
|
+
actions.append(redact_text(str(event.get("apiName") or event.get("method") or "")))
|
|
118
|
+
elif kind == "console":
|
|
119
|
+
console += 1
|
|
120
|
+
elif kind in ("resource", "network", "http"):
|
|
121
|
+
network += 1
|
|
122
|
+
if event.get("error") or kind == "error":
|
|
123
|
+
msg = event.get("error", {}).get("message") if isinstance(event.get("error"), dict) else event.get("message")
|
|
124
|
+
if msg:
|
|
125
|
+
errors.append(redact_text(str(msg)))
|
|
126
|
+
except (OSError, KeyError) as exc:
|
|
127
|
+
raise MalformedArtifact(f"could not read trace {path}: {exc}") from exc
|
|
128
|
+
|
|
129
|
+
last_error = errors[-1] if errors else ""
|
|
130
|
+
classification, confidence, reason = taxonomy.classify(last_error) if last_error else (taxonomy.UNKNOWN, 0.2, "No error found in trace.")
|
|
131
|
+
return {
|
|
132
|
+
"actions": [a for a in actions if a],
|
|
133
|
+
"consoleEvents": console,
|
|
134
|
+
"networkEvents": network,
|
|
135
|
+
"errors": errors,
|
|
136
|
+
"classification": classification,
|
|
137
|
+
"confidence": confidence,
|
|
138
|
+
"reason": reason,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main(argv=None):
|
|
143
|
+
"""CLI: JSON to stdout, exit 0 on success, 2 on a malformed artifact."""
|
|
144
|
+
parser = argparse.ArgumentParser(
|
|
145
|
+
prog="playwright_analysis",
|
|
146
|
+
description="Playwright adapter: normalize a JSON report or summarize a trace",
|
|
147
|
+
)
|
|
148
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
149
|
+
p = sub.add_parser("report", help="normalize Playwright's JSON reporter output")
|
|
150
|
+
p.add_argument("path")
|
|
151
|
+
p = sub.add_parser("trace", help="summarize a Playwright trace.zip")
|
|
152
|
+
p.add_argument("path")
|
|
153
|
+
args = parser.parse_args(argv)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
result = parse_report(args.path) if args.command == "report" else analyze_trace(args.path)
|
|
157
|
+
except MalformedArtifact as exc:
|
|
158
|
+
json.dump({"error": "malformed-artifact", "detail": str(exc)}, sys.stdout, indent=2, sort_keys=True)
|
|
159
|
+
sys.stdout.write("\n")
|
|
160
|
+
return 2
|
|
161
|
+
json.dump(result, sys.stdout, indent=2, sort_keys=True)
|
|
162
|
+
sys.stdout.write("\n")
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
sys.exit(main())
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./registry.schema.json",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"frameworks": [
|
|
5
|
+
{
|
|
6
|
+
"id": "playwright",
|
|
7
|
+
"displayName": "Playwright",
|
|
8
|
+
"supportLevel": "Production",
|
|
9
|
+
"adapterDir": "shared/frameworks/playwright",
|
|
10
|
+
"liveExecution": true,
|
|
11
|
+
"liveGeneration": true,
|
|
12
|
+
"detection": {
|
|
13
|
+
"configFiles": ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"],
|
|
14
|
+
"configPrefixes": ["playwright.config."],
|
|
15
|
+
"dependencies": ["@playwright/test", "playwright"],
|
|
16
|
+
"directories": []
|
|
17
|
+
},
|
|
18
|
+
"capabilities": {
|
|
19
|
+
"detection": "Full",
|
|
20
|
+
"execution": "Full",
|
|
21
|
+
"generation": "Full",
|
|
22
|
+
"artifactMapping": "Full",
|
|
23
|
+
"normalization": "Full",
|
|
24
|
+
"analysis": "Full",
|
|
25
|
+
"diagnostics": "Full",
|
|
26
|
+
"reporting": "Full"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "selenium",
|
|
31
|
+
"displayName": "Selenium",
|
|
32
|
+
"supportLevel": "Beta",
|
|
33
|
+
"adapterDir": "shared/frameworks/selenium",
|
|
34
|
+
"liveExecution": false,
|
|
35
|
+
"liveGeneration": false,
|
|
36
|
+
"detection": {
|
|
37
|
+
"configFiles": [],
|
|
38
|
+
"configPrefixes": [],
|
|
39
|
+
"dependencies": ["selenium-webdriver", "selenium"],
|
|
40
|
+
"directories": []
|
|
41
|
+
},
|
|
42
|
+
"capabilities": {
|
|
43
|
+
"detection": "Full",
|
|
44
|
+
"execution": "Adapter-complete",
|
|
45
|
+
"generation": "Adapter-complete",
|
|
46
|
+
"artifactMapping": "Full",
|
|
47
|
+
"normalization": "Full",
|
|
48
|
+
"analysis": "Full",
|
|
49
|
+
"diagnostics": "Full",
|
|
50
|
+
"reporting": "Full"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "cypress",
|
|
55
|
+
"displayName": "Cypress",
|
|
56
|
+
"supportLevel": "Beta",
|
|
57
|
+
"adapterDir": "shared/frameworks/cypress",
|
|
58
|
+
"liveExecution": false,
|
|
59
|
+
"liveGeneration": false,
|
|
60
|
+
"detection": {
|
|
61
|
+
"configFiles": ["cypress.config.js", "cypress.config.ts", "cypress.config.mjs"],
|
|
62
|
+
"configPrefixes": [],
|
|
63
|
+
"dependencies": ["cypress"],
|
|
64
|
+
"directories": ["cypress"]
|
|
65
|
+
},
|
|
66
|
+
"capabilities": {
|
|
67
|
+
"detection": "Full",
|
|
68
|
+
"execution": "Adapter-complete",
|
|
69
|
+
"generation": "Adapter-complete",
|
|
70
|
+
"artifactMapping": "Full",
|
|
71
|
+
"normalization": "Full",
|
|
72
|
+
"analysis": "Full",
|
|
73
|
+
"diagnostics": "Full",
|
|
74
|
+
"reporting": "Full"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "webdriverio",
|
|
79
|
+
"displayName": "WebdriverIO",
|
|
80
|
+
"supportLevel": "Beta",
|
|
81
|
+
"adapterDir": "shared/frameworks/webdriverio",
|
|
82
|
+
"liveExecution": false,
|
|
83
|
+
"liveGeneration": false,
|
|
84
|
+
"detection": {
|
|
85
|
+
"configFiles": ["wdio.conf.js", "wdio.conf.ts", "wdio.conf.mjs"],
|
|
86
|
+
"configPrefixes": [],
|
|
87
|
+
"dependencies": ["webdriverio", "@wdio/cli"],
|
|
88
|
+
"directories": []
|
|
89
|
+
},
|
|
90
|
+
"capabilities": {
|
|
91
|
+
"detection": "Full",
|
|
92
|
+
"execution": "Adapter-complete",
|
|
93
|
+
"generation": "Adapter-complete",
|
|
94
|
+
"artifactMapping": "Full",
|
|
95
|
+
"normalization": "Full",
|
|
96
|
+
"analysis": "Full",
|
|
97
|
+
"diagnostics": "Full",
|
|
98
|
+
"reporting": "Full"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "robot",
|
|
103
|
+
"displayName": "Robot Framework",
|
|
104
|
+
"supportLevel": "Planning",
|
|
105
|
+
"adapterDir": null,
|
|
106
|
+
"liveExecution": false,
|
|
107
|
+
"liveGeneration": false,
|
|
108
|
+
"detection": {
|
|
109
|
+
"configFiles": [],
|
|
110
|
+
"configPrefixes": [],
|
|
111
|
+
"dependencies": [],
|
|
112
|
+
"directories": []
|
|
113
|
+
},
|
|
114
|
+
"capabilities": {
|
|
115
|
+
"detection": "Planning",
|
|
116
|
+
"execution": "—",
|
|
117
|
+
"generation": "—",
|
|
118
|
+
"artifactMapping": "—",
|
|
119
|
+
"normalization": "—",
|
|
120
|
+
"analysis": "—",
|
|
121
|
+
"diagnostics": "—",
|
|
122
|
+
"reporting": "—"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"id": "appium",
|
|
127
|
+
"displayName": "Appium",
|
|
128
|
+
"supportLevel": "Planning",
|
|
129
|
+
"adapterDir": null,
|
|
130
|
+
"liveExecution": false,
|
|
131
|
+
"liveGeneration": false,
|
|
132
|
+
"detection": {
|
|
133
|
+
"configFiles": [],
|
|
134
|
+
"configPrefixes": [],
|
|
135
|
+
"dependencies": [],
|
|
136
|
+
"directories": []
|
|
137
|
+
},
|
|
138
|
+
"capabilities": {
|
|
139
|
+
"detection": "Planning",
|
|
140
|
+
"execution": "Planning",
|
|
141
|
+
"generation": "—",
|
|
142
|
+
"artifactMapping": "—",
|
|
143
|
+
"normalization": "—",
|
|
144
|
+
"analysis": "—",
|
|
145
|
+
"diagnostics": "—",
|
|
146
|
+
"reporting": "—"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Load the canonical framework registry. All detection and matrix checks
|
|
2
|
+
// derive from shared/frameworks/registry.json — do not duplicate framework lists.
|
|
3
|
+
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
let cached = null;
|
|
11
|
+
|
|
12
|
+
export function registryPath() {
|
|
13
|
+
return path.join(here, 'registry.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function loadFrameworkRegistry() {
|
|
17
|
+
if (cached) return cached;
|
|
18
|
+
const raw = JSON.parse(fs.readFileSync(registryPath(), 'utf8'));
|
|
19
|
+
cached = raw;
|
|
20
|
+
return raw;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function listFrameworks() {
|
|
24
|
+
return loadFrameworkRegistry().frameworks;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getFramework(id) {
|
|
28
|
+
return listFrameworks().find((f) => f.id === id) ?? null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function liveExecutionFrameworks() {
|
|
32
|
+
return listFrameworks().filter((f) => f.liveExecution).map((f) => f.id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function supportedFrameworks() {
|
|
36
|
+
return listFrameworks().filter((f) => f.supportLevel !== 'Planning');
|
|
37
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "urn:qa-pack:framework-registry:1",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["version", "frameworks"],
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"version": { "type": "integer", "minimum": 1 },
|
|
9
|
+
"frameworks": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"minItems": 1,
|
|
12
|
+
"items": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"required": [
|
|
15
|
+
"id",
|
|
16
|
+
"displayName",
|
|
17
|
+
"supportLevel",
|
|
18
|
+
"adapterDir",
|
|
19
|
+
"liveExecution",
|
|
20
|
+
"liveGeneration",
|
|
21
|
+
"detection",
|
|
22
|
+
"capabilities"
|
|
23
|
+
],
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
27
|
+
"displayName": { "type": "string", "minLength": 1 },
|
|
28
|
+
"supportLevel": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": ["Production", "Beta", "Experimental", "Planning"]
|
|
31
|
+
},
|
|
32
|
+
"adapterDir": { "type": ["string", "null"] },
|
|
33
|
+
"liveExecution": { "type": "boolean" },
|
|
34
|
+
"liveGeneration": { "type": "boolean" },
|
|
35
|
+
"detection": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"required": ["configFiles", "configPrefixes", "dependencies", "directories"],
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"properties": {
|
|
40
|
+
"configFiles": { "type": "array", "items": { "type": "string" } },
|
|
41
|
+
"configPrefixes": { "type": "array", "items": { "type": "string" } },
|
|
42
|
+
"dependencies": { "type": "array", "items": { "type": "string" } },
|
|
43
|
+
"directories": { "type": "array", "items": { "type": "string" } }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"capabilities": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"required": [
|
|
49
|
+
"detection",
|
|
50
|
+
"execution",
|
|
51
|
+
"generation",
|
|
52
|
+
"artifactMapping",
|
|
53
|
+
"normalization",
|
|
54
|
+
"analysis",
|
|
55
|
+
"diagnostics",
|
|
56
|
+
"reporting"
|
|
57
|
+
],
|
|
58
|
+
"additionalProperties": false,
|
|
59
|
+
"properties": {
|
|
60
|
+
"detection": { "type": "string" },
|
|
61
|
+
"execution": { "type": "string" },
|
|
62
|
+
"generation": { "type": "string" },
|
|
63
|
+
"artifactMapping": { "type": "string" },
|
|
64
|
+
"normalization": { "type": "string" },
|
|
65
|
+
"analysis": { "type": "string" },
|
|
66
|
+
"diagnostics": { "type": "string" },
|
|
67
|
+
"reporting": { "type": "string" }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Selenium analysis adapter.
|
|
2
|
+
|
|
3
|
+
Selenium's runners (JUnit, TestNG, pytest) emit JUnit XML, so Selenium
|
|
4
|
+
normalization *is* the framework-agnostic JUnit parser — the adapter only points
|
|
5
|
+
at the right artifact and tags provenance. This is the proof of the pack's
|
|
6
|
+
multi-framework claim: the second framework's analysis is a few lines, because
|
|
7
|
+
the contract and the parsing are shared, and only the artifact location differs.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from qa_analysis import junit, taxonomy
|
|
11
|
+
|
|
12
|
+
FRAMEWORK = "selenium"
|
|
13
|
+
|
|
14
|
+
# Where Selenium projects conventionally write results (documented in
|
|
15
|
+
# selenium-artifacts.md); discovery uses these, the parser is shared.
|
|
16
|
+
RESULT_GLOBS = ["**/target/surefire-reports/*.xml", "**/test-results/*.xml", "**/junit*.xml"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def normalize(junit_path):
|
|
20
|
+
"""Normalize a Selenium run into the shared result shape via the agnostic
|
|
21
|
+
JUnit parser. Identical output shape to any other framework's normalization."""
|
|
22
|
+
return junit.parse_junit(junit_path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def classify_failure(message, http_status=None):
|
|
26
|
+
"""Classify a Selenium failure using the shared taxonomy — no Selenium-specific
|
|
27
|
+
classifier, because failure classes are framework-agnostic."""
|
|
28
|
+
return taxonomy.classify(message, http_status=http_status)
|