Vitest

npm install @tracera/vitest

Tested on Node.js 22.19–26.12 and Vitest 2–5.0 (see Autotest adapters). @tracera/vitest on npm.

In vitest.config.ts:

import { defineConfig } from 'vitest/config';
import { tracera } from '@tracera/vitest/plugin';

export default defineConfig({
  plugins: [tracera()],
});

The plugin wires the custom runner, setup file, and reporter. You can still set test.runner / setupFiles / reporters manually if you prefer.

Environment variables, run mode, and retries: Autotest adapters.

Retry Env key: VITEST_RETRY.

Bind testCaseId and autotestName

Import test, expect, and hooks from vitest, and tracera and annotations from @tracera/vitest. The plugin wires the runner, setup, and reporter so stock beforeEach / afterEach map to the result’s Setup / Teardown.

At declaration (preferred — bind runs even if the body skips early):

import { test, expect } from 'vitest';
import { annotations } from '@tracera/vitest';

test('login', { meta: annotations(42, 'e2e/login') }, async () => {
  expect(true).toBe(true);
});

Inside the test (optional if annotations already bind the case):

import { test, expect } from 'vitest';
import { tracera } from '@tracera/vitest';

test('login', async () => {
  tracera.testCase(42, 'e2e/login');
  expect(true).toBe(true);
});

Skip

Use stock Vitest skip (test.skip, or skip(reason) from the test context). The skip reason is added to the result as comment.

import { test } from 'vitest';
import { annotations } from '@tracera/vitest';

test('login', { meta: annotations(42, 'e2e/login') }, async ({ skip }) => {
  if (!process.env.READY) skip('env not ready');
});

Steps

Use tracera.step: the call tracera.step(title, fn), @tracera.step("…") on a class method, or tracera.step("…")(fn). In a decorator title, {name} / {0} are filled from the method arguments; you can also pass a function that returns the title. In the call form, {name} / {0} are not filled in — pass a finished title string (or build it yourself). Async methods and Promises are awaited. With the Tracera plugin (or an explicit runner: "@tracera/vitest/runner"), steps inside stock beforeEach / afterEach go to the result’s Setup / Teardown. Suite-level hooks such as beforeAll / afterAll are not reported.

import { test, beforeEach, afterEach } from 'vitest';
import { tracera } from '@tracera/vitest';

beforeEach(async () => {
  await tracera.step('open shop', async () => {
    // …
  });
});

test('checkout', async () => {
  tracera.testCase(10, 'e2e/checkout');
  await tracera.step('add to cart', async () => {
    // …
  });
});

afterEach(async () => {
  await tracera.step('cleanup', async () => {
    // …
  });
});

Decorator on a method:

import { tracera } from '@tracera/vitest';

class Cart {
  @tracera.step('add {item}')
  async addToCart(item: string) {
    // …
  }
}

Comments, errors, and attachments

Comments, errors, and attachments: Autotest adapters.

import { test } from 'vitest';
import { tracera } from '@tracera/vitest';

test('login', async () => {
  tracera.testCase(42, 'e2e/login');
  tracera.comment('result-level note');
  tracera.error('result-level error text');

  await tracera.step('open login', async () => {
    tracera.comment('landed on login');
    tracera.attachment({
      fileName: 'suite-log.txt',
      mimeType: 'text/plain',
      data: new TextEncoder().encode('finished login flow'),
    });
  });
});

Parameterized tests

With test.each / test.for, wrap the callback: annotations(id, 'login as {login}', fn) so fields from the row object fill {login} and similar placeholders. Optional {param_id} is the Vitest test name for that row.

import { test, expect } from 'vitest';
import { annotations } from '@tracera/vitest';

test.each([{ login: 'ok' }, { login: 'sso' }])(
  'login as $login',
  annotations(42, 'login as {login}', async ({ login }) => {
    expect(login).toBeTruthy();
  }),
);