DEV Community

Cover image for Deleting 500 tests made the suite stronger
Ian
Ian

Posted on

Deleting 500 tests made the suite stronger

This one covers October 21 to 27. The theme was proof: which tests mean something, how fast they run, and how a program can tell a human from a script. Most of the code went into my workflow kit and into two private projects that model human behaviour, one for a browser and one for a game.

A test that can only fail for a fake reason is a cost

workkit is my workflow kit for Claude Code: GitHub issues as the work list, labels as the pipeline, hooks that enforce the rules. It shipped nine releases this week, 0.78 through 0.83.1, and the biggest change was to its own tests.

The kit's npm test used to run every suite in one process, one after another. Version 0.81 gave it a parallel runner: each suite runs in its own worker process, with cores minus one running at once. Each suite's output prints whole, in sorted order, so two suites never interleave their lines, and the run ends with the five slowest suites. That last list is the useful part. It told me which suites were slow because they waited, not because they worked.

Then came the audit. I went through the kit's tests with one question: if this test failed, would it mean real behaviour broke? About 450 cases said no. They asserted that a mock was called, that a file existed after a write, that a framework did what its docs promise. They were struck. My dotfiles repo got the same treatment and lost 85 cases and two whole suites. The suites are smaller and every red is now worth reading.

The rule that keeps it that way is a hook. The safety:test-bar hook fires when a test file is written. It names each line that misses the bar: an assertion on an implementation detail, a mock of something the test could run for real, a test with no failure condition. It never blocks. It just says, at write time, which lines are padding. One of its rules is deadline: it names any test that sleeps a literal number of milliseconds. The kit's own tests were full of those, and every one became a poll that waits until a condition holds, with one shared deadline. The flakes went with them.

Two smaller things in the same releases. The commit gate now runs the root npm test on the staged tree, the exact files going into the commit, not the working tree. And it stands down on a repo's first commit, where there is nothing to prove yet.

A mouse path that overshoots on purpose

A new private repo this week models human input for a real Chrome window. It has no public page yet, so no link; the work is worth describing anyway.

The problem it solves: automation that moves a mouse in a straight line at constant speed, and types at a fixed interval, is trivially a script. Humans do neither. So each pointer move follows a minimum-jerk curve, the shape a hand makes when it is trying to be smooth. It overshoots the target a little, corrects, and sometimes stops part way, like a finger lifting off a trackpad. Clicks have settle and hold times. Scrolling has wheel and trackpad modes. During a wait, the cursor drifts.

Typing gets the same treatment. Key gaps are drawn from a log-normal distribution, so most are short and a few are long. Keys overlap the way they do on a real keyboard. There are pauses after words and after sentences, and the occasional typo that gets noticed and corrected. Typos never happen in password fields.

The part I like most is the seed. launch({ seed }) draws every behaviour scale from one number, so the same seed gives the same speed, rhythm and habits every run, and a different seed is a different person. A neutral seed types at about 92 words per minute; the trait range runs from roughly 74 to 110. A run pin replays one path exactly, which is how I debug a flaky click.

The input goes over the Chrome DevTools Protocol as trusted events, so navigator.webdriver reads false and the usual Playwright tells are absent. It ships an MCP server, so an agent can drive a browser with the same input model.

Training the vision model on the game itself

The game bot (private, no link) watches a game window, finds monsters and the player with a small vision model, and presses keys with humanized timing. The big change this week was where the training data comes from.

Until now the sprites came from a community API. Now npm run train pulls the game client from the game PC when its hash has changed and exports monsters and backgrounds straight from it. The synthetic training set is built from crops of every map, with the game's front layers drawn over the sprites, so the model learns what a monster looks like behind a tree.

The training exam got honest, too. When a box is missed, the report now says what it was boxed as instead, which is how the three snails turned out to be the lowest scorers. The fix was a weight: training.monsters.weight oversamples the monsters the exam scored lowest, those three at 3x. npm run train -- --map=<id> is a quick fine-tune from the last full fit, for one map, instead of a full run.

Two more small ones. A monster that moves on its own after lying still is flagged moving and left out of loot. And every config block is now checked by one engine in one order, so a typo like aera fails to load and names itself, instead of being ignored.

The hooks finished moving in, and the network got a log

My dotfiles are private, so no link, but two things are worth a paragraph.

Last week I moved my Claude Code hooks from shell scripts into one in-process module. This week the last of them joined: the linter, the session context and nudge hooks, notifications and keep-awake. A tool call now forks nothing while the keep-awake hold was seen alive in the last minute. Keep-awake itself got harder to break: the hold survives a Stop while a subagent or background task is still running, and an API-error turn no longer lets the Mac sleep while helpers run.

The other is a skill called netwatch, born from a bad week of wifi. netwatch.sh pings the router and 1.1.1.1 once a second, resolves a name through both, samples the wifi channel and signal, and logs every outage with its duration and a cause: the wifi dropped, or the ISP did. A report.sh counts a night by cause, so the question of whose fault it was has a number.

Small but satisfying

  • The streamer-mode VS Code extension (it hides sensitive files while you stream) can pause auto-detect for 1, 5, 30 or 60 minutes and turns itself back on. It also lost its second Activity Bar icon; one icon with a badge now shows the state.
  • workkit's review, triage and suite markers moved to ~/.workkit/markers/, out of the temp folder the system cleaner empties, with a seven-day sweep.
  • A finding outside the issue in hand now stays in chat under a Findings heading instead of being filed unasked. There is a toggle to file them.
  • The content pipeline published about 80 posts across 16 product sites, with no hands on it.

Next week: the browser project gets its first real job, and the test bar grows a rule or two from what it keeps catching. If you have audited a test suite and struck a big share of it, what was the question you asked of each test? Comments open.

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev •

Asking what a failure actually means is a useful test-audit criterion. Mutation testing can complement that review: deliberately change a business rule and see whether the remaining suite detects the regression.