@qualflare/vitest
v0.4.2
Published
Native Vitest reporter for Qualflare — captures status, per-attempt retry history, flakiness, assertion diffs, nested steps and metadata.
Maintainers
Readme
@qualflare/vitest
A native Vitest reporter for Qualflare — captures test results directly
from your vitest run: status, per-attempt retry history and flakiness, assertion diffs, nested steps, and
author-facing metadata (labels, links, tags, priority, custom attachments).
Without it, Vitest results reach Qualflare through the Jest-compatible JSON file, which carries pass/fail and duration and nothing else — no retries, no flakiness, no attachments, no metadata.
The reporter itself makes no network calls. It writes a report directory, and
qualflare-cli uploads it — which is what lets any
number of sharded CI jobs merge into a single Launch.
Install
npm install --save-dev @qualflare/vitestRequires vitest >=3.0.0 (installed separately as a peer dependency) and Node >=18 (Vitest 4
itself requires Node >=20). You also need
@qualflare/cli v0.1.24 or newer. v0.1.18 was the
first release that recognised vitest as a framework (an older CLI silently labels your suites
generic), and v0.1.24 is the first that reads localImagePath — image attachments are now written
into outputDir rather than base64-inlined into the report, and a CLI that ignores the field leaves
an attachment with neither content nor a storage key, which the server records from its name alone
as an undownloadable placeholder.
The peer range is deliberately open-ended rather than capped at a known-good version, so a new
Vitest release never hard-blocks npm install for you. 3.0, 3.2 and 4.x are exercised in CI against
a real vitest run; newer versions are untested but not refused — please
open an issue if one misbehaves.
Vitest 2 and earlier are not supported: the reporter is built on the Reported Tasks API
(onTestCaseResult, onTestRunEnd, TestCase), which does not exist before 3.0.
Quickstart
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { qualflareReporter } from '@qualflare/vitest';
export default defineConfig({
test: {
reporters: [['default'], qualflareReporter({ environment: 'staging' })],
},
});qualflareReporter() is a typed helper. Vitest types a custom reporter's options as
Record<string, unknown>, so the hand-written tuple form silently accepts typos — but it works too,
if you prefer it:
reporters: [['default'], ['@qualflare/vitest/reporter', { environment: 'staging' }]],Then run your tests and upload the results — two steps, and no token needed for the first:
# 1. Run. Writes ./qualflare-results. Zero network calls.
npx vitest run
# 2. Upload. `qf login <identifier> <token>` stores the credential once.
qf <your-project-identifier> collect ./qualflare-resultsSharded CI
Point every shard at the same outputDir and collect once at the end. Each process writes its
own uniquely-named file, so shards never overwrite each other, and qf collect merges every file in
the directory into a single Launch:
# in each parallel job — all writing to the same directory
npx vitest run --shard="$SHARD_INDEX/$SHARD_TOTAL"
# once, after all shards finish (e.g. with the directory restored from CI artifacts)
qf <your-project-identifier> collect ./qualflare-resultsNothing needs configuring for this: Vitest hands reporters its own --shard value, so each case is
stamped with the shard that ran it automatically. (Vitest's shard index is 1-based — --shard=1/3
is the first shard — and Qualflare's is 0-based; the conversion is handled for you.)
Enriching your tests
import { expect, test } from 'vitest';
import { qualflare } from '@qualflare/vitest';
test('a user can check out', async () => {
qualflare.label('epic', 'Billing');
qualflare.link('https://tracker.example/QF-42', { type: 'issue', name: 'QF-42' });
qualflare.tag('smoke');
qualflare.priority('high');
await qualflare.step('add an item to the cart', () => {
qualflare.parameter('sku', 'BOOK-1');
expect(cart.items).toHaveLength(1);
});
expect(cart.total).toBe('10.00');
});Metadata travels on task.meta, the channel Vitest serialises from the test worker back to the
reporter, so these calls are ordinary synchronous function calls with nothing to await and no
promise that can reject into your run. Full reference in
docs/METADATA-API.md.
Vitest has no test.step() of its own, so qualflare.step() is the only way to get step structure
into a report. Steps nest, and timing is exact — real elapsed time around the awaited body.
Configuration
Every option has an environment-variable override, and everything has a sensible default — see
docs/CONFIGURATION.md. There is no token option: this reporter makes
no requests, so it has no credential.
One option is worth calling out because it fails late: environment is matched against the
environment's uid (slug), not its display name, so Staging in the UI is staging here. A
wrong value cannot fail at test time — the reporter makes no requests — so the run succeeds and
collect 404s afterwards. See
the note in the configuration docs.
Test reports
This reporter is tested with itself. e2e/ is a Vitest suite covering this package's own
behaviour — the metadata API, nested steps, image attachments and per-attempt retry history — run by
this reporter and uploaded to Qualflare on every merge to main. The results below are that suite's,
reported through the code this README documents:
Every case there is meant to pass, so a red run is a real regression rather than a fixture that fails
on purpose. Deliberately-failing cases live in test/integration/, which is never uploaded.
Known limitations
- Image attachments go out of band. A screenshot handed to
qualflare.attachment()or carried by a Vitest annotation is written intooutputDirand referenced by name, not base64-inlined, so it no longer competes for the report body or the run's attachment budget. Only png/jpeg/gif can take that route — those are what the upload endpoint accepts — and everything else still inlines. - Per-attempt history is reconstructed, not read, and needs
@qualflare/cliv0.1.23+. Vitest exposes no per-attempt array, soCase.attemptsis rebuilt from its accumulated error list. Per-attempt durations are unavailable, and a test usingexpect.soft()reports no attempt history at all. On a CLI older than v0.1.23 the history is written to the report and then discarded before it reaches the server — seedocs/LIMITATIONS.md. - Browser-mode screenshots and traces are not attached. They reach a reporter through
TestCase.artifacts(), which is experimental and 4.x-only, so it is deliberately not read until it stabilises.
Full details in docs/LIMITATIONS.md.
Development
npm run typecheck # tsc --noEmit
npm run lint # eslint .
npm run build # tsup -> dist/ (ESM + CJS + d.ts)
npm test # unit tests
npm run test:integration # spawns a REAL `vitest run` against test/integration/fixtures/The integration suite loads the reporter from the built dist/, not src/, so it exercises the
real package exports map — run npm run build first.
License
Apache-2.0
