Skip to Content
ToolsTest Harness

Test Harness

The test harness tracks which requirements are exercised by your tests. It works with any JavaScript test runner that supports global setup and teardown — Vitest, Jest, Playwright, and others.

How It Works

  1. prepare() runs before tests, parsing requirements into a lookup cache
  2. requirement() calls in tests return descriptions and track coverage
  3. finalize() runs after tests, printing coverage and optionally syncing to cloud

Setup

// vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { globalSetup: './vitest.setup.ts', }, })
// vitest.setup.ts import { prepare, finalize } from '@popoverai/dotrequirements/test' export function setup() { prepare() } export async function teardown() { await finalize() }

Using requirement()

The requirement() function returns a formatted string for test descriptions and tracks coverage:

import { requirement } from '@popoverai/dotrequirements/test' describe(requirement('AUTH-LOGIN-1'), () => { it(requirement('AUTH-LOGIN-1.0'), async () => { // Test implementation }) it(requirement('AUTH-LOGIN-1.1'), async () => { // Test implementation }) })

Return Value

requirement() returns the requirement’s content, formatted for display:

requirement('AUTH-LOGIN-1') // → "A registered user, Jamie, can log in to their account" requirement('AUTH-LOGIN-1.0') // → "When Jamie provides valid credentials, they are authenticated" requirement('AUTH-LOGIN-2.given') // → "Given: Jamie has two-factor authentication enabled"

For labeled nodes, the label is included in the output (e.g., “Given: …”).

Multiple Requirements

Track multiple requirements in a single test by passing multiple IDs:

it(requirement('AUTH-LOGIN-1', 'AUTH-SECURITY-1'), async () => { // Both requirements tracked; returns first one's content })

Error Handling

If a requirement ID doesn’t exist, requirement() throws an error with the invalid path. This ensures typos are caught immediately rather than silently passing coverage.

Path Reference Patterns

Reference requirements using numeric paths or label-based paths:

PatternExampleDescription
RootAUTH-LOGIN-1Root requirement
NumericAUTH-LOGIN-1.0First child (position 0)
Numeric nestedAUTH-LOGIN-1.2.0First child of third child
LabelAUTH-LOGIN-2.givenFirst “Given” criterion
Label disambiguatedAUTH-LOGIN-2.given#1Second “Given” criterion
Label nestedAUTH-LOGIN-2.then.andFirst “And” under first “Then”

Label paths are:

  • Case-insensitive (given, Given, GIVEN all work)
  • Kebab-case for multi-word labels (edge-case, happy-path)

Coverage Reporting

Local Report

After tests complete, finalize() prints a coverage summary:

=== Requirements Coverage Report === Total Requirements: 12 Tested Requirements: 10 Untested Requirements: 2 Coverage: 83.3% ✓ Tested Requirements: - AUTH-LOGIN-1: A registered user, Jamie, can log in to their account - AUTH-LOGIN-1.0: When Jamie provides valid credentials... ✗ Untested Requirements: - AUTH-LOGIN-2: A user with two-factor auth must provide an OTP - AUTH-SECURITY-1: Session tokens expire after 8 hours ====================================

Coverage is calculated at the full path level—AUTH-LOGIN-1.0 counts separately from AUTH-LOGIN-1.

Cloud Reporting

With cloud credentials configured, coverage is automatically synced to dot•requirements cloud:

  • Historical tracking — When each requirement was last tested
  • Branch-aware — Separate coverage per Git branch
  • Team visibility — Everyone sees the same data at app.dotrequirements.io 

Configure by adding to .env.local:

DOTREQUIREMENTS_PROJECT_ID=your-project-id DOTREQUIREMENTS_PROJECT_SECRET=your-project-secret

Cloud reporting is error-tolerant—if the request fails, a warning is logged but your tests still pass.

API Reference

prepare(options?)

Call in global setup before tests run. Parses requirements files into a lookup cache for fast requirement() calls.

import { prepare } from '@popoverai/dotrequirements/test' prepare() // or with options prepare({ projectRoot: '/path/to/project' })

Options:

OptionTypeDescription
projectRootstringOverride automatic project root detection

finalize()

Call in global teardown after tests complete. Aggregates tracking data, prints coverage report, and syncs to cloud if configured.

import { finalize } from '@popoverai/dotrequirements/test' await finalize()

Returns a promise—always await it to ensure the coverage report prints and cleanup completes before the process exits.

requirement(...ids)

Returns requirement content for use as test descriptions. Tracks coverage for all provided IDs.

import { requirement } from '@popoverai/dotrequirements/test' requirement('AUTH-LOGIN-1') // Single requirement requirement('AUTH-LOGIN-1.0') // Child by numeric path requirement('AUTH-LOGIN-1.given') // Child by label path requirement('REQ-A', 'REQ-B') // Multiple requirements

Parameters:

ParameterTypeDescription
ids...stringOne or more requirement paths

Returns: string — The content of the first requirement

Throws: Error if any ID doesn’t resolve to a requirement

File Discovery

The test harness finds **/*.requirements.md files anywhere in your project.

Ignored directories: node_modules/, dist/, .git/, build/, example/, examples/, __tests__/, fixtures/

Project Root Detection

The harness determines project root in this order:

  1. Explicit projectRoot option passed to prepare()
  2. DOTREQUIREMENTS_PROJECT_ROOT environment variable
  3. Cached project root (written by prepare())
  4. Walking up from cwd looking for .requirements/

Running Without Setup

You can use requirement() without prepare(), but:

  • Requirements are parsed on-demand (slower for large projects)
  • A warning is logged about missing setup
  • Coverage tracking still works

For best performance and clean output, always configure global setup/teardown.

Last updated on