Use Traces To Debug Failing Tests
A failed assertion tells you what was wrong at the end of a test, but often not how the browser got there. A trace preserves the actions, screenshots, console and JavaScript events, navigation, and network activity that led to the failure.
A Shared Trace Format
Jason Huggins' SeleniumConf talk was one of the reasons Craftdriver adopted portable browser traces instead of a Craftdriver-only viewer format.
The exported zip follows the Playwright-style recording layout also used by tools like Vibium and WDIO, so the same artifact can be opened in viewers such as Vibium Player.
Craftdriver still records a crash-resilient raw NDJSON trace first, then packages it into the shared zip format when a test runner asks for an artifact.
Keep A Trace When A Vitest Test Fails
Start one browser for the suite, but scope tracing to the test hooks. afterEach() knows whether the test failed, so it can keep only the useful zip:
import { afterAll, afterEach, beforeAll, beforeEach, describe, it } from 'vitest';
import { rmSync } from 'node:fs';
import path from 'node:path';
import { Browser } from 'craftdriver';
describe('login', () => {
let browser: Browser;
const rawTraceDir = path.resolve('test-results/traces/login-raw');
const traceZip = path.resolve('test-results/traces/login-failure.zip');
beforeAll(async () => {
browser = await Browser.launch();
});
beforeEach(async () => {
await browser.startTrace({
outDir: rawTraceDir,
title: 'Failing login test',
});
});
afterEach(async ({ task }) => {
const failed = task.result?.state === 'fail';
await browser.stopTrace(failed ? { path: traceZip } : undefined);
if (!failed) rmSync(rawTraceDir, { recursive: true, force: true });
});
afterAll(async () => browser.quit());
it('fills and submits the login form', async () => {
await browser.navigateTo(
'https://dtopuzov.github.io/craftdriver/examples/login.html',
);
await browser.fill('#username', 'alice');
await browser.fill('#password', 'secret');
await browser.click('#submit');
await browser.expect('#welcome').toContainText('Welcome back, alice!');
});
});A passing run removes the raw trace and leaves no artifact. After a normal Vitest failure, the evidence is kept here:
test-results/
└── traces/
├── login-raw/ # trace.ndjson and evidence screenshots
└── login-failure.zip # open this in Vibium PlayerFor the best online experience, drop the zip into Vibium Player. To keep the trace entirely local, use Playwright Trace Viewer:
npx playwright show-trace test-results/traces/login-failure.zipCraftdriver includes screenshot-backed frame snapshots, so Playwright's main browser panel shows the captured page state. It is a visual snapshot rather than a restorable DOM, so the DOM/locator inspector is intentionally limited.
If the test process is killed before afterEach() runs, a zip cannot be finalized, but the append-only trace.ndjson and completed screenshots remain in login-raw/.
Try A Trace Before Creating A Failure
- Download the sample login trace.
- Open Vibium Player.
- Drop
vitest-login.ziponto the player and step through the login.
stopTrace() captures the final page state, so a failed assertion is visible as the last player frame. The sample was generated by the runnable Vitest proof for this recipe against the same published login page. To exercise the failure workflow yourself, change the expected message to Welcome back, bob!, run the test, and open the resulting login-failure.zip.
Trace files can contain screenshots, URLs, console messages, network metadata, selectors, and entered values. Use test credentials and review a trace before publishing it outside your team.