bug-report-from-failure
On-demand builder that converts a SINGLE test failure record (JUnit XML, Allure JSON, pytest --tb=short, Playwright HTML, Cypress mocha-junit) into a structured, tracker-agnostic bug SPEC: extracts test name, assertion, stack trace, environment, and artefacts, and proposes severity, defect type (IEEE 1044), and a root-cause hypothesis (ISTQB CTAL-TA), then hands the JSON spec to a jira/linear/github-issues-bug-workflow runner to file. Use when you already hold a failure artefact and want one classified, ready-to-file report. Distinct from the event-driven CI orchestrator that triggers automatically on a pipeline failure and files in bulk, and from screen-recording-driven bug reporting; this is the on-demand, single-record spec builder.
Install with skills.sh (any agent)
npx skills add testland/qa --skill bug-report-from-failurebug-report-from-failure
Overview
A test failure produces structured data (XML, JSON, HTML) with everything a triager needs - assertion, stack, test name, environment. This workflow ingests that record and emits a ready-to-file bug spec.
It composes:
Distinct from screen-recording-driven bug reporting: this one starts from a CI failure record.
When to use
Step 1 - Ingest the failure record
The skill accepts these inputs (auto-detected by extension):
| Format | Source | Schema reference |
|---|---|---|
| JUnit XML | pytest, JUnit, surefire, Playwright | <testsuites>/<testsuite>/<testcase>/<failure> per the de-facto schema (Apache Ant) at llg.cubic.org/docs/junit/ (opens in new window) |
| Allure JSON | Allure framework (any language) | per-test JSON in allure-results/; schema at docs.qameta.io/allure-report (opens in new window) |
pytest --tb=short log | pytest stdout/stderr | line-oriented; regex-driven |
| Playwright HTML report | Playwright trace | report.json inside the HTML bundle |
| TestNG XML | TestNG | similar to JUnit; per testng.org (opens in new window) |
Parser bodies and per-format field shapes (JUnit, Allure, pytest, Playwright, TestNG) live in references/parsers.md. parse_junit(path) returns one dict per failing testcase; Allure's first-class severity / feature / suite labels are harvested when present.
Step 2 - Extract classification fields
For each failure, propose values for the bug report:
| Field | Source | Default if unknown |
|---|---|---|
| Title | First line of failure.message truncated to 100 chars | "Test failure: {test_name}" |
| Body | Markdown with test name, stack, env, links | (always present) |
| Severity | Allure severity label OR inferred from assertion class (AssertionError → Medium; TimeoutError → High; ConnectionError → High) | Medium |
| Priority | Match severity by default; production-runner = bump | Medium |
| Defect type (IEEE 1044) | Inferred from stack location: tests/* → Test specification; app/* → Code (implementation) | Code |
| Component | Allure feature / suite label OR top-of-stack module | (none) |
Severity inference rules (heuristic, reviewer confirms):
SEVERITY_FROM_ERROR = {
"AssertionError": "medium",
"TimeoutError": "high",
"ConnectionError": "high",
"OutOfMemoryError": "critical",
"SecurityException": "critical",
}
def infer_severity(failure_type, message):
if failure_type in SEVERITY_FROM_ERROR:
return SEVERITY_FROM_ERROR[failure_type]
if "production" in message.lower() or "p0" in message.lower():
return "high"
return "medium"Step 3 - Render the Markdown body
render_body(failure, env) emits a standard Markdown block consumed verbatim by every platform runner - test name, assertion, stack, environment, artefact links, a proposed-classification table (flagged "triager to confirm"), reproduction steps, and dupe history. Full template: references/spec-template.md.
Step 4 - Search for duplicates
Before filing, search the platform tracker for open bugs with matching title / test name. Use the per-platform skill:
# Pseudo
def find_dupes(platform, test_name):
if platform == "jira":
return jira_bug_workflow_runner.search_jql(
f'project = ENG AND text ~ "{test_name}" AND issuetype = Bug'
)
if platform == "linear":
return linear_bug_workflow_runner.find_dupes(TEAM_ID, test_name)
if platform == "github":
return github_issues_bug_workflow.search_issues(
f'is:open label:bug "{test_name}" in:title,body'
)If duplicates exist, the workflow attaches a comment instead of creating a new bug.
Step 5 - File the bug
Emit a tracker-agnostic spec:
bug_spec:
title: "Test failure: checkout fails with promo X"
body: |
## Test failure
...
severity: high
priority: p2
labels: [bug, type:regression, component:checkout]
defect_type: Code
component: checkout
reproduction:
commit: "abc123"
command: "pytest tests/checkout/test_promo.py::test_stacked"
environment:
branch: main
ci_run: "https://github.com/.../runs/123"Then pass to the relevant platform-runner. Sample dispatcher:
def file_bug(spec, platform):
if platform == "jira":
return jira_bug_workflow_runner.create_bug(
project_key="ENG",
summary=spec["title"],
description_text=spec["body"],
severity=spec["severity"].capitalize(),
priority=spec["priority"].upper(),
labels=spec["labels"],
)
if platform == "linear":
return linear_bug_workflow_runner.create_bug(
team_id=os.environ["LINEAR_TEAM_ID"],
title=spec["title"],
description_md=spec["body"],
priority=PRIORITY_MAP[spec["priority"]],
state_id=BACKLOG_STATE_ID,
label_ids=resolve_label_ids(spec["labels"]),
)
if platform == "github":
return github_issues_bug_workflow.create_bug(
title=spec["title"],
body=spec["body"],
severity=spec["severity"],
priority=spec["priority"],
labels=spec["labels"],
)Step 6 - Confirm and audit
After filing:
Worked example - pytest + JUnit → GitHub Issues
from pathlib import Path
failures = parse_junit(Path("results.xml"))
for f in failures:
spec = {
"title": f"Test failure: {f['test'].split('::')[-1]}",
"body": render_body(f, env=collect_env_vars()),
"severity": infer_severity(f["type"], f["message"]),
"priority": "p3", # default; reviewer adjusts
"labels": ["bug", "auto-filed", "ci-failure"],
"defect_type": "Code",
"component": guess_component(f["test"]),
}
if find_dupes("github", f["test"]):
# Comment on the existing issue
continue
issue = file_bug(spec, "github")
print(f"Filed #{issue['number']}: {issue['html_url']}")Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Hand-copying assertion to bug | Stack truncation, escaping errors | Always parse the structured artefact |
| One bug per test failure ignoring deduplication | Tracker fills with the same flake | Always run Step 4 (dedupe) |
| Inferring severity from test name | "test_critical_path_*" doesn't mean failure is critical | Infer from assertion class + message keywords |
| No reproduction section | Triager can't repro; bug bounces back | Always include Step 3's commit + command |
| File before deduplication | Same defect filed N times in N CI runs | Search first |
| No artefacts linked | Triager can't see what happened | Always link screenshots / videos / HAR |
| Inferred classification not flagged as a proposal | Triager assumes it's confirmed; bad data downstream | Always label classification fields as "proposed - triager confirms" |
Limitations
References
Per-format failure parsers
View source (opens in new window)Per-format failure parsers
Each input is auto-detected by extension. The spine's Step 1 table lists the formats and schema references; the parser bodies live here.
JUnit XML
Emitted by pytest, JUnit, surefire, Playwright. The schema is informal but stable (Apache Ant / xUnit family); the agreed-on tags are testsuites, testsuite, testcase, failure, error, skipped, system-out, system-err. The failure element's type attribute carries the assertion class (e.g. AssertionError), per llg.cubic.org/docs/junit/ (opens in new window).
import xml.etree.ElementTree as ET
def parse_junit(path):
tree = ET.parse(path)
failures = []
for testcase in tree.iter("testcase"):
f = testcase.find("failure") or testcase.find("error")
if f is None:
continue
failures.append({
"test": f"{testcase.get('classname')}::{testcase.get('name')}",
"duration_s": float(testcase.get("time", 0)),
"type": f.get("type") or "Failure",
"message": f.get("message") or "",
"stack": f.text or "",
"system_out": (testcase.findtext("system-out") or "").strip(),
})
return failuresAllure JSON
Allure stores per-test JSON files in allure-results/, per docs.qameta.io/allure-report (opens in new window). Its labels are first-class; harvest severity, feature, suite. Shape:
{
"uuid": "...",
"name": "test_checkout_with_promo",
"fullName": "tests.checkout.test_checkout_with_promo",
"status": "failed",
"statusDetails": {
"message": "AssertionError: expected $22.49, got $24.99",
"trace": "Traceback (most recent call last):\n File..."
},
"labels": [
{"name": "suite", "value": "checkout"},
{"name": "severity", "value": "critical"},
{"name": "feature", "value": "promo-codes"}
],
"attachments": [
{"name": "screenshot", "source": "abc123-attachment.png", "type": "image/png"}
]
}Other formats
Bug-spec render template
View source (opens in new window)Bug-spec render template
render_body(failure, env) produces this Markdown block, consumed verbatim by every platform runner. Placeholders in <...> are filled from the parsed failure and the CI environment variables.
## Test failure
**Test:** `<class>::<test>`
**Suite:** <suite>
**Duration:** <duration> s
**Environment:** <env from CI vars: branch, commit, OS, browser>
### Assertion
<failure.message>
### Stack trace
<failure.stack>
### Artefacts
- Screenshot: <link or attachment ref>
- Video: <link>
- HAR: <link>
- CI run: <link>
- Test source: <github permalink at commit sha>
### Classification (proposed - triager to confirm)
| Field | Value |
|---|---|
| Severity | <inferred> |
| Priority | <inferred> |
| Defect type (IEEE 1044) | <inferred> |
| Root cause (CTAL-TA) | (triager to assign) |
| Component | <inferred> |
| Suite | <inferred> |
### Reproduction
1. Check out `<commit>`
2. Run: `<command>`
3. Observe: <one-line description>
### History
<dupe-search result: any prior occurrences of this test failing in last N days>Related skills
azuredevops-bug-workflow
Authors and triages Bug work items in Azure DevOps Boards via the Work Item Tracking REST API (api-version 7.1) - Bug creation with JSON Patch, state transitions across New/Active/Resolved/Closed, and WIQL queries for triage queues and duplicate detection. Deep operational blocks (field-value fetch, PR / build artifact links, bulk close, az boards CLI, CI wiring) live in references/. Use when programmatically managing Azure DevOps Bug lifecycle states: creating from CI failures, triaging open defect queues, transitioning states in bulk, or attaching traceability links to builds and pull requests.
bug-lifecycle-reference
Pure-reference catalog of defect lifecycle states and transitions. Defines the ISTQB-canonical states (new / open / assigned / in-progress / fixed / verified / closed / reopened / deferred / rejected / duplicate) and the transitions between them, distinguishes the ISTQB terms (error → fault / defect → failure), maps the lifecycle to the standard Jira / Linear / GitHub Issues workflows, and cites IEEE 1044-2009 and ISO/IEC/IEEE 29119-3 for the canonical anchors. Use as the lifecycle vocabulary for bug-report review, duplicate detection, and the platform-workflow skills.
confirmation-testing-workflow
Procedure for proving that a claimed defect fix actually reached the build under test and actually works. Covers the merge-base ancestry check that proves the running build contains the fix commit rather than trusting a version label, the priority order for choosing which reproduction to re-run, and the VERIFIED / NOT FIXED / BLOCKED verdict table whose governing rule is that any ambiguous, flaky, or unreproducible result resolves to BLOCKED and is never guessed. Scoped to ISTQB confirmation testing (does this specific fix work?), not regression testing (did the fix break something else?), and not triage or severity assignment. Use when a developer has marked a defect Fixed and someone must decide whether it moves to Verified or back to Reopened.
defect-taxonomy-istqb
Pure-reference catalog of defect categorisation taxonomies. Covers the IEEE 1044-2009 anomaly classification (anomaly class, anomaly type, anomaly severity, root cause category), the ISTQB CTAL-TA root-cause taxonomy (requirements / design / implementation / interface / test-data / build-environment), and the Orthogonal Defect Classification (ODC) eight-attribute framework. Maps each taxonomy to a worked example showing how the same defect classifies under each. Use to categorise defects consistently across a team, drive root-cause analysis, and inform process-improvement decisions (where in the SDLC do we leak the most defects?).
github-issues-bug-workflow
Author and run GitHub Issues bug workflows via REST API (2026-03-10): issue creation, state changes (open / closed with `state_reason`), label-based severity/priority classification, and comment attachment. Covers `POST /repos/{owner}/{repo}/issues`, `PATCH` for `state_reason` transitions (completed / not_planned / duplicate / reopened), and label conventions for GitHub's binary open/closed model; Projects v2, `gh` CLI, and CI wiring live in references/. Use when programmatically managing the GitHub Issues bug lifecycle; for the same workflow on another tracker use azuredevops-bug-workflow, jira-bug-workflow-runner, or linear-bug-workflow-runner.
jira-bug-workflow-runner
Jira Cloud bug workflow runner using the REST API v3: issue creation with an ADF description, runtime transition lookup and apply, JQL search for triage queues and duplicate detection, severity/priority field updates, label-based classification (severity/priority/regression), and idempotent CI-driven filing from JUnit XML test failures. Use when the target tracker is Jira Cloud and the task involves Jira lifecycle states (create, triage, transition, close). Distinct from a platform-agnostic event-driven CI defect filer, and from linear-bug-workflow-runner / github-issues-bug-workflow for other trackers.
linear-bug-workflow-runner
Author and run Linear bug workflows via the GraphQL API: issue creation, state transitions (workflowState assignment), priority assignment (0 No priority / 1 Urgent / 2 High / 3 Medium / 4 Low), label-based classification, search by team and content. Covers the issueCreate mutation, issueUpdate for state transitions, the workflowStates query for per-team state IDs, and Linear's API-key vs OAuth Bearer auth modes; resolve-by-type, CI wiring, and result parsing live in references/. Use when the target tracker is Linear specifically; for other trackers use jira-bug-workflow-runner (Jira) or github-issues-bug-workflow (GitHub Issues). Files and transitions the issue; reproducing the defect is a separate concern.
severity-vs-priority-reference
Pure-reference catalog distinguishing defect severity (impact on the system / user) from defect priority (urgency of fix), each on its own axis. Enumerates the canonical 5-point severity scale (Critical / High / Medium / Low / Trivial) and the 5-point priority scale (Immediate / High / Medium / Low / Deferred), explains why they must be tracked separately (a Critical/Deferred legacy bug exists; a Trivial/Immediate spelling bug on the homepage exists), maps to IEEE 1044-2009 severity classes, and ties to bug-lifecycle-reference state transitions. Use when triaging a defect, configuring a tracker's severity/priority fields, or reviewing whether a bug report assigned them consistently.