Tracepanel

In tests

The Vitest plugin runs each test as a trace and fails a test with an N+1, duplicate queries or too many queries.

The Vitest plugin turns what Tracepanel finds into test failures, so an N+1 query fails CI before it reaches production. It comes with the @tracepanel/js package and needs Vitest 4.1 or later.

Turn it on

Add the plugin to the Vitest config:

// vitest.config.ts
import { defineConfig } from "vitest/config";
import { tracepanel } from "@tracepanel/js/vitest";

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

failOn sets the kinds of issues that fail a test:

tracepanel({ failOn: ["n+1", "duplicates", "slow-sql"] });

It takes the issue names that has: takes in a search, such as slow-sql or error. The default is n+1 and duplicates.

The plugin turns Tracepanel on in the test processes, with the widget off, and instruments pg, mysql2 and the ORMs on them before your tests import them. It starts the project's agent when none is running.

What a test records

Each test runs as a test trace named after the test. A request the test makes of a server it started, through supertest or fetch(), is a trace of its own, linked to the test's in the console. beforeAll runs outside the tests' traces, so seeding the database doesn't count. beforeEach and afterEach run inside each test's trace.

When the test passes, the agent looks at its traces. An issue of a kind in failOn fails the test with the issue, its query, the line of code that ran it and a link to the trace:

AssertionError: tracepanel found 1 issue in this test:

N+1: 4 similar queries · SELECT authors
  SELECT name FROM authors WHERE id = $1
  at src/books.ts:28 in listBooks
  in GET /books → http://127.0.0.1:52068/#/t/01M60NB74Q6T9AXZGMFJ7V8FB1
  Load the related rows with the parent rows (a join, or one batched query) ...

The test's trace: http://127.0.0.1:52068/#/t/01M60NB74J4Q5VDHCJD1Z4T1J7
Allowed here with the test's option { meta: { tracepanel: { allow: ["n+1"] } } }.

When a query goes through Prisma, Drizzle, TypeORM, Sequelize or Knex, the hint is that ORM's own way to load the related rows.

Change the rules for a test

The tracepanel meta changes the rules for a test or a describe(). A test's own rules come first, then those of the suites around it:

RuleWhat it does
maxQueriesFails the test when it and its requests make more queries than this.
failOnReplaces the kinds of issues that fail the test.
allowTakes kinds of issues out of those that fail the test.

testTrace() gives the test its trace: .queries counts the queries so far, and await .issues() lists what the agent found in the test's requests so far. maxQueries(n, fn) fails when fn makes more than n queries, its requests' included:

import { expect, test } from "vitest";
import { maxQueries, testTrace } from "@tracepanel/js/vitest";

test("feed", { meta: { tracepanel: { maxQueries: 10, allow: ["n+1"] } } }, async () => {
  await request(app).get("/feed");
  expect(testTrace().queries).toBeLessThanOrEqual(10);
  await maxQueries(2, () => request(app).get("/feed?page=2"));
});

Without an agent

With TRACEPANEL_AGENT=off, the tests run as usual and one warning says that issues aren't checked. Query counts need no agent, so maxQueries still works.

The plugin works with Vitest's forks and threads pools, where every worker sends its tests to the same agent, and with concurrent tests.

On this page