refactor(engine): add fingerprinted browser leases

This commit is contained in:
James
2026-07-16 12:58:10 -04:00
parent 3bb26b0f08
commit 9b23c00237
9 changed files with 765 additions and 224 deletions
@@ -324,8 +324,8 @@ describe("browser pool", () => {
expect(first.browser).toBe(second.browser);
expect(launchFn).toHaveBeenCalledTimes(1);
await releaseBrowser(first.browser, poolCfg);
await releaseBrowser(second.browser, poolCfg);
await first.release();
await second.release();
});
it("concurrent acquires via Promise.all trigger exactly one launch", async () => {
@@ -339,14 +339,14 @@ describe("browser pool", () => {
expect(a.browser).toBe(b.browser);
expect(b.browser).toBe(c.browser);
await releaseBrowser(a.browser, poolCfg);
await releaseBrowser(b.browser, poolCfg);
await releaseBrowser(c.browser, poolCfg);
await a.release();
await b.release();
await c.release();
});
it("pool recovers from a disconnected browser", async () => {
const first = await acquireBrowser(["--no-sandbox"], poolCfg);
await releaseBrowser(first.browser, poolCfg);
await first.release();
// Simulate Chrome crash
(first.browser as unknown as { connected: boolean }).connected = false;
@@ -359,17 +359,42 @@ describe("browser pool", () => {
expect(second.browser).not.toBe(first.browser);
expect(launchFn).toHaveBeenCalledTimes(2);
await releaseBrowser(second.browser, poolCfg);
await second.release();
});
it("release at refCount 0 closes the browser", async () => {
const result = await acquireBrowser(["--no-sandbox"], poolCfg);
const closeFn = result.browser.close as ReturnType<typeof vi.fn>;
await releaseBrowser(result.browser, poolCfg);
await result.release();
expect(closeFn).toHaveBeenCalledTimes(1);
});
it("releaseBrowser preserves the sole-owner legacy path", async () => {
const result = await acquireBrowser(["--no-sandbox"], poolCfg);
const closeFn = result.browser.close as ReturnType<typeof vi.fn>;
await releaseBrowser(result.browser);
expect(closeFn).toHaveBeenCalledTimes(1);
await result.release();
expect(closeFn).toHaveBeenCalledTimes(1);
});
it("releaseBrowser rejects an ambiguous pooled browser handle", async () => {
const first = await acquireBrowser(["--no-sandbox"], poolCfg);
const second = await acquireBrowser(["--no-sandbox"], poolCfg);
const closeFn = first.browser.close as ReturnType<typeof vi.fn>;
await expect(releaseBrowser(first.browser)).rejects.toThrow(
"Cannot release a pooled browser by handle while 2 leases are active",
);
expect(closeFn).not.toHaveBeenCalled();
await first.release();
await second.release();
});
it("pool returns a separate browser when forceScreenshot mismatches pooled mode", async () => {
const first = await acquireBrowser(["--no-sandbox"], poolCfg);
expect(first.captureMode).toBe("screenshot");
@@ -379,8 +404,8 @@ describe("browser pool", () => {
expect(second.browser).toBe(first.browser);
expect(launchFn).toHaveBeenCalledTimes(1);
await releaseBrowser(first.browser, poolCfg);
await releaseBrowser(second.browser, poolCfg);
await first.release();
await second.release();
});
it("forceReleaseBrowser does not kill Chrome when other sessions hold refs", async () => {
@@ -394,8 +419,9 @@ describe("browser pool", () => {
// Should NOT have disconnected — other session still holds a ref
expect(disconnectFn).not.toHaveBeenCalled();
// Release the remaining ref normally
await releaseBrowser(second.browser, poolCfg);
// Each owner releases its own identity; neither can consume the other.
result.forceRelease();
await second.release();
});
it("drainBrowserPool is safe to call when no browser is pooled", async () => {