Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/tests/unit/tools/chrome-headless-shell.test.ts
12925 views
1
/*
2
* chrome-headless-shell.test.ts
3
*
4
* Copyright (C) 2026 Posit Software, PBC
5
*/
6
7
import { unitTest } from "../../test.ts";
8
import { assert, assertEquals } from "testing/asserts";
9
import { join } from "../../../src/deno_ral/path.ts";
10
import { existsSync, safeRemoveSync } from "../../../src/deno_ral/fs.ts";
11
import { isWindows } from "../../../src/deno_ral/platform.ts";
12
import { runningInCI } from "../../../src/core/ci-info.ts";
13
import { InstallContext } from "../../../src/tools/types.ts";
14
import { detectCftPlatform, findCftExecutable } from "../../../src/tools/impl/chrome-for-testing.ts";
15
import { installableTool, installableTools } from "../../../src/tools/tools.ts";
16
import {
17
chromeHeadlessShellInstallable,
18
chromeHeadlessShellInstallDir,
19
chromeHeadlessShellExecutablePath,
20
isInstalled,
21
noteInstalledVersion,
22
readInstalledVersion,
23
} from "../../../src/tools/impl/chrome-headless-shell.ts";
24
25
// -- Step 1: Install directory + executable path --
26
27
unitTest("chromeHeadlessShellInstallDir - path ends with chrome-headless-shell", async () => {
28
const dir = chromeHeadlessShellInstallDir();
29
assert(
30
dir.replace(/\\/g, "/").endsWith("chrome-headless-shell"),
31
`Expected path ending with chrome-headless-shell, got: ${dir}`,
32
);
33
});
34
35
unitTest("chromeHeadlessShellExecutablePath - returns undefined when not installed", async () => {
36
// If chrome-headless-shell happens to be installed, this test is still valid:
37
// it should return either a valid path or undefined, never throw.
38
const result = chromeHeadlessShellExecutablePath();
39
if (result !== undefined) {
40
assert(
41
result.includes("chrome-headless-shell"),
42
`Expected path containing chrome-headless-shell, got: ${result}`,
43
);
44
}
45
// No assertion failure means the function works correctly either way
46
});
47
48
// -- Step 2: Version helpers --
49
50
unitTest("version - round-trip write and read", async () => {
51
const tempDir = Deno.makeTempDirSync();
52
try {
53
noteInstalledVersion(tempDir, "145.0.7632.46");
54
const read = readInstalledVersion(tempDir);
55
assertEquals(read, "145.0.7632.46");
56
} finally {
57
safeRemoveSync(tempDir, { recursive: true });
58
}
59
});
60
61
unitTest("version - returns undefined for empty dir", async () => {
62
const tempDir = Deno.makeTempDirSync();
63
try {
64
assertEquals(readInstalledVersion(tempDir), undefined);
65
} finally {
66
safeRemoveSync(tempDir, { recursive: true });
67
}
68
});
69
70
// -- Step 3: isInstalled() --
71
72
unitTest("isInstalled - returns false when directory is empty", async () => {
73
const tempDir = Deno.makeTempDirSync();
74
try {
75
assertEquals(isInstalled(tempDir), false);
76
} finally {
77
safeRemoveSync(tempDir, { recursive: true });
78
}
79
});
80
81
unitTest("isInstalled - returns false when only version file exists", async () => {
82
const tempDir = Deno.makeTempDirSync();
83
try {
84
noteInstalledVersion(tempDir, "145.0.0.0");
85
assertEquals(isInstalled(tempDir), false);
86
} finally {
87
safeRemoveSync(tempDir, { recursive: true });
88
}
89
});
90
91
unitTest("isInstalled - returns false when only binary exists (no version file)", async () => {
92
const tempDir = Deno.makeTempDirSync();
93
try {
94
const { platform } = detectCftPlatform();
95
const subdir = join(tempDir, `chrome-headless-shell-${platform}`);
96
Deno.mkdirSync(subdir);
97
const binaryName = isWindows ? "chrome-headless-shell.exe" : "chrome-headless-shell";
98
Deno.writeTextFileSync(join(subdir, binaryName), "fake");
99
assertEquals(isInstalled(tempDir), false);
100
} finally {
101
safeRemoveSync(tempDir, { recursive: true });
102
}
103
});
104
105
unitTest("isInstalled - returns true when version file and binary exist", async () => {
106
const tempDir = Deno.makeTempDirSync();
107
try {
108
noteInstalledVersion(tempDir, "145.0.0.0");
109
const { platform } = detectCftPlatform();
110
const subdir = join(tempDir, `chrome-headless-shell-${platform}`);
111
Deno.mkdirSync(subdir);
112
const binaryName = isWindows ? "chrome-headless-shell.exe" : "chrome-headless-shell";
113
Deno.writeTextFileSync(join(subdir, binaryName), "fake");
114
115
assertEquals(isInstalled(tempDir), true);
116
} finally {
117
safeRemoveSync(tempDir, { recursive: true });
118
}
119
});
120
121
// -- Step 4: latestRelease() (external HTTP call, skip on CI) --
122
123
unitTest("latestRelease - returns valid RemotePackageInfo", async () => {
124
const release = await chromeHeadlessShellInstallable.latestRelease();
125
assert(release.version, "version should be non-empty");
126
assert(
127
/^\d+\.\d+\.\d+\.\d+$/.test(release.version),
128
`version format wrong: ${release.version}`,
129
);
130
assert(release.url.startsWith("https://"), `URL should be https: ${release.url}`);
131
assert(release.url.includes(release.version), "URL should contain version");
132
assert(release.assets.length > 0, "should have at least one asset");
133
assertEquals(release.assets[0].name, "chrome-headless-shell");
134
}, { ignore: runningInCI() });
135
136
// -- Step 5: preparePackage() (downloads ~50MB, skip on CI) --
137
138
function createMockContext(workingDir: string): InstallContext {
139
return {
140
workingDir,
141
info: (_msg: string) => {},
142
withSpinner: async (_options, op) => {
143
await op();
144
},
145
error: (_msg: string) => {},
146
confirm: async (_msg: string) => true,
147
download: async (_name: string, url: string, target: string) => {
148
const resp = await fetch(url);
149
if (!resp.ok) throw new Error(`Download failed: ${resp.status}`);
150
const data = new Uint8Array(await resp.arrayBuffer());
151
Deno.writeFileSync(target, data);
152
},
153
props: {},
154
flags: {},
155
};
156
}
157
158
unitTest("preparePackage - downloads and extracts chrome-headless-shell", async () => {
159
const tempDir = Deno.makeTempDirSync();
160
const ctx = createMockContext(tempDir);
161
const pkg = await chromeHeadlessShellInstallable.preparePackage(ctx);
162
try {
163
assert(pkg.version, "version should be non-empty");
164
assert(pkg.filePath, "filePath should be non-empty");
165
const binary = findCftExecutable(pkg.filePath, "chrome-headless-shell");
166
assert(binary !== undefined, "binary should exist in extracted dir");
167
} finally {
168
safeRemoveSync(pkg.filePath, { recursive: true });
169
safeRemoveSync(tempDir, { recursive: true });
170
}
171
}, { ignore: runningInCI() });
172
173
// -- Step 6: afterInstall --
174
175
unitTest("afterInstall - returns false", async () => {
176
const tempDir = Deno.makeTempDirSync();
177
const ctx = createMockContext(tempDir);
178
try {
179
const result = await chromeHeadlessShellInstallable.afterInstall(ctx);
180
assertEquals(result, false);
181
} finally {
182
safeRemoveSync(tempDir, { recursive: true });
183
}
184
});
185
186
// -- Step 7: chromeHeadlessShellInstallable export --
187
188
unitTest("chromeHeadlessShellInstallable - has correct name and methods", async () => {
189
assertEquals(chromeHeadlessShellInstallable.name, "Chrome Headless Shell");
190
assertEquals(chromeHeadlessShellInstallable.prereqs.length, 0);
191
assert(typeof chromeHeadlessShellInstallable.installed === "function");
192
assert(typeof chromeHeadlessShellInstallable.installDir === "function");
193
assert(typeof chromeHeadlessShellInstallable.installedVersion === "function");
194
assert(typeof chromeHeadlessShellInstallable.latestRelease === "function");
195
assert(typeof chromeHeadlessShellInstallable.preparePackage === "function");
196
assert(typeof chromeHeadlessShellInstallable.install === "function");
197
assert(typeof chromeHeadlessShellInstallable.afterInstall === "function");
198
assert(typeof chromeHeadlessShellInstallable.uninstall === "function");
199
});
200
201
// -- Integration: full install/uninstall lifecycle --
202
203
unitTest("install lifecycle - prepare, install, verify, uninstall", async () => {
204
const tool = chromeHeadlessShellInstallable;
205
const tempDir = Deno.makeTempDirSync();
206
const ctx = createMockContext(tempDir);
207
208
// Prepare (download + extract)
209
const pkg = await tool.preparePackage(ctx);
210
211
try {
212
// Install into real quartoDataDir
213
await tool.install(pkg, ctx);
214
215
// Verify installed state
216
assertEquals(await tool.installed(), true);
217
218
const version = await tool.installedVersion();
219
assert(version, "installedVersion should return a version string");
220
assert(/^\d+\.\d+\.\d+\.\d+$/.test(version!), `version format: ${version}`);
221
222
const exePath = chromeHeadlessShellExecutablePath();
223
assert(exePath !== undefined, "executable path should be defined after install");
224
assert(existsSync(exePath!), `executable should exist at: ${exePath}`);
225
226
const dir = await tool.installDir();
227
assert(dir !== undefined, "installDir should return a path when installed");
228
229
// Uninstall
230
await tool.uninstall(ctx);
231
232
// Verify uninstalled state
233
assertEquals(await tool.installed(), false);
234
assertEquals(chromeHeadlessShellExecutablePath(), undefined);
235
} finally {
236
// Safety net: ensure uninstall happened even if assertions failed
237
if (await tool.installed()) {
238
await tool.uninstall(ctx);
239
}
240
safeRemoveSync(pkg.filePath, { recursive: true });
241
safeRemoveSync(tempDir, { recursive: true });
242
}
243
}, { ignore: runningInCI() });
244
245
// -- Step 8: Tool registry integration --
246
247
unitTest("tool registry - chrome-headless-shell is listed in installableTools", async () => {
248
const tools = installableTools();
249
assert(
250
tools.includes("chrome-headless-shell"),
251
`installableTools() should include "chrome-headless-shell", got: ${tools}`,
252
);
253
});
254
255
unitTest("tool registry - installableTool looks up chrome-headless-shell", async () => {
256
const tool = installableTool("chrome-headless-shell");
257
assert(tool !== undefined, "installableTool should find chrome-headless-shell");
258
assertEquals(tool.name, "Chrome Headless Shell");
259
});
260
261