Chrome 137 quietly removed --load-extension support from all branded Chrome builds. For most people that's invisible. For us it was a hard break: our GPU recording path needs a MV3 zoom extension loaded via that flag, and it also needs proprietary codec support (H.264, AAC, HEVC) for sites with embedded video. Playwright's bundled Chromium keeps --load-extension working, but its FFmpeg is statically compiled without the proprietary codecs — and that's not a swappable .so, it's welded in. We were stuck between a browser that could zoom but not play video, and one that could play video but not zoom.
Chrome for Testing (CfT) is built with proprietary_codecs=true and ships its own libffmpeg.so. More importantly, it sets is_chrome_for_testing=true, which is mutually exclusive with is_chrome_branded=true in the Chromium source — so the branding guard that blocks --load-extension simply never fires. CfT version 139.0.7258.154 shares the same major.minor.build as Playwright 1.54.0's bundled Chromium, so CDP stays compatible. The cost: a mandatory 53px "Chrome is being controlled by automated software" banner that stacks on top of the existing 87px browser chrome — 140px total vertical offset before page content begins. The 87px figure itself includes a deliberate 2px sub-pixel buffer to absorb rounding artifacts at 2× device scale. CfT has no auto-update mechanism, so the version is pinned in the Dockerfile and must be bumped manually when Playwright is upgraded.
I dropped the implementation notes into NotebookLM. Here's what came back.
NotebookLM Audio Overview — Migrating Video Recording to Chrome for Testing
Transcript
Speaker 1: So we're unpacking a stack of engineering notes updated today, March 18, 2026. And the mission for this deep dive is to look at a back-end migration from standard Google Chrome to Chrome for Testing —
Speaker 2: or CfT, I guess.
Speaker 1: Right. CfT. It's basically how they solved this seemingly impossible software conflict in automated web recording. And for you listening, it's kind of like you have this Swiss Army knife, right? Imagine you're camping and you really need to open a bottle of wine, but you also need to cut some rope —
Speaker 1: but suddenly the knife has this weird new rule where if you pull out the corkscrew, the blade just physically locks shut. Like you literally cannot use both at the same time.
Speaker 2: Which is exactly what happened to the developer's GPU recording path. They hit this massive lockout because they needed two browser capabilities to run at the exact same time.
Speaker 1: Right — playing embedded video and zooming the page.
Speaker 2: Exactly. So playing video needs proprietary codecs like H.264 and AAC. But zooming the page needs an MV3 extension loaded via a specific command line flag — the --load-extension flag.
Speaker 1: But standard Google Chrome version 137 and newer completely disabled that extension flag for their official branded builds, right?
Speaker 2: Yeah, that just instantly kills the zoom functionality. So, naturally, the fallback is to use the unbranded open-source Chromium browser that comes bundled with Playwright, which is their automation tool. But wait — the open-source version lacks those proprietary video codecs. So they're just stuck.
Speaker 1: Totally stuck. They were caught between a browser that zooms but won't play video, and one that plays video but won't zoom.
Speaker 2: I mean, wait — why couldn't they just force the open-source version to play the video? Like, just drop the right FFmpeg library file into the directory and swap it out?
Speaker 1: Well, you'd think so. But the open-source FFmpeg engine in that specific Chromium build is actually statically compiled without those codecs.
Speaker 2: Oh, wow. So it's just hardcoded in there?
Speaker 1: Yeah, it's welded into the frame of the application. So a simple library swap just doesn't work. You need an entirely different binary to let both features coexist.
Speaker 2: Okay, let's unpack this. Enter Chrome for Testing, or CfT.
Speaker 1: Right. And it solves the video issue right out of the gate because it ships with proprietary_codecs=true and has its own libffmpeg.so file.
Speaker 2: Nice. And it also flips that hard-coded switch — is_chrome_for_testing=true — which is huge because of how the Chromium source code is written.
Speaker 1: Exactly. That state is mutually exclusive with is_chrome_branded=true.
Speaker 2: It's basically a VIP backstage pass, right? It lets the developers walk right past Chrome's security bouncers so they can load the zoom extension.
Speaker 1: It really is an elegant fix to a horrible dependency nightmare. And it perfectly matches the CDP protocol version they needed for Playwright 1.54.0 — version 139.0.7258.
Speaker 2: Okay, so fixing the engine is a massive win. But swapping engines naturally changes the physical dimensions of the car. Adopting a specialized testing browser brings in a literal, physical UI problem on the screen.
Speaker 1: Yeah, it forces this mandatory 53-pixel warning banner — the one that declares "Chrome is being controlled by automated software."
Speaker 2: Oh man, that completely wrecked the visual recording coordinates.
Speaker 1: It really does. Because if you're capturing a specific viewport, the browser chrome at the top usually takes up 87 pixels.
Speaker 2: But now you add a 53-pixel banner on top of that. So suddenly you're dealing with this hyper-specific 140-pixel vertical offset before the actual web page content even begins.
Speaker 1: And the developer's notes map out that exact 140-pixel offset. But here is the wild part — that 87-pixel browser chrome measurement actually includes a deliberate 2-pixel buffer.
Speaker 2: Wait, a 2-pixel buffer just for rounding errors?
Speaker 1: Yeah. To absorb sub-pixel rounding errors. When you scale down a web page for recording on high-density displays, the math often gives you fractions of a pixel.
Speaker 2: And the rendering engine obviously can't draw half a pixel, so it just drops it.
Speaker 1: Exactly. Leaving this annoying one or two pixel white gap at the bottom of an 800-pixel-high viewport.
Speaker 2: Debugging a single white line of pixels sounds like an absolute nightmare. Adding exactly 2 pixels to the top offset to force the math to round correctly is just brilliant duct-tape engineering.
Speaker 1: It really is. And because CfT is purely for testing environments, it doesn't auto-update. So the developers have to manually pin versions to ensure that 140-pixel offset — and everything else — stays totally stable.
Speaker 2: Well, for you listening, every seamless screen recorder or automated web tool you use relies heavily on this exact kind of invisible scaffolding.
Speaker 1: Yeah. You're never really meant to see the precise sub-pixel math or the specialized browser builds running under the hood.
Speaker 2: It's just duct tape and math all the way down.
Speaker 1: Right. And it kind of leaves you with a big question to think about. If standard browsers keep locking down features like extensions for security — like Chrome Stable 137 did — are we heading toward a future where the internet is split entirely into browsers for humans and browsers for bots?
Speaker 2: It's definitely a fascinating split to watch unfold.
Speaker 1: Yeah. Next time your software restricts what tools you can use at the same time, just remember — you're not the only one trying to use the corkscrew and the blade at once. Sometimes you just have to go find a completely different knife.
Research Notes
Chrome for Testing (CfT) — Research & Implementation Notes
Last updated: 2026-03-18
Status: IMPLEMENTED — migration from google-chrome-stable to Chrome for Testing is complete.
Problem Statement
The GPU recording path requires two capabilities from the browser binary:
- Proprietary codec support (H.264, AAC, HEVC) — for sites with embedded video
--load-extensionsupport — for the MV3 zoom extension (zoomLevel ≠ 1.0)
No single previously-used binary satisfied both:
| Binary | Proprietary codecs | --load-extension | Notes |
|---|---|---|---|
google-chrome-stable | ✅ | ❌ | Chrome 137+ removed --load-extension for branded builds |
| Playwright bundled Chromium | ❌ | ✅ | FFmpeg statically compiled without proprietary codecs; libffmpeg.so swap not viable |
| Chrome for Testing (CfT) | ✅ | ✅ | See below |
Why Chrome for Testing Satisfies Both Requirements
Proprietary codecs
CfT is built with proprietary_codecs=true. Confirmed via binary strings inspection — H.264, AAC, HEVC decoder symbols present in the binary.
Runtime codec detection: There is no programmatic runtime check for browser codec support. We rely on CfT's build flags. The FFmpeg output encoder (hevc_nvenc on GPU, libx264 on CPU) is independent — that is for the recording container output, not browser playback codecs. H.264 and AAC playback confirmed working via recording a page with embedded video (no blank/broken frames).
--load-extension not blocked
The --load-extension guard in Chromium source is:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// block --load-extension
#endifCfT sets is_chrome_for_testing=true, which is mutually exclusive with is_chrome_branded=true. The branding guard does not fire. --load-extension works.
CDP compatibility with Playwright 1.54.0
- CfT version
139.0.7258.154—major.minor.build = 139.0.7258 - Playwright 1.54.0 bundled Chromium —
139.0.7258.5— samemajor.minor.build - CDP protocol is versioned at the
major.minor.buildlevel; patch divergence is safe
Implemented Changes
docker/gpu/Dockerfile
Replaced google-chrome-stable apt install with CfT zip download:
# Chrome for Testing — proprietary codecs (H.264/AAC/HEVC) + --load-extension support
# Version matches Playwright 1.54.0 Chromium (139.0.7258.x) for CDP compatibility
ARG CfT_VERSION=139.0.7258.154
RUN wget -q "https://storage.googleapis.com/chrome-for-testing-public/${CfT_VERSION}/linux64/chrome-linux64.zip" \
-O /tmp/cft.zip \
&& unzip -q /tmp/cft.zip -d /opt \
&& mv /opt/chrome-linux64 /opt/chrome-for-testing \
&& rm /tmp/cft.zip \
&& chmod +x /opt/chrome-for-testing/chromeExisting Chrome shared-library apt deps stay — CfT needs them too. CfT ships its own libffmpeg.so inside the zip; no LD_LIBRARY_PATH override required.
src/WebRecorder.ts
possibleChromePaths — CfT first, existing entries as fallback:
const possibleChromePaths = [
"/opt/chrome-for-testing/chrome", // Chrome for Testing: codecs + --load-extension
"/usr/bin/google-chrome-stable", // Debian/Ubuntu package (fallback)
"/usr/bin/google-chrome", // Alternative location (fallback)
"/usr/bin/chromium-browser", // Chromium alternative (fallback)
"/usr/bin/chromium", // Another Chromium location (fallback)
];executablePath — unconditional (no longer zoom-conditional):
// Chrome for Testing: proprietary codecs (H.264/AAC/HEVC) + --load-extension support
executablePath: chromePath,Previously hasZoom ? undefined : chromePath — undefined fell back to Playwright's bundled Chromium for zoom recordings. CfT supports both paths, so the conditional was removed.
ignoreDefaultArgs — remains zoom-conditional (runtime flag conflict, not a browser-capability issue):
ignoreDefaultArgs: hasZoom
? ["--enable-automation", "--disable-extensions"]
: ["--enable-automation"],Playwright injects --disable-extensions by default; when --load-extension is active, that must be suppressed.
UI Height Constants
These values are in src/config/types.ts as DEFAULT_RECORDING_PARAMS and used in src/WebRecorder.ts:188-190.
| Constant | Default | Description |
|---|---|---|
chromeUiOffset | 87px | Chrome window chrome (address bar + tabs). 87 not 85 — extra 2px absorbs sub-pixel rounding at 2x device scale factor, which caused a 1-2px white line on 800px-height viewports. |
chromeForTestingBanner | 53px | The "Chrome is being controlled by automated software" / CfT info banner shown below the address bar. Added on top of chromeUiOffset. Set to 0 if using a standard Chrome build without the banner. |
| Total offset | 140px | chromeUiOffset + chromeForTestingBanner — full vertical space consumed by browser UI before the page content begins. |
Both are per-request overridable via recording params.
Resolved Questions
--no-sandbox— already present insrc/utils/chrome-flags.ts:4("--no-sandbox" // Required in containers). No new flags needed for CfT vs Chrome stable.- CfT auto-update — CfT has no auto-update mechanism. Version is pinned via
ARG CfT_VERSIONin the Dockerfile. When Playwright is upgraded,CfT_VERSIONmust be manually bumped to match the newmajor.minor.build. libffmpeg.so— CfT ships its own alongside the binary in the zip. Container is functioning correctly; noLD_LIBRARY_PATHoverride needed.
Verification Steps
- Build GPU Docker image:
task build-docker→ select GPU - Shell into container:
/opt/chrome-for-testing/chrome --version— confirm139.0.7258.154 - Test recording with
zoomLevel: 1.5on a page with H.264 video:- Zoom extension loads (CDP target inspection log shows
service_workerwithchrome-extension://URL) - Video plays (no blank/broken frames in output)
- Zoom extension loads (CDP target inspection log shows
- Test recording with
zoomLevel: 1.0on a page with H.264 video — confirm video plays
Steps 3 & 4 verified — H.264 and AAC playback confirmed working.