refactor: cleanup

This commit is contained in:
zhom
2026-09-09 10:09:14 +04:00
parent 598d3bd513
commit dd42d46753
249 changed files with 67417 additions and 6659 deletions
+67 -14
View File
@@ -1,9 +1,59 @@
import assert from "node:assert/strict";
import http from "node:http";
export const ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecf";
function abortAfter(timeoutMs) {
return AbortSignal.timeout(timeoutMs);
/**
* One HTTP exchange with the driver, over `node:http` rather than `fetch`.
*
* `fetch` is undici, and undici gives every request a 300 s headers timeout
* of its own. A long `execute/async` sends no headers until the script
* completes, so a `download_browser` that pulls a 1 GB Wayfern build over a
* slow link died at 300 s whatever `timeoutMs` asked for. `node:http` has no
* such default, which leaves `timeoutMs` as the only clock.
*/
function exchange(method, url, body, timeoutMs) {
return new Promise((resolve, reject) => {
const payload = body === undefined ? undefined : JSON.stringify(body);
const request = http.request(
url,
{
method,
headers:
payload === undefined
? {}
: {
"content-type": "application/json",
"content-length": Buffer.byteLength(payload),
},
signal: AbortSignal.timeout(timeoutMs),
},
(response) => {
const chunks = [];
response.on("data", (chunk) => chunks.push(chunk));
response.on("error", reject);
response.on("end", () =>
resolve({
status: response.statusCode ?? 0,
text: Buffer.concat(chunks).toString("utf8"),
}),
);
},
);
request.on("error", (error) => {
const timedOut =
error?.name === "AbortError" || error?.name === "TimeoutError";
reject(
timedOut
? new Error(
`WebDriver ${method} ${url} gave no response within ${timeoutMs}ms`,
{ cause: error },
)
: error,
);
});
request.end(payload);
});
}
export class WebDriverClient {
@@ -12,30 +62,27 @@ export class WebDriverClient {
}
async request(method, pathname, body, timeoutMs = 330_000) {
const response = await fetch(`${this.baseUrl}${pathname}`, {
const { status, text } = await exchange(
method,
headers:
body === undefined ? undefined : { "content-type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body),
signal: abortAfter(timeoutMs),
});
const text = await response.text();
`${this.baseUrl}${pathname}`,
body,
timeoutMs,
);
let payload = null;
if (text) {
try {
payload = JSON.parse(text);
} catch {
throw new Error(
`WebDriver ${method} ${pathname} returned non-JSON HTTP ${response.status}: ${text.slice(0, 500)}`,
`WebDriver ${method} ${pathname} returned non-JSON HTTP ${status}: ${text.slice(0, 500)}`,
);
}
}
const error = payload?.value?.error;
if (!response.ok) {
const message =
payload?.value?.message ?? text ?? `HTTP ${response.status}`;
if (status < 200 || status >= 300) {
const message = payload?.value?.message ?? text ?? `HTTP ${status}`;
throw new Error(
`WebDriver ${method} ${pathname} failed (${error ?? response.status}): ${message}`,
`WebDriver ${method} ${pathname} failed (${error ?? status}): ${message}`,
);
}
return payload?.value;
@@ -51,11 +98,17 @@ export class WebDriverClient {
env = {},
cwd,
startupTimeout = 90_000,
headless = false,
}) {
const options = { application, args, env, startupTimeout };
if (cwd) {
options.cwd = cwd;
}
// Only sent when asked, so a driver build without the capability is not
// handed an option it would reject.
if (headless) {
options.headless = true;
}
const value = await this.request(
"POST",
"/session",