--- name: headless-screenshot description: Take a real screenshot of an HTML/CSS page from inside a container with no browser installed - `nix shell nixpkgs#chromium --command chromium --headless=new --disable-gpu --no-sandbox --screenshot= --window-size=, "file://"` renders the page and writes a PNG in one command, no puppeteer/playwright or pre-installed browser needed (only needed for scripted interaction like clicking, not a static render). Use this whenever you're about to claim a CSS/layout/frontend change looks right, whenever a PR reviewer or the operator asks to see a screenshot of a web UI change, or before asking a peer GUI-testing agent to screenshot something you could just render yourself - reasoning about a visual change from source or compiled CSS alone is not the same as actually seeing it rendered, and a real browser is one shell command away even when `which chromium` comes back empty. --- # Headless Screenshot "No browser in this container" usually means no browser is *pre-installed*, not that one is unreachable. `nixpkgs#chromium` is a `nix shell` away, and a single headless invocation renders a page and writes a PNG without a running server, a display, or any scripted interaction library. ## The command ``` nix shell nixpkgs#chromium --command chromium \ --headless=new --disable-gpu --no-sandbox \ --screenshot=/path/to/out.png \ --window-size=1200,900 \ "file:///path/to/page.html" ``` - `--no-sandbox` is required inside most agent containers (no user namespaces for Chromium's sandbox); `--disable-gpu` sidesteps GPU init failures in a headless environment. Both are safe defaults here - this isn't a browser you're exposing to untrusted input. - The target can be a local `file://` path or a real `http(s)://` URL (a dev server, a served `dist/`, a deployed page) - same command either way. - `--window-size` sets the viewport; match it to what you actually want to see (a narrow width to check a mobile layout, a wide one for a dashboard row). - Swap `--screenshot=` for `--dump-dom=` to get the rendered DOM as text instead of an image - useful for confirming markup/attributes landed correctly without needing to eyeball pixels. Stray `dbus`/`UPower` errors on stderr are harmless container noise (no session bus, no power daemon) - ignore them as long as the PNG file actually gets written. ## When source-reading isn't enough Grepping compiled CSS or tracing class names tells you the rules exist and target the right selectors - it doesn't tell you what the result actually looks like (wrong tier picked, two rules fighting, a value that's technically correct but visually off). Before telling someone a layout/CSS change "looks right," or before reaching for a peer agent's GUI-testing capability, try rendering it yourself first: - **Testing a real page**: serve the built `dist/` (or point at wherever it's already deployed) and screenshot that - it's the actual production bundle, not a reconstruction. - **Testing an isolated CSS change** (e.g. a shared stylesheet used by several pages): a small self-contained HTML file with the real CSS inlined and a few representative markup samples is faster to build than standing up a server, and just as honest about what the CSS actually renders. - **Attach it**: post the PNG as a PR/issue comment (or file attachment) alongside a one-line note on what it's rendered from - a reviewer with no browser either benefits from the same shortcut. ## What this doesn't replace This is a static render, not a browser automation tool. Verifying an interaction (a click, a hover state, a form submission) needs puppeteer/playwright or similar - reach for those only when the question is genuinely about *behavior*, not appearance. A missing emoji/glyph rendering as a box in the screenshot is usually a font availability artifact of the headless environment, not a real bug - don't mistake one for the other when reporting results. ## Two false-positive traps (and their fixes) Both of these look exactly like a real product bug in a naive screenshot and aren't - check for them before filing anything, and use the fix below if you actually need an accurate render (e.g. to judge icon/glyph layout, not just to note "there'd be an icon here"). - **Tofu boxes instead of emoji.** Plain `nixpkgs#chromium` has no color-emoji font, so any 🖥/📡/🔨-style glyph renders as an empty box. Add `nixpkgs#noto-fonts-color-emoji` to the `nix shell` - but a `FONTCONFIG_FILE` that lists *only* the emoji dir throws away the base fonts and blanks all normal text instead, which is its own false alarm. Merge, don't override: ``` cat > fonts.conf < /etc/fonts/fonts.conf $(nix eval --raw nixpkgs#noto-fonts-color-emoji)/share/fonts EOF FONTCONFIG_FILE=$PWD/fonts.conf nix shell nixpkgs#chromium nixpkgs#noto-fonts-color-emoji \ --command chromium --headless=new --disable-gpu --no-sandbox \ --screenshot=out.png --window-size=1440,900 "https://example/" ``` - **A bare "loading..." placeholder instead of real content.** A page whose content arrives after the initial paint (SSE, a `fetch` on mount) can get captured before that data lands - the default `--screenshot` doesn't wait for it. Add `--virtual-time-budget=8000` (milliseconds; tune to the page) to let it actually settle first.