My unit tests were green. My integration tests were green. The feature was broken in a way that produced no error, no warning, and no visual clue that anything was wrong.
The feature was a CSS difference blend mode, used to compare a design mockup against a live page: matching pixels turn black, so only the differences light up. Mine turned nothing black. It looked exactly like normal mode, and I lost about a week to it.
The cause was one line of CSS, and the reason no test caught it is the interesting part.
1. The blend mode that silently did nothing
Here is the overlay, simplified. A closed shadow root holds an image positioned over the page:
#overlay-host {
position: absolute;
z-index: 2147483647; /* stay above the page */
}
.overlay-image {
mix-blend-mode: difference;
}
That looks fine. It is not.
mix-blend-mode blends an element with its backdrop — but only within its own stacking context. And z-index on a positioned element creates a stacking context. So the host became a sealed box, and the image blended against the empty space inside that box instead of against the page underneath.
The result isn't an error. It isn't even visibly wrong unless you know what difference should look like. The image just renders normally, as though you never set the property.
The same applies to opacity below 1, filter, transform, isolation, backdrop-filter, mask, contain: paint and will-change of any of those. Any one of them on an ancestor silently disables blending. On a page you don't control — which is the entire premise of a browser extension — any of them might arrive from the site's own CSS.
The fix was to stop creating a stacking context on the host and move z-index onto the children that actually need it:
#overlay-host {
position: absolute;
/* No z-index, transform, opacity, filter, isolation or contain here:
any of them creates a stacking context and silently kills blending. */
z-index: auto;
}
.overlay-image {
z-index: 2147483646;
mix-blend-mode: difference;
}
Plus a MutationObserver re-pinning those properties inline with !important, because hostile page CSS will try to set them back.
Why no test caught it: every assertion I had was of the form getComputedStyle(img).mixBlendMode === 'difference'. That was true the entire time. The property was set. It was applied. It just had nothing to blend with. Computed style tells you what the engine parsed, not what the compositor drew.
The test that caught it samples actual pixels:
// Red block under a red overlay in difference mode must composite to black.
const px = await pixelAt(screenshot, 120, 160);
assert(px[0] < 12 && px[1] < 12 && px[2] < 12,
`expected black, got rgb(${px})`);
That one assertion would have saved me the week.
2. The licence that never expired
Different flavour of invisible. This one lied to the user's face.
The extension has a paid tier backed by licence keys. Validation looked like this:
async function validateLicense(key, instanceId) {
const res = await fetch(VALIDATE_ENDPOINT, { /* ... */ });
const payload = await res.json();
if (res.ok && isValid(payload)) {
await storage.set({ isPro: true, /* ... */ });
return { ok: true, isPro: true };
}
return { ok: false, error: messageFrom(payload) }; // ← writes nothing
}
Spot it? The success path writes to storage. The failure path returns an error object and touches nothing.
So when a subscription lapsed and the key was disabled, pressing Re-check in the UI did this:
- called the API
- got back
valid: false, status: "disabled" - displayed "This license key is disabled." to the user
- left
isPro: truein storage
The customer read an accurate error message while keeping every paid feature. The only thing that ever revoked access was a once-per-24-hours background revalidation, so a cancelled subscriber kept Pro until their browser happened to check again.
Why no test caught it: my test suite faked the paid tier by writing isPro: true straight into storage. Every test of a Pro feature started from "pretend they're Pro". Nothing ever ran the activation path. I had thorough tests of what Pro users could do and zero tests of how someone becomes — or stops being — a Pro user.
Finding it meant buying my own product. I made a real subscription purchase in the payment provider's test mode, activated the key, then disabled it from the dashboard and watched what the extension actually did. The fix:
/* A definite "no" about the key this browser is running on: revoke now.
Only ever on a real answer from the API - offline and unconfigured results
say nothing about the key and must never downgrade anyone. The key must
match the stored one too, so typing someone else's dead key into the box
cannot strip a paying customer of Pro. */
await revokeIfCurrentKey(key, status || 'invalid');
return { ok: false, error: messageFrom(payload) };
Those three guards are the whole problem in miniature. "Revoke on failure" is a one-line change that would also have logged out every customer whose wifi dropped.
3. The SVG that rendered at 240×150
A user drops in an SVG mockup. It renders at roughly a quarter of its real size.
SVGs exported without width and height attributes — just a viewBox — have no intrinsic size. Load one into an <img> and ask for naturalWidth, and you get a placeholder. Here is Chrome 152, measured:
| SVG |
naturalWidth × naturalHeight
|
|---|---|
width="480" height="320" |
480 × 320 |
viewBox="0 0 640 400" only |
240 × 150 |
| no dimensions, no viewBox | 300 × 150 |
That 240 is the giveaway. CSS says a replaced element with no intrinsic size falls back to a default object size of 300×150 — but when there's a viewBox, there is an intrinsic ratio, so the browser keeps the 150 height and derives the width from it: 150 × (640/400) = 240.
Which means the wrong size scales with your design's aspect ratio. It isn't a constant you'd notice as obviously bogus. It's a plausible-looking number that happens to be a quarter of the truth, and it changes per file. That's why it read as "my positioning maths is off" rather than "the dimensions are wrong".
The fix is to stop asking the <img> and read the file:
function measureSvg(text) {
const svg = new DOMParser()
.parseFromString(text, 'image/svg+xml').documentElement;
if (!svg || svg.nodeName !== 'svg' || svg.querySelector('parsererror')) return null;
const plain = (v) => {
const m = /^\s*([\d.]+)\s*(px)?\s*$/.exec(v || '');
const n = m ? parseFloat(m[1]) : NaN;
return Number.isFinite(n) && n > 0 ? n : null;
};
const w = plain(svg.getAttribute('width'));
const h = plain(svg.getAttribute('height'));
if (w && h) return { width: Math.round(w), height: Math.round(h) };
const box = (svg.getAttribute('viewBox') || '').trim().split(/[\s,]+/).map(Number);
if (box.length === 4 && box[2] > 0 && box[3] > 0) {
return { width: Math.round(box[2]), height: Math.round(box[3]) };
}
return null;
}
Note plain() rejects width="100%" and width="10em" — percentage and relative units aren't a pixel size, and treating them as one is the same bug with extra steps.
The pattern
All three bugs share a shape: the code did exactly what it said, and the result was still wrong.
- The blend mode was applied — to the wrong backdrop.
- The licence check returned the correct answer — and discarded it.
- The image loaded successfully — at a size nobody asked for.
Not one threw. Not one logged. Every assertion I had about state passed, because the state was right. The outcome was wrong, and outcomes live in rendered pixels, in persisted storage after a round trip, and in what a real file actually contains.
So the harness samples all three. It drives a real Chrome over the DevTools Protocol — no npm packages, just Node's WebSocket client speaking CDP — loads a deliberately hostile test page that tries to break the overlay with !important rules, and then reads pixels back out of real screenshots:
const chrome = spawn(chromePath, [
`--load-extension=${EXT_DIR}`,
`--remote-debugging-port=${PORT}`,
'--enable-unsafe-extension-debugging',
'--force-device-scale-factor=1',
]);
const { data } = await cdp.send('Page.captureScreenshot', { format: 'png' }, session);
The hostile page is worth the effort on its own. It sets display: none !important on the overlay host, plus z-index, isolation and transform — the exact properties that killed the blend mode. Writing the attack into the fixture means the regression can't come back quietly.
187 tests now, across four suites. The ones that earn their keep aren't the ones asserting that a function returns what it returns. They're the handful that look at what actually came out the other end.
These came out of building PixelMatch, a Chrome extension that overlays your design on the live page and scores how closely the build matches. Free to install.
Top comments (0)