> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Reporter API

The Reporter API allows you to create custom test result processors and output formatters. This interface is experimental and may change in future versions.

> **Note**: For usage examples and configuration guidance, see the [Reporters guide](/guide/basic/reporters.md).

## Using custom reporter

Create a custom reporter by implementing the `Reporter` interface:

```ts
import type { Reporter, TestFileResult } from '@rstest/core';

const customReporter: Reporter = {
  onTestFileStart(test) {
    console.log(`Starting: ${test.testPath}`);
  },

  onTestFileResult(result) {
    console.log(`Finished: ${result.testPath}`);
  },

  onTestRunEnd({ results, duration }) {
    console.log(`All tests completed in ${duration.totalTime}ms`);
  },
};
```

Use the custom reporter in your configuration:

```ts
import { defineConfig } from '@rstest/core';
import { customReporter } from './path/to/custom-reporter';

export default defineConfig({
  reporters: [customReporter],
});
```

## Interface overview

The `Reporter` interface provides lifecycle hooks for test execution. Each hook is called at specific points during the test run.

> **Important**: For the most up-to-date interface definition and complete type signatures, please refer to the [source code](https://github.com/web-infra-dev/rstest/blob/main/packages/core/src/types/reporter.ts).

### Hook categories

- **File-level hooks**: `onTestFileStart`, `onTestFileReady`, `onTestFileResult`
- **Suite-level hooks**: `onTestSuiteStart`, `onTestSuiteResult`
- **Case-level hooks**: `onTestCaseStart`, `onTestCaseResult`
- **Run-level hooks**: `onTestRunStart`, `onTestRunEnd`, `onUserConsoleLog`, `onExit`

### Basic interface structure

```ts
interface Reporter {
  onTestFileStart?(test: TestFileInfo): void;
  onTestFileReady?(test: TestFileInfo): void;
  onTestFileResult?(test: TestFileResult): void;
  onTestSuiteStart?(test: TestSuiteInfo): void;
  onTestSuiteResult?(result: TestResult): void;
  onTestCaseStart?(test: TestCaseInfo): void;
  onTestCaseResult?(result: TestResult): void;
  onTestRunEnd?(payload: {
    results: TestFileResult[]; // Test file results; in watch mode, accumulated over the whole session.
    testResults: TestResult[]; // Results of every test, not grouped by file.
    summary: {
      tests: {
        total: number; // Total number of tests.
        passed: number; // Number of passed tests.
        failed: number; // Number of failed tests.
        skipped: number; // Number of skipped tests.
        todo: number; // Number of todo tests.
      };
      files: {
        total: number; // Total number of test files.
        failed: number; // Number of failed test files.
      };
    };
    duration: {
      totalTime: number; // Total duration in milliseconds.
      buildTime: number; // Build duration in milliseconds.
      testTime: number; // Test execution duration in milliseconds.
    };
    snapshotSummary: SnapshotSummary;
    unhandledErrors: {
      name?: string; // Error class name.
      message: string; // Error message.
      stack?: string; // Serialized stack trace.
      diff?: string; // Formatted assertion diff.
      expected?: string; // Serialized expected assertion value.
      actual?: string; // Serialized actual assertion value.
      retryCount?: number; // Retry attempt that produced this error.
      fullStack?: boolean; // Whether to print the full stack trace.
    }[];
    coverage?: CoverageMapData; // Present when coverage is enabled.
    rerunTestPaths?: string[]; // Watch only: files executed in the current cycle.
    getSourcemap: (sourcePath: string) => Promise<SourceMapInput | null>; // Resolves the source map of a built file.
  }): void | Promise<void>;
  onUserConsoleLog?(log: UserConsoleLog): void;
  onExit?(): MaybePromise<void>;
}
```


[Added in v0.12.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.12.0)

Starting in v0.12.0, Rstest also calls `onExit` when the reporter's owning context is released after normal run, list, or merge completion, watch close, or startup failure. It continues to run on abnormal CLI exits. List contexts currently do not attach reporters, so they have no reporter hooks to call. Use `onExit` to release resources such as streams or renderers. Errors from the hook do not replace the operation's result or error.


[Added in v0.11.1](https://github.com/web-infra-dev/rstest/releases/tag/v0.11.1)

Metadata result fields are available on reporter results. JSON-serializable runtime metadata written through `context.task.meta` is available on the `TestResult` passed to `onTestCaseResult`. Metadata written by hooks through `ctx.meta` is available on the corresponding suite result in `onTestSuiteResult`, and file-level hook metadata is available on `TestFileResult.meta` in `onTestFileResult`.

### onTestRunEnd


[Added in v0.12.0](https://github.com/web-infra-dev/rstest/releases/tag/v0.12.0)

`onTestRunEnd` is called when a run cycle finishes, with the results and aggregates of that cycle. Starting in v0.12.0, the payload carries `summary` and `rerunTestPaths`, and `unhandledErrors` is always an array of serialized errors.

In watch mode, `results`, `testResults`, and `summary` accumulate over the whole session: each file keeps its latest result, and deleted files are removed. `rerunTestPaths` lists the files executed in the current cycle and is absent for one-shot runs. Every entry in `unhandledErrors` is a plain object that can be serialized to JSON.

### TestResult

`TestResult` and `TestFileResult` are exported from `@rstest/core`. Suite and case hooks receive a `TestResult`; file hooks and `onTestRunEnd` receive `TestFileResult` values, which extend `TestResult` with the results of the file's tests.

- **Type:**

```ts
type TestResultStatus = 'skip' | 'pass' | 'fail' | 'todo';

interface TestResult {
  testId: string; // Task identifier.
  status: TestResultStatus; // Final result status.
  name: string; // Display name of the test, suite, or test file.
  testPath: string; // Path of the owning test file.
  parentNames?: string[]; // Names of the enclosing suites.
  duration?: number; // Execution duration in milliseconds.
  errors?: SerializedError[]; // Errors from the final attempt.
  retryErrors?: SerializedError[]; // Errors from earlier retry attempts.
  retryCount?: number; // Number of retry attempts performed.
  project: string; // Project name.
  meta?: TaskMeta; // Serializable metadata attached to the task.
  heap?: number; // Heap usage in bytes, sampled when `logHeapUsage` is enabled.
}
```

### TestFileResult

- **Type:**

```ts
interface TestFileResult extends TestResult {
  results: TestResult[]; // Results of the tests declared in this file.
  snapshotResult?: SnapshotResult; // Snapshot statistics of this file.
  coverage?: Record<string, FileCoverageData>; // Per-file coverage data when coverage is enabled.
}
```

`SerializedError` has the same shape as the entries of `unhandledErrors` in the `onTestRunEnd` payload. `SnapshotResult` comes from `@vitest/snapshot` and `FileCoverageData` from `istanbul-lib-coverage`.

## Examples

### Simple custom reporter

```ts
import type { Reporter } from '@rstest/core';

const simpleReporter: Reporter = {
  onTestFileStart(test) {
    console.log(`📁 ${test.testPath}`);
  },

  onTestCaseResult(result) {
    const status = result.status === 'pass' ? '✅' : '❌';
    console.log(`${status} ${result.name}`);
  },

  onTestRunEnd({ results }) {
    const passed = results.filter((r) => r.status === 'pass').length;
    const failed = results.filter((r) => r.status === 'fail').length;
    console.log(`\n📊 ${passed} passed, ${failed} failed`);
  },
};
```

### File output reporter

```ts
import { writeFileSync } from 'node:fs';
import type { Reporter } from '@rstest/core';

const jsonReporter: Reporter = {
  onTestRunEnd({ results }) {
    const report = {
      timestamp: new Date().toISOString(),
      results: results.map((r) => ({
        path: r.testPath,
        status: r.status,
        duration: r.duration,
        errors: r.errors,
      })),
    };

    writeFileSync('test-report.json', JSON.stringify(report, null, 2));
  },
};
```

### Reading runtime metadata

```ts
import type { Reporter } from '@rstest/core';

const metadataReporter: Reporter = {
  onTestCaseResult(result) {
    console.log(result.name, result.meta);
  },

  onTestSuiteResult(result) {
    console.log(result.name, result.meta);
  },

  onTestFileResult(result) {
    console.log(result.testPath, result.meta);
  },
};
```
