Skip to content

Stop Building the Browser You Already Have

Before building infrastructure, inventory what the estate already runs — the playwright MCP backend made the browser image unnecessary. And a QA checker's first

Stop Building the Browser You Already Have

I spent three CI iterations trying to build a browser image that was already running. The fix wasn’t more infrastructure. It was realizing the estate already had the tools I needed, and I was just too busy building a new one to notice.

The goal was simple: replicate the manual render-QA rigor we apply to articles, but automate it for every publish on both Ghost blogs. If a human checks the rendering, the machine should too.

The first hurdle was the environment. I assumed we needed a dedicated container with a headless browser to run the checks. I wrote the Dockerfile. I configured the CI pipeline. It failed. The PID-1 process wasn’t handling signals correctly, so I added tini. It failed again. The smoke test was looking for the browser binary in the wrong path. I fixed the path. It failed a third time because the playwright package wasn’t installed in the final image layer.

I was three iterations deep into debugging a container that shouldn’t have existed. Then I paused. Why am I building this? The estate already runs an MCP server that exposes Playwright capabilities. It’s already deployed, already authenticated, and already handles the browser lifecycle. I didn’t need a new image. I needed a client.

I scrapped the Dockerfile. I wrote a lightweight checker that calls the existing MCP server. The complexity dropped by an order of magnitude.

But the infrastructure wasn’t the only problem. The deployment itself was brittle. The External Secrets Operator (ESO) was serving version 1 of the custom resource definition. My manifest was written for version 2. The health gate caught it during a dry run with a cryptic no matches for kind error. It was a version mismatch, but it felt like a ghost in the machine until I checked the cluster’s actual CRD versions.

Then the worktrees started disappearing. The automated reaper, designed to clean up stale branches, was too aggressive. It ate two active worktrees mid-round. The checker script itself survived on disk, but the context was gone. I had to recover the state from the git ref. It was a reminder that automation tools often lack the nuance of human intent. The reaper didn’t know those worktrees were part of an active QA loop.

Finally, the merge blocked. The GH_TOKEN environment variable was set to a service account identity, which didn’t have permission to merge into the protected branch. The operator identity was required. I had to bypass the standard env injection and use the keyring token directly as the bearer token. It worked, but it highlighted how fragile credential chains can be when multiple systems assume different auth models.

Once the checker was stable, I wired it into the publish path. The harvester’s stage 6b now runs the same checker inline on every future publish. It’s not a separate cron job. It’s part of the pipeline.

The first live run was quiet. Both blogs listed clean. No errors. No warnings.

Then, the system validated itself.

A recent commit introduced a formatting error. Literal backticks were shipped in the markdown body, breaking the render. The manual pass would have caught it. The automated checker caught it too. But here’s the critical part: the checker failed the publish. The pipeline stopped. The operator saw the failure, fixed the backticks, and re-ran the commit.

The QA pass proved itself by catching a defect in its own pipeline. It wasn’t just checking the output; it was enforcing the quality of the input.

The transferable lesson is clear. Before building new infrastructure, inventory what the estate already runs. The Playwright MCP backend made the browser image unnecessary. I wasted three CI cycles building a solution to a problem that was already solved.

And when you do build a QA checker, don’t just let it report. Wire it into the publish path. Let it block. Let it fail. When a checker catches a defect in the pipeline that deploys it, you know the system is working. It’s validating itself end to end.

Don’t build new tools if the old ones can do the job. And if you do build a tool, make sure it has teeth.

Written by Todd Miller