@arizeai/phoenix-client/vitest ships a Vitest entrypoint plus an optional
reporter that prints a Phoenix-flavored summary at the end of the run.
Setup#
Create a separate phoenix.vitest.config.ts so eval files don’t get
swept into your normal unit-test config:
includekeeps eval suites separate from unit tests by matching the*.eval.tsconvention.reporterskeeps Vitest’s default diagnostics and enables the Phoenix summary block.setupFiles: ["dotenv/config"]loadsPHOENIX_ENDPOINT,PHOENIX_API_KEY, and any other env vars from.env.testTimeoutis bumped because LLM calls can be slow.
The jsdom test environment is not supported. Either omit environment
or set it to "node".
Add a script to package.json:
The script intentionally uses vitest run rather than watch mode —
many evaluators include longer-running LLM calls.
API#
describe(name, fn, config?)#
Declares a Phoenix test suite. The suite name is the dataset and
experiment name on the Phoenix server. describe.only and
describe.skip work like Vitest’s variants.
| Field | Type | Description |
|---|---|---|
datasetName | string | Override the dataset / experiment name (defaults to the suite name). |
description | string | Description recorded on the dataset and experiment. |
metadata | Record<string, unknown> | Suite-level metadata applied to the experiment. |
client | PhoenixClient | Pre-configured @arizeai/phoenix-client instance. |
repetitions | number | Run each test in the suite this many times (default 1; PHOENIX_TEST_REPETITIONS overrides the default). Per-test repetitions wins. |
acceptanceCriteria | AcceptanceCriterion[] | Aggregate annotation thresholds that fail the suite after all tests run. |
dryRun | boolean | Run the whole suite locally — no dataset, experiment, runs, or annotations are created in Phoenix. Same effect as PHOENIX_TEST_TRACKING=false, scoped to this suite. |
test(name, params, fn, timeout?)#
Declares a single Phoenix test case. The params object carries the
Phoenix Example fields. test.only, test.skip, and test.each
mirror Vitest semantics. it is a re-export of test.
| Param field | Maps to |
|---|---|
input | Example.input |
expected | Example.output (reference) |
metadata | Example.metadata |
id | Example.id (stable upsert id) |
repetitions | Number of runs against this example (overrides the suite value). Reported as "<name> [rep i/N]". |
dryRun | When true, this case runs as an ordinary local test — no dataset example, no run, nothing uploaded — even if the suite syncs. |
test.each(table)(name, fn, timeout?)#
Run the same test body across many examples.
The name template supports %i, %s, and %j for parity with Vitest’s
test.each. Without a placeholder the row index is appended.
Logging#
px.logOutput(value)records the actualoutputfor the run.px.logAnnotation({ name, score, ... })records an annotation.px.evaluate(evaluator, params?)runs an evaluator object and records its result as an annotation linked to the evaluator trace. Evaluators can come from@arizeai/phoenix-evals.createEvaluator(),asExperimentEvaluator(), or any plain{ name, evaluate }object.
See CI Eval Test Annotations for the full annotation shape.
Acceptance Criteria#
Use acceptanceCriteria to gate the suite on aggregate annotation scores in
CI. Criteria run after the suite finishes, so all cases still execute and the
reporter shows the full scorecard before failing. Each criterion aggregates one
annotation (by annotationName) with one metric:
metric: "average"— gate on overall quality: the mean score across all runs must clearthreshold(compared indirection).metric: "passRate"— gate on consistency: each run passes when itspassFnpredicate returnstrue, and the suite passes when the fraction of passing runs is at leastminPassRate(e.g.minPassRate: 0.9⇒ 90% must pass).
passFn receives the run’s annotation and returns a boolean, so it can express
any pass rule — a score bar, a range, a label match, a metadata check, etc.
| Field | Description |
|---|---|
annotationName | Annotation to aggregate. If a run logs the same annotation more than once, the last one counts. |
metric | "average" checks the mean of all numeric/boolean scores against threshold (tolerates weak runs if the mean holds); "passRate" counts runs whose passFn returns true and requires that fraction to reach minPassRate. |
threshold | average only. The bar the mean must clear (in direction). |
direction | average only. "maximize" (default; higher mean is better, clears threshold when >=) or "minimize" (lower is better, clears when <=). Use "minimize" for cost, latency, or error rates. |
passFn | passRate only. (annotation) => boolean predicate deciding whether a single run passes, given its last annotation for annotationName (score, label, explanation, metadata, …). |
minPassRate | passRate only. Minimum fraction of runs (0–1) that must pass for the suite to pass (1 = all). The suite passes when passRate >= minPassRate. |
Edge cases. An average criterion with no numeric/boolean scores — or a
passRate criterion whose annotation was never logged — fails rather than
passing vacuously. Skipped tests are excluded from the aggregate; dry-run tests
are included because they still execute locally.
In the reporter’s Acceptance Criteria block the reported value is the mean
for average and the fraction of runs that passed for passRate (so a
fully-passing passRate criterion reads 1.000).
Reporter Output#
When @arizeai/phoenix-client/vitest/reporter is loaded, the runner prints
a per-suite block at the end of the run with pass/fail counts,
annotation aggregates, acceptance criteria, and links to the Phoenix dataset and experiment.
The default Vitest reporter still runs alongside it.
Source Map
src/vitest/index.tssrc/vitest/reporter.tssrc/testing/runner.tssrc/testing/acceptance.ts

