@devant-net/junit-uploader
v0.1.5
Published
Generic JUnit XML uploader for devq-cloud — works with Vitest, Jest, Bun test, pytest, Go test, JUnit/Java, and anything else that emits standard JUnit XML.
Maintainers
Readme
@devant-net/junit-uploader
Generic JUnit XML uploader for Devant Cloud.
Works with any test runner that emits standard JUnit XML — Vitest, Jest,
Bun test, pytest, Go test, JUnit/Java, Mocha, Cucumber, Karma — anything
the JUnit-compatible ecosystem touches. Framework-specific reporters
(@devant-net/playwright-reporter, @devant-net/cypress-reporter,
@devant-net/maestro-reporter) handle richer per-step / per-attempt /
per-artifact data; this is the long-tail catch-all.
Install
bun add -D @devant-net/junit-uploader
# or
npm i -D @devant-net/junit-uploaderUse
After your test command emits a JUnit XML, pipe it to Devant Cloud:
# Vitest
vitest run --reporter=junit --outputFile=junit.xml
devq-junit-upload --junit junit.xml --framework vitest
# Bun test
bun test --reporter=junit --reporter-outfile=junit.xml
devq-junit-upload --junit junit.xml --framework bun-test
# Jest
jest --reporters=jest-junit
devq-junit-upload --junit junit.xml --framework jest
# pytest
pytest --junit-xml=junit.xml
devq-junit-upload --junit junit.xml --framework pytest
# Go (via gotestsum)
gotestsum --junitfile=junit.xml -- ./...
devq-junit-upload --junit junit.xml --framework go-test
# Mocha (via mocha-junit-reporter)
mocha --reporter mocha-junit-reporter
devq-junit-upload --junit test-results.xml --framework mochaCI sharding writes one XML per shard — pass the directory and the uploader
parses every *.xml recursively, merging all <testsuite>s into one run:
devq-junit-upload --junit test-results/ --framework vitestConnecting to Devant Cloud
Same env-var contract as the other devant-net reporters:
| Var | Default | Notes |
|-------------------|--------------------------|------------------------------------------------|
| DEVQ_API_URL | http://localhost:32124 | Your tenant's URL. |
| DEVQ_TOKEN | dev-admin-token | CI token (dq_ci_…) from Settings → CI/CD. |
| DEVQ_PROJECT_ID | 1 | Numeric project id. |
| DEVQ_RUN_NAME | <framework> — <ISO date> | Display name on the run. |
| DEVQ_RUN_ID | unset | If set, skip create/complete (orchestration). |
…or pass them explicitly via --api-url, --api-token, --project-id.
How tests bind to test cases
The uploader resolves each <testcase> against a Devant Cloud test_case row in
this order:
<property name="devq-key">DEF-AB12</property>— explicit override.<property name="testCaseId">DEF-AB12</property>orxray-test-key— common Xray / test-id conventions; only used if the value matches[A-Z]+-[A-Z0-9]+.@KEYtoken anywhere inname,classname, or property values.- Auto-create. The uploader prints the new key:
[devq] minted DEF-XYZ9 for "src/math.test.ts > add > adds positive numbers" — embed '@DEF-XYZ9' in the test name to bind it
Once a key is bound in source, future runs (and renames) reuse the same case.
Embedding the key in source
// Vitest / Jest / Bun test
it("adds positive numbers @DEF-XYZ9", () => { /* … */ });# pytest
def test_add_positive_numbers():
"""@DEF-XYZ9"""
...Or use a property if your runner supports it (pytest does via
pytest-junit markers; Jest does via jest-junit properties).
Status mapping
Standard JUnit semantics:
| In XML | Devant Cloud status |
|-----------------------|---------------|
| <skipped/> | skipped |
| <failure> or <error> | fail |
| (none of the above) | pass |
Non-standard status="..." attributes (e.g. Maestro's
SUCCESS/WARNING/ERROR enum) are ignored here — use
@devant-net/maestro-reporter if you need that mapping.
Artifact attachment
Pure JUnit has no per-test artifact convention, but most CI setups dump
screenshots / logs / coverage HTML into a sibling directory. Pass it with
--artifacts-dir and the uploader best-effort matches each file's
basename against the test's name / classname:
devq-junit-upload \
--junit junit.xml \
--artifacts-dir test-results/screenshots \
--framework jestFiles are attached to the matching test's first attempt. Mime types are
auto-detected from the extension (.png, .mp4, .json, .log,
.html, .xml, ...). Path-safety: the uploader refuses any file whose
realpath escapes --artifacts-dir.
What gets sent
| JUnit element | Devant Cloud call |
|---------------------|----------------------------------------|
| <testsuite> | (informational — folded into one run) |
| <testcase> | POST /v1/runs/:id/results + 1 attempt |
| <failure> / <error> | error_message + error_stack |
| <skipped/> | result status = skipped |
| <system-out> / <system-err> | stdout / stderr on the attempt |
| <property> | scanned for case-key bindings |
| <testcase time> | duration_ms on the result + attempt |
| <testcase timestamp> | started_at on the attempt + run window |
Run-level started_at and duration_ms are computed from the JUnit
timestamps when present, else by walking back from "now" using the sum
of <testcase time> values — so the dashboard always shows a coherent
run window even when the framework's XML omits per-test timestamps.
Limitations
- No retries. Standard JUnit emits one
<testcase>per logical test (most runners merge retries before writing the XML). If yours doesn't, the duplicates each get their owntest_resultrow. - No step trees. JUnit XML doesn't carry sub-step data; the result has one attempt and zero steps. Frameworks that expose richer data through their reporter API have native devant-net packages.
- Approximate artifact match.
--artifacts-dirmatches by filename-substring. False positives are possible if test names share short common substrings; prefer to put framework-specific reporters in front of the JUnit uploader when artifact accuracy matters.
