Tests that catchslow interactions.
The first run sets the limits. Later runs fail when a change goes over them.
npm install -D zerospikesWhat it adds to Playwright
Works on hosted CI runners
zerospikes compare measures main and the pull request on the same runner, in alternating sessions, and fails only on a real slowdown.
Drag and scroll latency
Latency for every pointermove and wheel step, where the Event Timing API stops.
Leak checks
zs.cycles fails on the heap, DOM nodes or listeners an open-and-close leaves behind.
Run history by branch
Each run takes ~1KB and carries its commit and branch. npx zerospikes shows each scenario's trend across runs and branches.
How it works
A Playwright test, with test imported from zerospikes. Here zs.measure types "mar" into the search box several times, clearing it before each, and keeps the median. Setup
import { test } from "zerospikes";
test("filter as you type", async ({ page, zs }) => {
await page.goto("/");
const search = page.getByRole("searchbox");
await zs.measure(
"type 'mar'",
() => search.pressSequentially("mar"),
{ before: () => search.fill("") },
);
});The first run passes and records a limit for each metric: its slowest time plus room for noise.
$ npx playwright test
PASS filter as you type (4.1s)
zerospikes 1 scenario · all within limits · 1 limits recorded
$ Later, a change to the search makes every key press scan all the rows. Each key now takes 212 ms against a limit of 64, and the test fails.
$ npx playwright test
FAIL filter as you type (4.6s)
zerospikes 1 scenario · 1 over limits
FAIL filter as you type › type 'mar' input key 212 ms > 64, frame.max 205 ms > 58
$ In npx zerospikes, the failed scenario lists its numbers, limits and the functions that took the time. If the slowdown is on purpose, Press w to save it as the new limits.
filter as you type › type 'mar' FAIL
Metric Last Limit
frame.max 205 ms 58 ms
input.key 212 ms 64 ms
Where the time went
188 ms filterRows src/search.js:42
14 ms renderList src/list.js:17
Slower than its limits. On purpose? w save the last run as its limitsQuestions
Why zerospikes?
Measuring an interaction in DevTools means recording a trace, reading it and remembering the number for next time. As a test, the measurement runs on every change, and CI fails on the pull request that makes the interaction slower.
Won't noise make tests fail for no reason?
Each scenario runs once to warm up and five more times, and the median is what gets checked. A recorded limit leaves room above the slowest run. A local machine busy with something heavy, can still go over. On hosted CI, zerospikes measures both builds in alternating sessions and fails only on a slowdown of 10% or more that isn't noise. Read more at Limits.
Which browsers and apps does it work with?
Frame and input times work in any Playwright browser. CPU time, long frames, memory checks and the profile of a failure need Chromium. It also measures Electron apps, and Tauri apps on Windows, through their DevTools port (experimental).
How is it different from a regular Playwright test?
A Playwright test checks that the interaction works, and passes whether it takes 20 ms or 500 ms. A zerospikes scenario is still a Playwright test, but it also fails when the interaction takes longer than its limits.