autotel-vitest
v0.4.65
Published
Vitest fixture for OpenTelemetry: one span per test so all instrumented code is filterable by test run
Readme
autotel-vitest
autotel-vitest gives each Vitest test a parent OpenTelemetry span, so all autotel instrumented calls made during that test become child spans.
How test and server appear as one trace
When you test an API over HTTP, the test runs in the Vitest worker process and the server runs in another process. Use injectTraceContext() from autotel/http so the request carries a traceparent header. Your server uses extractTraceContext() and creates its spans in that context, so the same trace ID is used on both sides. In your OTLP backend, look up the test’s trace ID to see the full trace: test span and all server spans (HTTP, handlers, db.userId, etc.). When you test in-process (no HTTP), all code runs in the same process and the fixture’s context automatically links test and handler spans in one trace.
sequenceDiagram
participant Test as Test process
participant Server as Server process
participant OTLP as OTLP backend
Note over Test: One span per test (trace ID = X)
Test->>Server: fetch() with traceparent (injectTraceContext)
Note over Server: extractTraceContext()<br/>HTTP span + child spans
Test->>OTLP: Export spans (trace X)
Server->>OTLP: Export spans (trace X)
Note over OTLP: One trace = test + APIInstall
Install autotel-vitest, autotel, and vitest:
# pnpm
pnpm add -D vitest
pnpm add autotel autotel-vitest
# npm
npm install --save-dev vitest
npm install autotel autotel-vitest
# yarn
yarn add --dev vitest
yarn add autotel autotel-vitestQuick Start
1. Initialize autotel in a setup file
// vitest.setup.ts
import { init } from 'autotel';
import { SimpleSpanProcessor } from 'autotel/processors';
import { otelTestCollector } from 'autotel-vitest';
init({
service: 'unit-tests',
spanProcessors: [new SimpleSpanProcessor(otelTestCollector)],
});Two details matter here:
setupFiles, notglobalSetup. Spans are created in the worker that runs the test, and worker module state is not shared with the main process whereglobalSetupruns.- Pass
otelTestCollectortoinit. OpenTelemetry SDK 2.x removedaddSpanProcessor, so a processor can only be registered when the provider is built. This is the collector the fixture drains after each test.
2. Register the setup file in Vitest config
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
setupFiles: ['./vitest.setup.ts'],
},
});3. Import test from autotel-vitest
import { test, expect } from 'autotel-vitest';
test('creates user', async () => {
await userService.createUser({ email: '[email protected]' });
expect(true).toBe(true);
});The fixture is auto: true, so every test gets a parent span automatically.
What You Get
- One span per test named
test:${task.name} - Span attributes:
test.name,test.file,test.suite - If a test throws, the span is marked as error and records the exception
- Any
trace()/span()calls in the test flow become children of the active test span - An
otel-traceannotation on every test, holding the trace URL (fromOTEL_TRACE_URL_TEMPLATE) or the raw trace id, so reporters — including the browser trace view's step list — link back to the trace - The test's spans on
task.meta.otelSpans, which executable-stories renders as a trace waterfall in its report with no extra wiring
Optional: Reporter (runner-side spans)
Use the reporter when you also want runner-process spans for test/suite timing:
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
setupFiles: ['./vitest.setup.ts'],
reporters: ['default', 'autotel-vitest/reporter'],
},
});Reporter spans:
- test span:
test:${name} - suite span:
suite:${name}
In browser mode, Vitest exposes a recorded Playwright trace to reporters as an
annotation attachment; the reporter copies its path onto the test span as
test.trace.path, so a failed span leads straight to the replay.
Testing Utilities
autotel-vitest re-exports helpers from autotel/testing:
createTraceCollectorassertTraceCreatedassertTraceSucceededassertTraceFailedassertNoErrorsassertTraceDurationwaitForTracegetTraceDurationcreateMockLogger
Example:
import { test, createTraceCollector, assertTraceCreated } from 'autotel-vitest';
test('traces user creation', async () => {
const collector = createTraceCollector();
await userService.createUser({ email: '[email protected]' });
assertTraceCreated(collector, 'user.createUser');
});Troubleshooting
- No spans exported: verify
init()runs in asetupFilesentry, notglobalSetup, and that it is passed a processor wrappingotelTestCollector. - No child spans under test span: ensure your test imports
testfromautotel-vitest, notvitest. - Reporter not running: ensure
reportersincludes'autotel-vitest/reporter'. task.meta.otelSpansempty: a span is only in the collector after it is exported, which is asynchronous. The fixture flushes before it drains; if you read spans inside the test body,await flush()first.
API
test: extended Vitesttestwith auto per-test span fixtureotelTestCollector: the collector the fixture drains ontotask.meta.otelSpans; pass it toinit()(see Quick Start)expect,describe,beforeEach,afterEach,beforeAll,afterAll: re-exported fromvitestautotel/testinghelpers listed above: re-exported from this package
