Infrastructure · We run it ourselves
A Node library that launches and drives Chromium, Firefox and WebKit through one API — written for end-to-end tests, equally usable as a scriptable browser.
The interesting use of Playwright in this practice is not testing. It is the only dependency in the site repository that ships an entire rendering engine, and that engine is used three ways: as the layout engine for printed documents, as a rasteriser at build time, and as a measurement instrument pointed at other people's websites. The test runner is a separate package and it is not installed.
01
Playwright ships two things under similar names and this repository installs only one of them — `playwright`, the library that launches and drives a browser, rather than `@playwright/test`, the runner with fixtures, retries, sharding and a trace viewer. The distinction decides what the dependency costs over a year. A driven browser is a function call that either produces a file or throws; a test suite is a standing obligation against markup that changes. Seven scripts here import `chromium` from the library, and not one of them asserts anything about the site's own DOM.
02
Printing through `page.pdf()` makes CSS the layout language for a tax invoice — `@page` margins, the print stylesheet, break rules — resolved by the same engine that renders the HTML twin of that same document. The alternatives trade differently: pdfkit and ReportLab want boxes placed at coordinates, which is precise at first and unmaintainable by the third revision, and wkhtmltopdf is frozen on a WebKit that predates modern layout. Here one renderer emits a string, the script writes the .html and the .pdf from it, and changing the design of an invoice is changing a stylesheet.
03
Determinism has to be earned rather than assumed. `page.pdf()` prints the moment the load event fires — before the webfont is parsed and before images decode — and it fails silently: the document ships with a fallback face and nobody notices until the counterparty opens it. The fix is to wait on conditions rather than on a clock: `await document.fonts.ready`, then `img.decode()` on every image on the page. A two-second `waitForTimeout` in the same place is a bet on the slowest machine that will ever run the script, and it is a bet that gets lost in CI.
04
Print is a separate rendering mode rather than a screen render with different margins, and Playwright exposes the switch through `emulateMedia({ media: "print" })`. That makes a build-time assertion on a document possible. After printing an invoice the script sets the viewport to the printable width in CSS pixels — 210 mm less two 15 mm margins, at 96/25.4 px per mm — re-measures the body box under print media, and reports when the document has outgrown its single sheet. The same measurement under the screen stylesheet answers a question nobody asked, because the screen layout uses a different width.
05
Several properties of a web page exist only while it is running. A countdown that restarts for every visitor is indistinguishable in the HTML source from a genuine deadline — the difference is what the script does in a fresh browsing context — so the dark-patterns census loads each origin twice in separate contexts and compares the two deadlines. An HTTP client cannot see that, and cannot see a consent overlay inserted after paint or a stock counter generated at random either. A real browser is not a convenience in that work; it is the instrument.
06
The same dependency covers the small rendering chores a site accumulates — the open-graph image for an article, and `favicon.ico`, which is drawn from the SVG at 16, 32, 48, 64, 128 and 256 px, each size rendered at its own resolution through a canvas instead of downsampled from one raster. Hairlines that read at 256 do not survive being scaled to 16, and a canvas with an alpha channel always exports RGBA, where Chrome's screenshot of a fully opaque page emits RGB and Turbopack then refuses the ICO. Rasterising artwork in the engine the reader will view it in removes a class of disagreement between build output and browser.
07
The browser also reads the artefact back. A DOM-based check of how full a printed page is measures blocks, not where Chrome decided to break the page, and page breaks are the only place print layout genuinely fails. So the inspection script serves pdf.js and the finished document from a loopback port, draws every page into a canvas and reports ink coverage per page — below roughly 78 per cent is a page worth opening. The document is produced and audited by one engine, which is a closed loop rather than two opinions about the same file.
An entry that cannot point at something you can open, run or read does not compile. That is a property of the type, not a promise in a paragraph.
Invariant
Every use above needs a real browser binary on the machine — roughly 150 MB per engine after `playwright install`, plus a few hundred megabytes of resident memory per context — so none of it runs inside a Vercel function. Document rendering here is a local command or a build step, never a request handler; putting it behind an HTTP endpoint means paying for a container that keeps a browser warm, and that container is the actual cost of "generate the PDF on demand". The print path is narrower still, because page.pdf() exists only in Chromium. And the use the tool is named for is the one this practice declines: an end-to-end suite is priced in maintenance rather than in the number of tests, every selector is a bet on markup that the next redesign invalidates, and a suite that goes intermittently amber gets muted instead of fixed — at which point it costs money and asserts nothing. This repository therefore has no playwright.config and no spec file; the gates that fail the build are Node scripts that assert on structured data and on route agreement, which do not flake.