A piece of artwork went live last month without the contact line at the bottom. Phone, handle and site were in the HTML, the CSS placed them, the browser laid them out, and the file that got published did not contain them. Three checks ran before publishing, and all three passed.

What the check asked

The pipeline was short: render a local HTML file, screenshot it, verify it. Verification asked two questions. Is the image 1080 by 1350? It is. Does each element sit inside its own box, with no overlap and no text against the edge? Yes.

Nobody asked whether the file contained the whole page. The image had the dimensions from the request, and the layout assertions had run against a live DOM whose document was 1722 pixels tall.

A screenshot request describes a rectangle

I rebuilt the case with a minimal page: one flex column, 1080 wide, 90 pixels of lateral padding, nine paragraphs and a footer with a 3px rule above it. The design height was 1350. The content did not fit, and a flex column grows instead of overflowing, so the document ended at 1722. Nothing in the CSS was wrong. A page can be taller than the box you drew for it.

Measuring before capturing:

JSON.stringify({ doc: document.documentElement.scrollHeight, viewport: window.innerHeight, footer: Math.round(document.querySelector('.rodape').getBoundingClientRect().bottom) }) 

Output on that page:

{"doc":1722,"viewport":1350,"footer":1652} 

The class is rodape, footer in Portuguese, because the test page came out of a Portuguese pipeline.

The footer bottom sits at 1652. Its rule, the three pixels that separate contact data from the body, sits at y=1590. The document was 372 pixels taller than the box in the request.

Two files with the right size and the same missing block

chrome --headless=new --screenshot --window-size=1080,1350 produced a 1080 by 1350 PNG. Last row containing ink: y=1262. Everything from 1262 down is white, 87 pixels of it, which reads as bottom padding. The rule at 1590 is absent, the footer is absent.

The same page captured over CDP at exactly 1080 by 1350 produced another 1080 by 1350 PNG. Last row with ink: 1349, zero white. The footer is absent too, and this file carries no visual hint of loss, because the crop lands in the middle of a paragraph and the image looks full bleed.

Two files, identical dimensions, identical gap, different reasons to believe nothing is wrong. The first looks like a design with breathing room. The second looks finished.

The size check was comparing the request to itself

The verification read the PNG dimensions and compared them to the constant it had passed in. Both numbers came from the same place, so the check could only fail if the image library were broken. The layout assertions had a similar problem: they read getBoundingClientRect() from a 1722 pixel document and confirmed each box sat where the CSS said. They were right about the layout and silent about the file.

A check is worth something when its reference comes from outside the thing being checked. Here the reference had to be the document height, read after layout and before capture.

Capture at the height you measured

Two changes fixed it, and neither is about the tool.

First, read scrollHeight after the load and pass that number to the capture. Second, assert that the element you care about is inside the captured box, using its own coordinates:

altura = doc["doc"] assert doc["footer"] <= altura, f"footer fora da captura: {doc['footer']} > {altura}" 

With 1722 passed in, the full page came back: last row with ink 1650, 71 pixels of white under it, the 3px rule at 1590, contact line present. The same request through the CLI at 1722 also worked, which puts the bug in the number rather than in the binary.

That detail matters for the diagnosis. I had blamed the tool for months.

What CDP adds, and the two details that bite

Emulation.setDeviceMetricsOverride fixes the viewport at the exact size of the piece before navigation, so the page lays out against the box you intend instead of the default window. Page.captureScreenshot with captureBeyondViewport: true returns the whole document, including what sits under the fold.

ws_envia(s, {"id": 2, "method": "Emulation.setDeviceMetricsOverride", "params": {"width": w, "height": h, "deviceScaleFactor": 1, "mobile": False}}) ws_envia(s, {"id": 4, "method": "Page.captureScreenshot", "params": {"format": "png", "captureBeyondViewport": True}}) 

Because the capture can exceed the requested size, my script crops it back:

if (aw, ah) != (w, h): im.crop((0, 0, w, h)).save(png) print(f"recortado de {aw}x{ah} para {w}x{h}") 

Today that line printed recortado de 1080x1722 para 1080x1350. It is a failure message wearing an informational tone, and the crop it reports is exactly what removed the footer. A shrink deserves an exception, not a print.

The second detail is the debugging port. Two captures on the same port collide, because the previous Chrome has not released the socket yet (TIME_WAIT) and the second connection fails with an error that says nothing about ports. Deriving the port from the pid removed it: porta = 9401 + (os.getpid() % 400).

One more, measured twice today: --screenshot clips at the window height instead of stretching the image. My own script's docstring said it stretched. Rulers drawn at y=200, 600 and 1000 landed on those exact rows in both tools, and a ruler at 1800 did not appear at all in the CLI output, which is a crop and not a rescale. The fix worked while the explanation was wrong, and that is a reason to re-measure your own comments.

The shape of this bug

Every version of it looks the same: an artifact carries the coordinate system of the request, and the check reads coordinates from the request. Image dimension against requested dimension. Body length against expected length. Row count against the count in the config.

The cheapest defence is one independent reading per artifact, taken from the source that produced it: scrollHeight for a page, the element rectangle for a component, the actual byte length for a payload. It costs a line of code and it fails loudly, which is what the three green checks were missing.

Measured on Chromium 145.0.7632.0, headless=new, one machine, November 2026. The clipping behaviour of --screenshot has changed between versions, so pin your own numbers before quoting mine.

Se o seu pipeline também publica imagem sem conferir se o rodapé entrou no arquivo, comenta aqui embaixo o que a sua verificação mede. Aprendo mais com o caso dos outros do que com o meu, e respondo todo mundo.