Skip to content

Commit 8214573

Browse files
committed
Fix chrome-headless-shell binary name lookup on Linux arm64
The Playwright CDN arm64 mirror now redirects to the same chrome-for-testing-public bucket used for every other platform (see previous commit), so the archive layout changed too: it ships chrome-headless-shell-linux-arm64/chrome-headless-shell, not Playwright's older chrome-linux/headless_shell. With the previous arm64 special-case still in place, chromeHeadlessShellBinaryName() searched for a binary that no longer exists in the archive, so installation looked like it downloaded fine but never registered as installed. Verified against the real archive (downloaded via playwrightCdnDownloadUrl) rather than by exercising the arm64 code path directly, since isPlaywrightCdnPlatform() auto-detects the host platform and this bug is only reachable on arm64 hardware. Removed the now-inapplicable "Playwright arm64 layout" unit test fixture and stale comments describing the old chrome-linux/headless_shell layout.
1 parent 8290a75 commit 8214573

3 files changed

Lines changed: 41 additions & 33 deletions

File tree

‎src/tools/impl/chrome-headless-shell-paths.ts‎

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@
1111
import { join } from "../../deno_ral/path.ts";
1212
import { existsSync } from "../../deno_ral/fs.ts";
1313
import { quartoDataDir } from "../../core/appdirs.ts";
14-
import {
15-
findChromeExecutable,
16-
isPlaywrightCdnPlatform,
17-
} from "./chrome-for-testing.ts";
14+
import { findChromeExecutable } from "./chrome-for-testing.ts";
1815

1916
const kVersionFileName = "version";
2017

@@ -24,16 +21,13 @@ export function chromeHeadlessShellInstallDir(): string {
2421
}
2522

2623
/**
27-
* The executable name for chrome-headless-shell on the current platform.
28-
* CfT builds use "chrome-headless-shell", Playwright arm64 builds use "headless_shell".
29-
* Returns the CfT name if platform detection fails (unsupported platform).
24+
* The executable name for chrome-headless-shell.
25+
* The Playwright CDN arm64 mirror redirects to the same chrome-for-testing-public
26+
* bucket used for every other platform, so it shares the "chrome-headless-shell"
27+
* binary name too.
3028
*/
3129
export function chromeHeadlessShellBinaryName(): string {
32-
try {
33-
return isPlaywrightCdnPlatform() ? "headless_shell" : "chrome-headless-shell";
34-
} catch {
35-
return "chrome-headless-shell";
36-
}
30+
return "chrome-headless-shell";
3731
}
3832

3933
/**

‎tests/unit/tools/chrome-for-testing.test.ts‎

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -130,25 +130,6 @@ unitTest("findChromeExecutable - finds binary in nested structure", async () =>
130130
}
131131
});
132132

133-
// Step 3b: findChromeExecutable() — Playwright arm64 layout
134-
// Skip on Windows: arm64 layout is Linux-only, no .exe extension.
135-
unitTest("findChromeExecutable - finds binary in Playwright arm64 layout", async () => {
136-
if (isWindows) return; // arm64 layout is Linux-only
137-
const tempDir = Deno.makeTempDirSync();
138-
try {
139-
// Playwright arm64 extracts to chrome-linux/headless_shell
140-
const subdir = join(tempDir, "chrome-linux");
141-
Deno.mkdirSync(subdir);
142-
Deno.writeTextFileSync(join(subdir, "headless_shell"), "fake binary");
143-
144-
const found = findChromeExecutable(tempDir, "headless_shell");
145-
assert(found !== undefined, "should find headless_shell in chrome-linux/");
146-
assert(found!.endsWith("headless_shell"), `should end with headless_shell, got: ${found}`);
147-
} finally {
148-
safeRemoveSync(tempDir, { recursive: true });
149-
}
150-
});
151-
152133
// Playwright CDN tests
153134
// Skipped on CI: makes external HTTP calls to GitHub/Playwright CDN.
154135
// Same pattern as CfT API tests above — run locally to catch API contract changes.

‎tests/unit/tools/chrome-headless-shell.test.ts‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { runningInCI } from "../../../src/core/ci-info.ts";
1313
import { InstallContext } from "../../../src/tools/types.ts";
1414
import {
1515
detectChromePlatform,
16+
downloadAndExtractChrome,
1617
fetchPlaywrightBrowsersJson,
1718
findChromeExecutable,
1819
isPlaywrightCdnPlatform,
@@ -100,8 +101,7 @@ unitTest("isInstalled - returns false when only binary exists (no version file)"
100101
try {
101102
const { platform } = detectChromePlatform();
102103
const binName = chromeHeadlessShellBinaryName();
103-
// CfT layout: chrome-headless-shell-{platform}/binary
104-
// Playwright arm64 layout: chrome-linux/binary (found via walkSync fallback)
104+
// CfT layout (all platforms, including Playwright CDN arm64): chrome-headless-shell-{platform}/binary
105105
const subdir = join(tempDir, `chrome-headless-shell-${platform}`);
106106
Deno.mkdirSync(subdir);
107107
const target = isWindows ? `${binName}.exe` : binName;
@@ -169,6 +169,39 @@ unitTest("Playwright CDN - browsers.json and URL construction", async () => {
169169
);
170170
}, { ignore: runningInCI() });
171171

172+
// The Playwright CDN arm64 archive redirects to the same chrome-for-testing-public
173+
// bucket used for every other platform, so it shares that layout: a
174+
// chrome-headless-shell-linux-arm64/chrome-headless-shell binary, not Playwright's
175+
// older chrome-linux/headless_shell layout. This downloads the real archive to
176+
// guard against CfT (or Playwright's mirror of it) changing that layout again.
177+
// Checked with existsSync against the literal extracted path rather than
178+
// findChromeExecutable(), which appends ".exe" whenever the *host* is Windows —
179+
// irrelevant here since the archive itself is always a Linux arm64 build,
180+
// regardless of what platform runs this test.
181+
unitTest("Playwright CDN arm64 archive uses chrome-headless-shell binary name, not headless_shell", async () => {
182+
const entry = await fetchPlaywrightBrowsersJson();
183+
const url = playwrightCdnDownloadUrl(entry.browserVersion);
184+
const tempDir = Deno.makeTempDirSync();
185+
try {
186+
await downloadAndExtractChrome(
187+
"Chrome Headless Shell (arm64)",
188+
url,
189+
tempDir,
190+
createMockContext(tempDir),
191+
);
192+
assert(
193+
existsSync(join(tempDir, "chrome-headless-shell-linux-arm64", "chrome-headless-shell")),
194+
"arm64 archive should contain chrome-headless-shell-linux-arm64/chrome-headless-shell",
195+
);
196+
assert(
197+
!existsSync(join(tempDir, "chrome-linux", "headless_shell")),
198+
"arm64 archive should not use Playwright's old chrome-linux/headless_shell layout",
199+
);
200+
} finally {
201+
safeRemoveSync(tempDir, { recursive: true });
202+
}
203+
}, { ignore: runningInCI() });
204+
172205
// -- Step 5: preparePackage() (downloads ~50MB, skip on CI) --
173206

174207
function createMockContext(workingDir: string): InstallContext {

0 commit comments

Comments
 (0)