Examples
Each example below is a small project of a made-up trading app, in a common setup. The projects are in tests/fixtures, and the output shown is from scanning them; the test suite checks that both stay as shown here.
Playwright end-to-end tests
trading-web tests the trading app's UI with page objects and fixtures. Its specs import test from a fixtures file that extends Playwright's:
// fixtures/index.ts
export const test = base.extend<TradingFixtures>({
signedInPage,
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
// ...
});
export { expect } from '@playwright/test';The scan follows that import one file deep, sees base.extend() on Playwright's test, and reads the specs that use it like any other:
// tests/orders/order-lifecycle.spec.ts
import { test, expect } from '../../fixtures/index';
// One order moves through every status, so each step depends on the one before.
test.describe('order lifecycle', { tag: '@orders' }, () => {
test.describe.configure({ mode: 'serial' });
test('places a limit order for 100 NVDA', async ({ orderTicket, blotter }) => {
// ...
test('shows a partial fill', async ({ blotter }) => {
await expect(blotter.status('NVDA')).toHaveText(/Partially filled \(\d+\/100\)/);
});npx test-inventory "tests/**/*.spec.ts" --framework playwright --output inventory.jsonThe describe's record has the mode set by describe.configure(), and its tests inherit the mode and the tag:
{
"id": "2b2853e11e8f8699",
"relativeFilePath": "tests/orders/order-lifecycle.spec.ts",
"lineStart": 4,
"title": "order lifecycle",
"isSerial": true,
"tags": ["@orders"]
}{
"fullName": "order lifecycle > shows a partial fill",
"suiteId": "2b2853e11e8f8699",
"isSerial": true,
"tags": ["@orders"]
}A Node.js service
order-service is an HTTP service that sends orders to a broker, tested with Vitest. Its repository tests need a database and skip themselves without one:
// tests/repository/orders-repository.test.ts
describe.skipIf(!process.env.DATABASE_URL)('orders repository', { tags: ['db'] }, () => {
// ...
test('stores an order with its broker id', async () => {npx test-inventory "tests/**/*.test.ts" --framework vitest --output inventory.jsonEach test in the describe gets the describe's condition and tags. stateSource and stateLine say where the state comes from:
{
"fullName": "orders repository > stores an order with its broker id",
"state": "skip",
"isConditional": true,
"condition": "!process.env.DATABASE_URL",
"stateSource": "suite",
"stateLine": 5,
"tags": ["db"]
}The project's Vitest config defines the db tag, and a tag in the config can skip tests or change how they run. The scan does not read the config, and says so:
{
"code": "config-may-change-state",
"level": "warning",
"relativeFilePath": "tests/repository/orders-repository.test.ts",
"lineStart": 5
}A skip from the test context is read as well, with its condition and note:
// tests/risk/daily-loss-limit.test.ts
test('resets the loss at the start of the next session', ({ skip }) => {
skip(process.env.TZ !== 'America/New_York', 'session boundaries are checked in New York time');{
"fullName": "daily loss limit > resets the loss at the start of the next session",
"state": "skip",
"condition": "process.env.TZ !== 'America/New_York'",
"reason": "session boundaries are checked in New York time",
"stateSource": "test",
"stateLine": 14,
"isParallel": true
}A known bug marked with test.fails is expectedToFail, and the comment above it is kept:
// tests/broker/fill-events.test.ts
// BUG: TRD-588 fractional partial fills are rounded down to whole shares
test.fails('keeps the fractional shares of a partial fill', () => {{
"fullName": "filledShares > keeps the fractional shares of a partial fill",
"state": "expectedToFail",
"comments": ["BUG: TRD-588 fractional partial fills are rounded down to whole shares"]
}React with Testing Library
trading-dashboard keeps its tests next to its React components, in *.test.tsx files. JSX is read like the rest of the code:
// src/components/OrderTicket.test.tsx
// TODO(TRD-731): the date picker for good-till-date orders is being rebuilt
it.skip('submits a good-till-date order', async () => {
const user = userEvent.setup();
render(<OrderTicket symbol="AAPL" lastPrice={187.5} buyingPower={25_000} onSubmit={vi.fn()} />);
await user.click(screen.getByRole('button', { name: 'Good till date' }));
});
it.todo('shows the estimated commission');npx test-inventory "src/**/*.test.tsx" --framework vitest --output inventory.json{
"fullName": "OrderTicket > submits a good-till-date order",
"relativeFilePath": "src/components/OrderTicket.test.tsx",
"state": "skip",
"comments": ["TODO(TRD-731): the date picker for good-till-date orders is being rebuilt"]
}{
"fullName": "OrderTicket > shows the estimated commission",
"state": "todo",
"bodyHash": null
}Vue with Vue Test Utils
watchlist-vue turns on Vitest's globals, so its specs use describe and it without importing them:
// tests/unit/Watchlist.spec.ts
import { mount } from '@vue/test-utils';
import Watchlist from '../../src/components/Watchlist.vue';
// ...
describe('when the feed is delayed', () => {
it('shows how many minutes the quotes are delayed', () => {
const wrapper = mount(Watchlist, { props: { quotes, delayedMinutes: 15 } });
expect(wrapper.get('[data-test="delay-banner"]').text()).toBe('Quotes are delayed by 15 minutes');
});
it.todo('greys out quotes older than the delay');
});npx test-inventory "tests/unit/**/*.spec.ts" --framework vitest --output inventory.jsonA name that a file uses but never imports or declares is read as a Vitest global. The scan then looks for globals: true in a Vitest or Vite config, or vitest/globals in a tsconfig, in the file's folder and the folders above it, and warns with globals-not-enabled when there is none. Here vitest.config.ts turns them on:
{
"fullName": "Watchlist > when the feed is delayed > greys out quotes older than the delay",
"suitePath": ["Watchlist", "when the feed is delayed"],
"state": "todo"
}Vitest browser mode
chart-browser draws candlestick charts on a canvas and tests them in Chromium and WebKit with Vitest's browser mode. One test is skipped in WebKit:
// src/candlestick-chart.test.ts
// WebKit returns an empty blob for canvases larger than the device pixel ratio allows in headless mode.
test.skipIf(server.browser === 'webkit')('exports the chart as a PNG', async () => {npx test-inventory "src/**/*.test.ts" --framework vitest --output inventory.json{
"fullName": "CandlestickChart > exports the chart as a PNG",
"state": "skip",
"isConditional": true,
"condition": "server.browser === 'webkit'",
"stateSource": "test"
}Browser instances come from the config, which is not read, so each test is one record. The runner runs it once per browser.
In-source and type tests
strategy-engine keeps some tests next to the code they test, inside if (import.meta.vitest):
// src/pnl/format.ts
if (import.meta.vitest) {
const { describe, it, expect } = import.meta.vitest;
describe('formatPnl', () => {
it('adds a plus sign to gains', () => {
expect(formatPnl(1234.5)).toBe('+$1,234.50');
});It also has type tests in *.test-d.ts files:
// tests/orders/order.test-d.ts
describe('Order', () => {
test('narrows to a limit order by its type', () => {
const order = { symbol: 'AAPL', side: 'buy', type: 'limit', quantity: 10, limitPrice: 187.5 } as Order;
if (order.type === 'limit') expectTypeOf(order).toEqualTypeOf<LimitOrder>();
});Scan the source files and the type test files along with the tests:
npx test-inventory "tests/**/*.{test,test-d}.ts" "src/**/*.ts" --framework vitest --output inventory.jsonThe import.meta.vitest check is how in-source tests are written, so it does not count as a condition:
{
"fullName": "formatPnl > adds a plus sign to gains",
"relativeFilePath": "src/pnl/format.ts",
"lineStart": 16,
"state": "active",
"isInCondition": false
}{
"fullName": "Order > narrows to a limit order by its type",
"relativeFilePath": "tests/orders/order.test-d.ts",
"isTypeTest": true
}A monorepo
trading-monorepo has three packages, each with its own Vitest config, and a root config that lists them as projects. Scan them all from the root:
npx test-inventory "packages/*/src/**/*.test.ts" --framework vitest --output inventory.jsonPaths are relative to the root, so each record names its package:
{
"fullName": "QuoteCache > returns the mid price of a fresh quote",
"relativeFilePath": "packages/market-data/src/quote-cache.test.ts"
}Only market-data turns on globals, in packages/market-data/vitest.config.ts. The scan finds that config from the package's test files, so it gives no globals-not-enabled warning:
{
"fileCount": 3,
"suiteCount": 3,
"testCount": 8,
"activeCount": 7,
"todoCount": 1,
"diagnosticCount": 0
}To scan one package, pass it as the root: --root packages/risk. The framework version is read from node_modules in the root or a folder above it, so a package of a monorepo gets the version installed at the top.