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:
| Rule | What it does |
|---|---|
maxQueries | Fails the test when it and its requests make more queries than this. |
failOn | Replaces the kinds of issues that fail the test. |
allow | Takes 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.
Tracing your code
Add steps of your own to a trace with span(), make a script or a scheduled job a trace with trace(), and name the signed-in user with setUser().
Command line
The tracepanel command that @tracepanel/js installs, to run a command with Tracepanel on, run the agent and serve your traces over MCP.