Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
12 changes: 6 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions src/__version__.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ export class CONSTRAINTS {
* The minimum and maximum supported versions of the Camoufox browser.
*/
static readonly MIN_VERSION: string = "alpha.1";
static readonly MAX_VERSION: string = "1";
/**
* Exclusive. The Firefox 156 builds (beta.32+) dropped properties this library still generates.
*/
static readonly MAX_VERSION: string = "beta.32";

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";
}
30 changes: 24 additions & 6 deletions src/pkgman.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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.`,
);
}

Expand Down Expand Up @@ -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 v${installedVerStr()} is not supported by this library (supported range: ${SUPPORTED_RANGE}). Please run \`camoufox fetch\` to install a supported version.`,
);
}

// Install and recheck
Expand Down
75 changes: 75 additions & 0 deletions test/pkgman.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,78 @@ 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<typeof import("node:module")>()),
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");
});

test("rejects Firefox 156 builds", async () => {
const camoufoxPath = await loadCamoufoxPath("1.62.1", "beta.34");
expect(() => camoufoxPath(false)).toThrow("<beta.32");
});
});

describe("CamoufoxFetcher supported range", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

test("skips releases newer than the supported range", async () => {
const { CamoufoxFetcher, OS_NAME } = await import("../src/pkgman");
const suffix = `${OS_NAME}.${CamoufoxFetcher.getPlatformArch()}.zip`;
const asset = (name: string) => ({
name,
browser_download_url: `https://example.com/${name}`,
});
vi.stubGlobal(
"fetch",
vi.fn(async () => ({
ok: true,
json: async () => [
{ assets: [asset(`camoufox-156.0.1-beta.34-${suffix}`)] },
{ assets: [asset(`camoufox-152.0.4-beta.30-${suffix}`)] },
],
})),
);
const fetcher = new CamoufoxFetcher();
await fetcher.init();
expect(fetcher.verstr).toBe("152.0.4-beta.30");
});
});
Loading