Build in public

The Suite That Waited for a Font

By Arun Jalota · · ~6 min read

Tonight the site's test suite failed in the one way I keep writing about: it didn't fail on a check. It never reached a check. The run threw after forty-five seconds with a single line — page.goto: Timeout 45000ms exceeded — and under it, the reason: it had been navigating to the home page, waiting until "networkidle." The page had loaded. The text was there. The suite was standing in the doorway waiting for the network to go quiet, and the network never did.

The culprit was three files. My pages ask a font CDN for three webfonts — Inter, Geist, Instrument Serif — and on this particular runner the requests to fonts.gstatic.com hung. Not refused, which would have been fast and clean. Hung. Open sockets, no bytes, no close. And networkidle means exactly what it says: the browser reports the page settled only after five hundred milliseconds pass with no in-flight requests. Three requests that never finish is a network that is never idle, so the condition I was waiting for could not, even in principle, become true.

The dependency I signed up for without reading it

I have written this same lesson three times now from three different rooms. A compiler I shipped from a public CDN turned a convenience into an availability dependency. A synchronous font stylesheet sat in my critical rendering path and blocked first paint whenever its origin was slow. Each time I thought I had found the last hidden third party. Each time I had found one of them.

This one was different in a way that stung more, because it wasn't in the site. It was in the test. The site handles a dead font CDN correctly — that's the whole point of two other checks in this very suite, one that blocks every third-party origin and one that stalls them for four seconds, both of which assert the page still reads. The site was fine. The thing that couldn't survive a hanging font server was the harness I built to prove the site could.

"Wait until the network is idle" is a promise about origins you don't control. It quietly enrolls every one of them in your CI's dependency graph.

That's the trap in networkidle, and it's a comfortable trap because the instruction sounds so local. Wait for the page to settle. But a page settles when all its requests settle, and my page's requests include a font server three networks away. By choosing that wait condition I had, without noticing, written a test that could only pass when a machine I don't own, can't deploy, and can't page was reachable from wherever the test happened to run. On my laptop it always was. On a runner with tighter egress it wasn't, and the suite went from five hundred and seventy passing assertions to zero — not zero passing, zero run.

Zero failed, again

And here is the part that connects it to everything else I've been chasing: the exit was a crash, not a red assertion. 0 failed and could not start print differently only if someone reads past the first line. An unattended run at one in the morning doesn't have that someone. A suite that throws in its setup and a suite that passes clean both leave the same silence behind them, and silence is the state I trust least, because it's the one that looks like success. I've now watched this suite fail to start three separate ways — no browser binary, a missing shared library, and now a wait condition that can't resolve — and every one of them wore the same quiet face.

What happenedWhat the log almost saidWhat was actually true
Font CDN unreachable from runnerTimeout navigating to homeThe suite never ran a single check
The site itself—Fine; renders in system-ui without the fonts
The assertions—All 570 still green once the gate could start

The fix: a local gate owns its inputs

The wrong fix is to loosen the wait — swap networkidle for load everywhere and hope nothing that mattered was still in flight. That trades a hang for a race, and a race is a flake, and a flake is how a real failure eventually gets re-run into a pass. The right fix is to make the gate hermetic: a check that runs on my own files should depend on my own files. So the audit contexts now intercept requests to the two font origins and answer them instantly with an empty body. The page still asks; the answer just comes from inside the test instead of from across the internet. The network goes idle because nothing is left hanging, and every assertion that was ever going to run, runs.

This is safe for exactly the reason the site is safe: the fonts were never in the critical path. They load non-render-blocking and swap in after the page is already usable, because the font stack ends in system-ui. Stubbing them to empty changes no byte the reader waits for and no behavior any check measures. And the two resilience checks that should talk to the real third-party — the block test and the stall test — keep their own routing and their own meaning. I hardened the setup, not the subject.

The distinction I'd hand a team is this: a test that depends on a service you don't own isn't testing your code, it's testing the weather. Sometimes that's the point — an integration test is supposed to feel the weather. But a unit or a local gate that reaches a CDN has a dependency it never declared and a failure mode it never chose. The reach is usually invisible, buried in a convenience like "wait until the page is done," because "done" is defined by the page, and the page talks to strangers.

What I'd tell my team

Read your wait conditions as dependency declarations, because that's what they are. networkidle is a lovely default and a quiet way to sign a contract with every origin your page touches. If the gate is meant to be local, make it local all the way down: stub the origins you don't own, so a slow font server three time zones away can't decide whether your build is green. And treat "the suite crashed in setup" as its own kind of red — louder than a failed assertion, not quieter — because the run that never started is the one most likely to be mistaken for the run that passed. My site was fine tonight. The thing that couldn't survive a hanging font was the test I wrote to prove it could, and the smallest honest fix was to stop asking a stranger whether my own files were okay.