diff --git a/package.json b/package.json index f8617d6..bbbdc26 100644 --- a/package.json +++ b/package.json @@ -51,13 +51,13 @@ "@types/language-tags": "^1.0.4", "@types/node": "^24.0.0", "@types/xml2js": "^0.4.14", - "playwright-core": "<1.61.0", + "playwright-core": "<1.63.0", "rimraf": "^6.0.1", "typescript": "^5.8.3", "vitest": "^4.0.0" }, "peerDependencies": { - "playwright-core": "<1.61.0" + "playwright-core": "<1.63.0" }, "devEngines": { "packageManager": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8217af6..f50348a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,8 +70,8 @@ importers: specifier: ^0.4.14 version: 0.4.14 playwright-core: - specifier: <1.61.0 - version: 1.59.1 + specifier: <1.63.0 + version: 1.62.1 rimraf: specifier: ^6.0.1 version: 6.1.3 @@ -659,9 +659,9 @@ packages: resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} - playwright-core@1.59.1: - resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} - engines: {node: '>=18'} + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} hasBin: true postcss@8.5.28: @@ -1288,7 +1288,7 @@ snapshots: picomatch@4.0.7: {} - playwright-core@1.59.1: {} + playwright-core@1.62.1: {} postcss@8.5.28: dependencies: diff --git a/src/__version__.ts b/src/__version__.ts index 274a286..9fbbc57 100644 --- a/src/__version__.ts +++ b/src/__version__.ts @@ -9,10 +9,8 @@ export class CONSTRAINTS { static readonly MIN_VERSION: string = "alpha.1"; static readonly MAX_VERSION: string = "1"; - static asRange(): string { - /** - * Returns the version range as a string. - */ - return `>=${CONSTRAINTS.MIN_VERSION}, <${CONSTRAINTS.MAX_VERSION}`; - } + /** + * Minimum version with Playwright 1.61+, which sends viewport fields that older builds reject. + */ + static readonly PLAYWRIGHT_1_61_MIN_VERSION: string = "beta.30"; } diff --git a/src/pkgman.ts b/src/pkgman.ts index 8fdcedf..31caf8a 100644 --- a/src/pkgman.ts +++ b/src/pkgman.ts @@ -1,6 +1,7 @@ import { execFileSync } from "node:child_process"; import type { PathLike } from "node:fs"; import * as fs from "node:fs"; +import { createRequire } from "node:module"; import * as os from "node:os"; import * as path from "node:path"; import type { Writable } from "node:stream"; @@ -77,6 +78,18 @@ const LAUNCH_FILE: { [key: string]: string } = { lin: "camoufox-bin", }; +function isPlaywright161OrNewer(): boolean { + try { + const { version } = createRequire(import.meta.url)( + "playwright-core/package.json", + ); + const [major, minor] = version.split(".").map(Number); + return major > 1 || (major === 1 && minor >= 61); + } catch { + return false; + } +} + class Version { release: string; version?: string; @@ -117,7 +130,7 @@ class Version { } isSupported(): boolean { - return VERSION_MIN.lessThan(this) && this.lessThan(VERSION_MAX); + return !this.lessThan(VERSION_MIN) && this.lessThan(VERSION_MAX); } static fromPath(filePath: PathLike = INSTALL_DIR): Version { @@ -137,13 +150,18 @@ class Version { static buildMinMax(): [Version, Version] { return [ - new Version(CONSTRAINTS.MIN_VERSION), + new Version( + isPlaywright161OrNewer() + ? CONSTRAINTS.PLAYWRIGHT_1_61_MIN_VERSION + : CONSTRAINTS.MIN_VERSION, + ), new Version(CONSTRAINTS.MAX_VERSION), ]; } } const [VERSION_MIN, VERSION_MAX] = Version.buildMinMax(); +const SUPPORTED_RANGE = `>=${VERSION_MIN.release}, <${VERSION_MAX.release}`; export class GitHubDownloader { githubRepo: string; @@ -234,7 +252,7 @@ export class CamoufoxFetcher extends GitHubDownloader { missingAssetError(): void { throw new MissingRelease( - `No matching release found for ${OS_NAME} ${this.arch} in the supported range: (${CONSTRAINTS.asRange()}). Please update the library.`, + `No matching release found for ${OS_NAME} ${this.arch} in the supported range: (${SUPPORTED_RANGE}). Please update the library.`, ); } @@ -430,9 +448,9 @@ export function camoufoxPath(downloadIfMissing: boolean = true): PathLike { ) { return INSTALL_DIR; } else { - if (!downloadIfMissing) { - throw new UnsupportedVersion("Camoufox executable is outdated."); - } + throw new UnsupportedVersion( + `Camoufox executable is outdated (supported range: ${SUPPORTED_RANGE}). Please run \`camoufox fetch\` to update.`, + ); } // Install and recheck diff --git a/test/pkgman.test.ts b/test/pkgman.test.ts index 46376f3..7d4986b 100644 --- a/test/pkgman.test.ts +++ b/test/pkgman.test.ts @@ -199,3 +199,45 @@ describe("CamoufoxFetcher.install cleanup", () => { ).toEqual({ version: "1.0", release: "beta.1" }); }); }); + +describe("camoufoxPath browser floor", () => { + let installDir: string; + + beforeEach(() => { + installDir = fs.mkdtempSync(path.join(os.tmpdir(), "cfx-floortest-")); + vi.stubEnv("CAMOUFOX_INSTALL_DIR", installDir); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + vi.doUnmock("node:module"); + vi.resetModules(); + fs.rmSync(installDir, { recursive: true, force: true }); + }); + + async function loadCamoufoxPath(playwright: string, release: string) { + fs.writeFileSync( + path.join(installDir, "version.json"), + JSON.stringify({ version: "152.0.4", release }), + ); + vi.doMock("node:module", async (importOriginal) => ({ + ...(await importOriginal()), + createRequire: () => () => ({ version: playwright }), + })); + vi.resetModules(); + return (await import("../src/pkgman")).camoufoxPath; + } + + test.each([ + ["1.60.0", "beta.29"], + ["1.62.1", "beta.30"], + ])("accepts Playwright %s with %s", async (playwright, release) => { + const camoufoxPath = await loadCamoufoxPath(playwright, release); + expect(camoufoxPath(false)).toBe(installDir); + }); + + test("rejects builds below beta.30 from Playwright 1.61", async () => { + const camoufoxPath = await loadCamoufoxPath("1.61.0", "beta.29"); + expect(() => camoufoxPath(false)).toThrow(">=beta.30"); + }); +});