UNPKG

creevey

Version:

Cross-browser screenshot testing tool for Storybook with fancy UI Runner

174 lines (119 loc) 6.25 kB
# JUnit Reporter Improvements — Design Spec **Date:** 2026-05-12 **Status:** ✅ COMPLETED (2026-05-12) — All design requirements implemented. HEAD: bebbaac2 **Scope:** Broad CI/CD compatibility (Jenkins, TeamCity, GitHub Actions, Allure, TestMo, etc.) **Target file:** `src/server/reporters/junit.ts` --- ## Problem The current `JUnitReporter` produces minimal XML that fails to integrate properly with CI tools: 1. Same story running in multiple browsers merges into one `<testsuite>`, producing incorrect per-browser failure counts. 2. `<failure>` has no text body — stack traces and image diff errors are silently dropped. 3. All bad outcomes are counted as `failures`; the spec distinguishes `errors` (crashes) from `failures` (assertion mismatches). 4. Screenshot attachments (`test.attachments`) are never written to the XML. 5. `hostname` attribute is missing from `<testsuite>` (required by Jenkins JUnit plugin schema). 6. Sequential `id` attribute is missing from `<testsuite>`. 7. Empty `<failure/>` tags are written when `test.err` is `undefined`. --- ## Design ### 1. Suite Keying (multi-browser fix) **Current:** `suites` and `suiteStartTimes` are both keyed by `test.parent.title` (story name). **New:** Key by `"${suiteName}/${browserName}"` internally. When writing XML, use only the story name as the `<testsuite name>` attribute, so CI tools still group by story. Each browser produces its own `<testsuite>` block. ``` Internal key: "Button/primary/chrome" XML name attr: "Button/primary" ``` This also fixes `suiteStartTimes` which has the same collision. Each `<testsuite>` additionally writes a `<properties>` block with `<property name="browser" value="chrome"/>` so CI tools can distinguish browsers within the same story group. --- ### 2. Failure Body & Error/Failure Distinction **Detection logic:** - `test.creevey.images` has entries → image diff mismatch → write `<failure>` - `test.err` is set but no images → unexpected crash → write `<error>` **`<failure>` element:** ```xml <failure message="Images do not match"> header: expected and actual images differ body: expected and actual images differ </failure> ``` - `message` attribute: `"Images do not match"` - Text body: one line per entry in `test.creevey.images`, formatted as `"${imageName}: ${image.error}"` (image names are screenshot step names, e.g. `"header"`, `"body"` for multi-step tests) **`<error>` element:** ```xml <error message="TypeError: Cannot read properties of null" type="Error"> TypeError: Cannot read properties of null at Object.&lt;anonymous&gt; (test.ts:12:5) </error> ``` - `message` attribute: first line of `test.err` - `type` attribute: `"Error"` - Text body: full `test.err` string (XML-escaped) **Counts:** `<testsuite>` and `<testsuites>` emit both `failures` and `errors` attributes, derived from the above distinction. **Guard:** Only write `<failure>` or `<error>` when there is actual content. If `test.state === 'failed'` but both `test.creevey.images` is empty and `test.err` is undefined (defensive edge case), fall back to `<failure message="Test failed"/>`. No empty self-closing tags in the normal path. --- ### 3. Screenshot Attachments For every `<testcase>` with `test.attachments` entries, write a `<properties>` block with one `<property name="attachment"/>` per file: ```xml <testcase name="primary" classname="Button/primary" time="1.234"> <failure message="Images do not match">chrome: ...</failure> <properties> <property name="attachment" value="Button/primary/chrome/button-actual-1.png"/> <property name="attachment" value="Button/primary/chrome/button-expect-1.png"/> <property name="attachment" value="Button/primary/chrome/button-diff-1.png"/> </properties> </testcase> ``` - Paths are **relative to the XML file** (`path.relative(reportDir, absolutePath)`) - `reportDir` is already available via constructor `options.reportDir` - Passing tests also get their `actual` screenshot attached (for visual audit in CI) - If `test.attachments` is empty or undefined, no `<properties>` block is written --- ### 4. Spec Attribute Additions **`hostname` on `<testsuite>`** ```xml <testsuite name="Button/primary" hostname="build-agent-01" ...> ``` - Value: `os.hostname()` from Node's built-in `os` module - Required by Jenkins JUnit plugin schema; useful for distributed CI runs **Sequential `id` on `<testsuite>`** ```xml <testsuite name="Button/primary" id="0" ...> <testsuite name="Button/hover" id="1" ...> ``` - Zero-indexed, derived from suite order in `onFinished()` --- ### 5. `writeElement` extension for text content The current `writeElement` only supports attributes and child elements. Failure/error text bodies require writing escaped text content between tags. Add an optional `textContent` parameter (or a dedicated `writeTextElement` helper) to `writeElement`: ```ts private writeElement( name: string, attrs: Record<string, string | number | undefined>, children?: () => void, textContent?: string, ): void ``` When `textContent` is provided and no `children` callback is given, write the text between the open and close tags (XML-escaped). --- ## Data Flow ``` runner.ts emits TEST_FAIL/TEST_PASS with FakeTest │ ├── FakeTest.creevey.images → <failure> text body (image errors) ├── FakeTest.err → <error> element (crash errors) ├── FakeTest.attachments → <properties name="attachment"> (relative paths) ├── FakeTest.parent.title → suite name (XML), part of internal key └── FakeTest.creevey.browserName → part of internal key + <property name="browser"> ``` --- ## What Is Not Changing - `classnameTemplate` — deferred (no concrete use case yet) - `<system-out>` / `<system-err>` — no console log capture in Creevey's test model - Self-closing elements for passing tests — current approach is correct per spec - `writeElement` indentation logic — unchanged --- ## Files Changed | File | Change | | ------------------------------- | ----------------- | | `src/server/reporters/junit.ts` | All changes above | No changes to public API, config schema, or other reporters.