Skip to content

Commit fcdc458

Browse files
committed
Add arm64 Linux support to chrome-headless-shell via Playwright CDN
1 parent b56903b commit fcdc458

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

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

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import {
2020
detectCftPlatform,
2121
downloadAndExtractCft,
2222
fetchLatestCftRelease,
23+
fetchPlaywrightBrowsersJson,
2324
findCftExecutable,
25+
isPlaywrightCdnPlatform,
26+
playwrightCdnDownloadUrl,
2427
} from "./chrome-for-testing.ts";
2528

2629
const kVersionFileName = "version";
@@ -32,6 +35,18 @@ export function chromeHeadlessShellInstallDir(): string {
3235
return quartoDataDir("chrome-headless-shell");
3336
}
3437

38+
/**
39+
* The executable name for chrome-headless-shell on the current platform.
40+
* CfT builds use "chrome-headless-shell", Playwright arm64 builds use "headless_shell".
41+
*/
42+
export function chromeHeadlessShellBinaryName(): string {
43+
try {
44+
return isPlaywrightCdnPlatform() ? "headless_shell" : "chrome-headless-shell";
45+
} catch {
46+
return "chrome-headless-shell";
47+
}
48+
}
49+
3550
/**
3651
* Find the chrome-headless-shell executable in the install directory.
3752
* Returns the absolute path if installed, undefined otherwise.
@@ -41,7 +56,7 @@ export function chromeHeadlessShellExecutablePath(): string | undefined {
4156
if (!existsSync(dir)) {
4257
return undefined;
4358
}
44-
return findCftExecutable(dir, "chrome-headless-shell");
59+
return findCftExecutable(dir, chromeHeadlessShellBinaryName());
4560
}
4661

4762
/** Record the installed version as a plain text file. */
@@ -62,7 +77,7 @@ export function readInstalledVersion(dir: string): string | undefined {
6277
/** Check if chrome-headless-shell is installed in the given directory. */
6378
export function isInstalled(dir: string): boolean {
6479
return existsSync(join(dir, kVersionFileName)) &&
65-
findCftExecutable(dir, "chrome-headless-shell") !== undefined;
80+
findCftExecutable(dir, chromeHeadlessShellBinaryName()) !== undefined;
6681
}
6782

6883
// -- InstallableTool methods --
@@ -84,8 +99,22 @@ async function installedVersion(): Promise<string | undefined> {
8499
}
85100

86101
async function latestRelease(): Promise<RemotePackageInfo> {
102+
const platformInfo = detectCftPlatform();
103+
104+
if (isPlaywrightCdnPlatform(platformInfo)) {
105+
// arm64 Linux: use Playwright CDN
106+
const entry = await fetchPlaywrightBrowsersJson();
107+
const url = playwrightCdnDownloadUrl(entry.revision);
108+
return {
109+
url,
110+
version: entry.browserVersion,
111+
assets: [{ name: "chrome-headless-shell", url }],
112+
};
113+
}
114+
115+
// All other platforms: use CfT API
87116
const release = await fetchLatestCftRelease();
88-
const { platform } = detectCftPlatform();
117+
const { platform } = platformInfo;
89118

90119
const downloads = release.downloads["chrome-headless-shell"];
91120
if (!downloads) {
@@ -110,13 +139,15 @@ async function preparePackage(ctx: InstallContext): Promise<PackageInfo> {
110139
const release = await latestRelease();
111140
const workingDir = Deno.makeTempDirSync({ prefix: "quarto-chrome-hs-" });
112141

142+
const binaryName = chromeHeadlessShellBinaryName();
143+
113144
try {
114145
await downloadAndExtractCft(
115146
"Chrome Headless Shell",
116147
release.url,
117148
workingDir,
118149
ctx,
119-
"chrome-headless-shell",
150+
binaryName,
120151
);
121152
} catch (e) {
122153
safeRemoveSync(workingDir, { recursive: true });

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import { unitTest } from "../../test.ts";
88
import { assert, assertEquals } from "testing/asserts";
99
import { join } from "../../../src/deno_ral/path.ts";
1010
import { existsSync, safeRemoveSync } from "../../../src/deno_ral/fs.ts";
11-
import { isWindows } from "../../../src/deno_ral/platform.ts";
11+
import { arch, isWindows, os } from "../../../src/deno_ral/platform.ts";
1212
import { runningInCI } from "../../../src/core/ci-info.ts";
1313
import { InstallContext } from "../../../src/tools/types.ts";
1414
import { detectCftPlatform, findCftExecutable } from "../../../src/tools/impl/chrome-for-testing.ts";
1515
import { installableTool, installableTools } from "../../../src/tools/tools.ts";
1616
import {
17+
chromeHeadlessShellBinaryName,
1718
chromeHeadlessShellInstallable,
1819
chromeHeadlessShellInstallDir,
1920
chromeHeadlessShellExecutablePath,
@@ -257,3 +258,10 @@ unitTest("tool registry - installableTool looks up chrome-headless-shell", async
257258
assert(tool !== undefined, "installableTool should find chrome-headless-shell");
258259
assertEquals(tool.name, "Chrome Headless Shell");
259260
});
261+
262+
// -- arm64 support --
263+
264+
unitTest("chromeHeadlessShellBinaryName - returns chrome-headless-shell on non-arm64", async () => {
265+
if (os === "linux" && arch === "aarch64") return; // Skip on actual arm64
266+
assertEquals(chromeHeadlessShellBinaryName(), "chrome-headless-shell");
267+
});

0 commit comments

Comments
 (0)