Files
2026-07-13 13:23:39 +08:00

834 lines
35 KiB
TypeScript

/* eslint-disable dot-notation -- Accessing private properties */
import http from 'node:http';
import { promisify } from 'node:util';
import { sleep } from 'crawlee';
import type { BrowserFingerprintWithHeaders } from 'fingerprint-generator';
import playwright from 'playwright';
import type { Server as ProxyChainServer } from 'proxy-chain';
import type { Page } from 'puppeteer';
import puppeteer from 'puppeteer';
import { addTimeoutToPromise } from '@apify/timeout';
import type { BrowserController } from '../../packages/browser-pool/src/abstract-classes/browser-controller';
import { BrowserPool } from '../../packages/browser-pool/src/browser-pool';
import { BROWSER_POOL_EVENTS } from '../../packages/browser-pool/src/events';
import { BrowserName, OperatingSystemsName } from '../../packages/browser-pool/src/fingerprinting/types';
import { PlaywrightPlugin } from '../../packages/browser-pool/src/playwright/playwright-plugin';
import { PuppeteerPlugin } from '../../packages/browser-pool/src/puppeteer/puppeteer-plugin';
import { createProxyServer } from './browser-plugins/create-proxy-server';
const fingerprintingMatrix: [string, PlaywrightPlugin | PuppeteerPlugin][] = [
[
'Playwright - persistent',
new PlaywrightPlugin(playwright.chromium, {
useIncognitoPages: false,
}),
],
[
'Playwright - Incognito',
new PlaywrightPlugin(playwright.chromium, {
useIncognitoPages: true,
}),
],
[
'Puppeteer - Persistent',
new PuppeteerPlugin(puppeteer, {
useIncognitoPages: false,
}),
],
[
'Puppeteer - Incognito',
new PuppeteerPlugin(puppeteer, {
useIncognitoPages: true,
}),
],
];
// Tests could be generated from this blueprint for each plugin
describe.each([
['Puppeteer', new PuppeteerPlugin(puppeteer)],
['Playwright', new PlaywrightPlugin(playwright.chromium)], // Chromium is faster than firefox and webkit
])('BrowserPool - %s', (_, plugin) => {
let browserPool: BrowserPool<{ browserPlugins: [typeof plugin]; closeInactiveBrowserAfterSecs: 2 }>;
beforeEach(async () => {
vitest.clearAllMocks();
browserPool = new BrowserPool({
browserPlugins: [plugin],
closeInactiveBrowserAfterSecs: 2,
retireInactiveBrowserAfterSecs: 2,
});
});
afterEach(async () => {
await browserPool?.destroy();
});
let target: http.Server;
let unprotectedProxy: ProxyChainServer;
let protectedProxy: ProxyChainServer;
beforeAll(async () => {
target = http.createServer((request, response) => {
response.end(request.socket.remoteAddress);
});
await promisify(target.listen.bind(target) as any)(0, '127.0.0.1');
unprotectedProxy = createProxyServer('127.0.0.2', '', '');
await unprotectedProxy.listen();
protectedProxy = createProxyServer('127.0.0.3', 'foo', 'bar');
await protectedProxy.listen();
});
afterAll(async () => {
await promisify(target.close.bind(target))();
await unprotectedProxy.close(false);
await protectedProxy.close(false);
});
describe('Initialization & retirement', () => {
test('should retire browsers', async () => {
await browserPool.newPage();
browserPool.retireAllBrowsers();
expect(browserPool.startingBrowserControllers.size).toBe(0);
expect(browserPool.activeBrowserControllers.size).toBe(0);
expect(browserPool.retiredBrowserControllers.size).toBe(1);
});
test('should destroy pool', async () => {
const page = await browserPool.newPage();
const browserController = browserPool.getBrowserControllerByPage(page)!;
vitest.spyOn(browserController, 'close');
await browserPool.destroy();
expect(browserController.close).toHaveBeenCalled();
expect(browserPool.startingBrowserControllers.size).toBe(0);
expect(browserPool.activeBrowserControllers.size).toBe(0);
expect(browserPool.retiredBrowserControllers.size).toBe(0);
expect(browserPool['browserKillerInterval']).toBeUndefined();
});
});
describe('Basic user functionality', () => {
// Basic user facing functionality
test('should open new page', async () => {
const page = await browserPool.newPage();
expect(page.goto).toBeDefined();
expect(page.close).toBeDefined();
});
// https://github.com/apify/crawlee/issues/3670
test('should not leak aborted cancelTask between concurrent newPage calls', async () => {
const previousTimeout = browserPool.operationTimeoutMillis;
browserPool.operationTimeoutMillis = 1;
// Each newPage call is wrapped in its own outer addTimeoutToPromise,
// matching how BasicCrawler wraps _runRequestHandler. Without the fix,
// queued limiter callbacks inherit the previous task's aborted
// cancelTask via AsyncLocalStorage propagation through p-limit, causing
// their first tryCancel() to throw InternalTimeoutError pre-emptively;
// that error then gets silently swallowed by the outer wrap.
const results = await Promise.allSettled(
Array.from({ length: 5 }, () =>
addTimeoutToPromise(async () => browserPool.newPage(), 60_000, 'outer timed out'),
),
);
browserPool.operationTimeoutMillis = previousTimeout;
browserPool.retireAllBrowsers();
// All calls must reject — none should silently resolve with undefined.
expect(results.every((r) => r.status === 'rejected')).toBe(true);
// Each rejection should reflect this call's own newPage timeout, not
// a leaked "canceled due to a timeout" from a sibling's aborted context.
for (const r of results) {
expect(r.status === 'rejected' && (r.reason as Error).message).toMatch(
/browserController\.newPage\(\) (failed|timed out)/,
);
}
});
// TODO: this test is very flaky in the CI
test.skip('should allow early aborting in case of outer timeout', async () => {
const timeout = browserPool.operationTimeoutMillis;
browserPool.operationTimeoutMillis = 500;
// @ts-expect-error mocking private method
const spy = vitest.spyOn(BrowserPool.prototype, '_executeHooks');
await browserPool.newPage();
expect(spy).toBeCalledTimes(4);
spy.mockReset();
await expect(
addTimeoutToPromise(async () => browserPool.newPage(), 10, 'opening new page timed out'),
).rejects.toThrowError('opening new page timed out');
// We terminated early enough so only preLaunchHooks were not executed,
// thanks to `tryCancel()` calls after each await. If we did not run
// inside `addTimeoutToPromise()`, this would not work and we would get
// 4 calls instead of just one.
expect(spy).toBeCalledTimes(1);
browserPool.operationTimeoutMillis = timeout;
browserPool.retireAllBrowsers();
});
test('should open new page in incognito context', async () => {
const browserPoolIncognito = new BrowserPool({
browserPlugins: [new PlaywrightPlugin(playwright.chromium, { useIncognitoPages: true })],
closeInactiveBrowserAfterSecs: 2,
});
const page = await browserPoolIncognito.newPage();
await browserPoolIncognito.newPage();
await browserPoolIncognito.newPage();
expect(page.context().pages()).toHaveLength(1);
});
test('should open new page in new browser', async () => {
vitest.spyOn(plugin, 'launch');
await browserPool.newPage();
await browserPool.newPageInNewBrowser();
await browserPool.newPageInNewBrowser();
expect(browserPool.startingBrowserControllers.size).toBe(0);
expect(browserPool.activeBrowserControllers.size).toBe(3);
expect(plugin.launch).toHaveBeenCalledTimes(3);
});
test('should correctly override page close', async () => {
// @ts-expect-error Private function
vitest.spyOn(browserPool!, '_overridePageClose');
const page = await browserPool.newPage();
expect(browserPool['_overridePageClose']).toBeCalled();
const controller = browserPool.getBrowserControllerByPage(page)!;
expect(controller.activePages).toEqual(1);
expect(controller.totalPages).toEqual(1);
await page.close();
expect(controller.activePages).toEqual(0);
expect(controller.totalPages).toEqual(1);
});
test('should retire browser after page count', async () => {
browserPool.retireBrowserAfterPageCount = 2;
vitest.spyOn(browserPool, 'retireBrowserController');
expect(browserPool.activeBrowserControllers.size).toBe(0);
await browserPool.newPage();
await browserPool.newPage();
await browserPool.newPage();
expect(browserPool.activeBrowserControllers.size).toBe(1);
expect(browserPool.retiredBrowserControllers.size).toBe(1);
expect(browserPool.retireBrowserController).toBeCalledTimes(1);
});
test('should allow max pages per browser', async () => {
browserPool.maxOpenPagesPerBrowser = 1;
// @ts-expect-error Private function
vitest.spyOn(browserPool!, '_launchBrowser');
await browserPool.newPage();
expect(browserPool.activeBrowserControllers.size).toBe(1);
await browserPool.newPage();
expect(browserPool.activeBrowserControllers.size).toBe(2);
await browserPool.newPage();
expect(browserPool.activeBrowserControllers.size).toBe(3);
expect(browserPool['_launchBrowser']).toBeCalledTimes(3);
});
test('should allow max pages per browser - no race condition', async () => {
browserPool.maxOpenPagesPerBrowser = 1;
// @ts-expect-error Private function
vitest.spyOn(browserPool, '_launchBrowser');
const usePlugin = {
browserPlugin: plugin,
};
await Promise.all([browserPool.newPage(usePlugin), browserPool.newPage(usePlugin)]);
expect(browserPool.activeBrowserControllers.size).toBe(2);
expect(browserPool['_launchBrowser']).toBeCalledTimes(2);
});
test('should close retired browsers', async () => {
browserPool.retireBrowserAfterPageCount = 1;
clearInterval(browserPool['browserKillerInterval']!);
browserPool['browserKillerInterval'] = setInterval(
async () => browserPool['_closeInactiveRetiredBrowsers'](),
100,
);
// @ts-expect-error Private function
vitest.spyOn(browserPool!, '_closeRetiredBrowserWithNoPages');
expect(browserPool.retiredBrowserControllers.size).toBe(0);
const page = await browserPool.newPage();
const controller = browserPool.getBrowserControllerByPage(page)!;
vitest.spyOn(controller, 'close');
expect(browserPool.retiredBrowserControllers.size).toBe(1);
await page.close();
await new Promise<void>((resolve) =>
setTimeout(() => {
resolve();
}, 1000),
);
expect(browserPool['_closeRetiredBrowserWithNoPages']).toHaveBeenCalled();
expect(controller.close).toHaveBeenCalled();
expect(browserPool.retiredBrowserControllers.size).toBe(0);
});
describe('hooks', () => {
test('should run hooks in series with custom args', async () => {
const indexArray: number[] = [];
const createAsyncHookReturningIndex = (i: number) => async () => {
const index = await new Promise<number>((resolve) => setTimeout(() => resolve(i), 100));
indexArray.push(index);
};
const hooks = new Array(10);
for (let i = 0; i < hooks.length; i++) {
hooks[i] = createAsyncHookReturningIndex(i);
}
await browserPool['_executeHooks'](hooks);
expect(indexArray).toHaveLength(10);
indexArray.forEach((v, index) => expect(v).toEqual(index));
});
test('browser lifecycle works correctly', async () => {
let resolvePreLaunchHook: (() => void) | null = null;
let resolvePostLaunchHook: (() => void) | null = null;
const preLaunchPromise = new Promise<void>((resolve) => {
resolvePreLaunchHook = resolve;
});
const postLaunchPromise = new Promise<void>((resolve) => {
resolvePostLaunchHook = resolve;
});
browserPool.preLaunchHooks = [...browserPool.preLaunchHooks, async () => preLaunchPromise];
browserPool.postLaunchHooks = [...browserPool.postLaunchHooks, async () => postLaunchPromise];
const newPagePromise = browserPool.newPage();
await sleep(200);
expect(browserPool.startingBrowserControllers.size).toBe(1);
expect(browserPool.activeBrowserControllers.size).toBe(0);
expect(browserPool.retiredBrowserControllers.size).toBe(0);
await sleep(5e3); // Make the wait longer than the browser pool's retireInactiveBrowserAfterSecs + closeInactiveBrowserAfterSecs
resolvePreLaunchHook!();
resolvePostLaunchHook!();
const page = await newPagePromise;
expect(browserPool.startingBrowserControllers.size).toBe(0);
expect(browserPool.activeBrowserControllers.size).toBe(1);
expect(browserPool.retiredBrowserControllers.size).toBe(0);
await (page.evaluate as any)('() => {}'); // Make sure the page is usable
await page.close();
});
describe('preLaunchHooks', () => {
test('should evaluate hook before launching browser with correct args', async () => {
const myAsyncHook = async () => Promise.resolve();
browserPool.preLaunchHooks.push(myAsyncHook);
// @ts-expect-error Private function
vitest.spyOn(browserPool!, '_executeHooks');
const page = await browserPool.newPage();
const pageId = browserPool.getPageId(page)!;
const { launchContext } = browserPool.getBrowserControllerByPage(page)!;
expect(browserPool['_executeHooks']).toHaveBeenNthCalledWith(
1,
browserPool.preLaunchHooks,
pageId,
launchContext,
);
});
// We had a problem where if the first newPage() call, which launches
// a browser failed in hooks, then the browserController would get stuck
// in limbo and subsequent newPage() calls would never resolve.
test('error in hook does not leave browser stuck in limbo', async () => {
const errorMessage = 'pre-launch failed';
browserPool.preLaunchHooks = [
async () => {
throw new Error(errorMessage);
},
];
const attempts = 5;
for (let i = 0; i < attempts; i++) {
try {
await browserPool.newPage();
} catch (err) {
expect((err as Error).message).toBe(errorMessage);
}
}
expect(browserPool.activeBrowserControllers.size).toBe(0);
expect.assertions(attempts + 1);
});
});
describe('postLaunchHooks', () => {
test('should evaluate hook after launching browser with correct args', async () => {
const myAsyncHook = async () => Promise.resolve();
browserPool.postLaunchHooks = [myAsyncHook];
// @ts-expect-error Private function
vitest.spyOn(browserPool, '_executeHooks');
const page = await browserPool.newPage();
const pageId = browserPool.getPageId(page)!;
const browserController = browserPool.getBrowserControllerByPage(page)!;
expect(browserPool['_executeHooks']).toHaveBeenNthCalledWith(
2,
browserPool.postLaunchHooks,
pageId,
browserController,
);
});
// We had a problem where if the first newPage() call, which launches
// a browser failed in hooks, then the browserController would get stuck
// in limbo and subsequent newPage() calls would never resolve.
test('error in hook does not leave browser stuck in limbo', async () => {
const errorMessage = 'post-launch failed';
const controllers: BrowserController[] = [];
browserPool.postLaunchHooks = [
async (_pageId, browserController) => {
controllers.push(browserController);
throw new Error(errorMessage);
},
];
const attempts = 5;
for (let i = 0; i < attempts; i++) {
try {
await browserPool.newPage();
} catch (err) {
expect((err as Error).message).toBe(errorMessage);
}
}
// Wait until all browsers are closed. This will only resolve if all close,
// if it does not resolve, the test will timeout and fail.
await new Promise<void>((resolve) => {
const int = setInterval(() => {
const stillWaiting = controllers.some((c) => c.isActive === true);
if (!stillWaiting) {
clearInterval(int);
resolve();
}
}, 10);
});
expect(browserPool.activeBrowserControllers.size).toBe(0);
expect.assertions(attempts + 1);
});
});
describe('prePageCreateHooks', () => {
test('should evaluate hook after launching browser with correct args', async () => {
const myAsyncHook = async () => Promise.resolve();
browserPool.prePageCreateHooks = [myAsyncHook];
// @ts-expect-error Private function
vitest.spyOn(browserPool, '_executeHooks');
const page = await browserPool.newPage();
const pageId = browserPool.getPageId(page)!;
const browserController = browserPool.getBrowserControllerByPage(page)!;
expect(browserPool['_executeHooks']).toHaveBeenNthCalledWith(
3,
browserPool.prePageCreateHooks,
pageId,
browserController,
browserController.launchContext.useIncognitoPages ? {} : undefined,
);
});
});
describe('postPageCreateHooks', () => {
test('should evaluate hook after launching browser with correct args', async () => {
const myAsyncHook = async () => Promise.resolve();
browserPool.postPageCreateHooks = [myAsyncHook];
// @ts-expect-error Private function
vitest.spyOn(browserPool, '_executeHooks');
const page = await browserPool.newPage();
const browserController = browserPool.getBrowserControllerByPage(page);
expect(browserPool['_executeHooks']).toHaveBeenNthCalledWith(
4,
browserPool.postPageCreateHooks,
page,
browserController,
);
});
});
describe('prePageCloseHooks', () => {
test('should evaluate hook after launching browser with correct args', async () => {
const myAsyncHook = async () => Promise.resolve();
browserPool.prePageCloseHooks = [myAsyncHook];
// @ts-expect-error Private function
vitest.spyOn(browserPool, '_executeHooks');
const page = await browserPool.newPage();
await page.close();
const browserController = browserPool.getBrowserControllerByPage(page);
expect(browserPool['_executeHooks']).toHaveBeenNthCalledWith(
5,
browserPool.prePageCloseHooks,
page,
browserController,
);
});
});
describe('postPageCloseHooks', () => {
test('should evaluate hook after launching browser with correct args', async () => {
const myAsyncHook = async () => Promise.resolve();
browserPool.postPageCloseHooks = [myAsyncHook];
// @ts-expect-error Private function
vitest.spyOn(browserPool, '_executeHooks');
const page = await browserPool.newPage();
const pageId = browserPool.getPageId(page);
await page.close();
const browserController = browserPool.getBrowserControllerByPage(page);
expect(browserPool['_executeHooks']).toHaveBeenNthCalledWith(
6,
browserPool.postPageCloseHooks,
pageId,
browserController,
);
});
});
describe('default browser automation masking', () => {
describe.each(fingerprintingMatrix)('%s', (_name, fingerprintPlugin) => {
let browserPoolWithDefaults: BrowserPool;
let page: any;
beforeEach(async () => {
browserPoolWithDefaults = new BrowserPool({
browserPlugins: [fingerprintPlugin],
closeInactiveBrowserAfterSecs: 2,
});
page = await browserPoolWithDefaults.newPage();
});
afterEach(async () => {
if (page) await page.close();
await browserPoolWithDefaults.destroy();
});
test('should hide webdriver', async () => {
await page.goto(`file://${__dirname}/test.html`);
const webdriver = await page.evaluate(() => {
return navigator.webdriver;
});
// Can be undefined or false, depending on the chrome version.
expect(webdriver).toBeFalsy();
});
});
});
describe('fingerprinting', () => {
describe.each(fingerprintingMatrix)('%s', (_name, fingerprintPlugin) => {
let browserPoolWithFP: BrowserPool;
let page: any;
beforeEach(async () => {
browserPoolWithFP = new BrowserPool({
browserPlugins: [fingerprintPlugin],
closeInactiveBrowserAfterSecs: 2,
useFingerprints: true,
});
page = await browserPoolWithFP.newPage();
});
afterEach(async () => {
if (page) await page.close();
await browserPoolWithFP.destroy();
});
test('should override fingerprint', async () => {
await page.goto(`file://${__dirname}/test.html`);
// @ts-expect-error mistypings
const browserController = browserPoolWithFP.getBrowserControllerByPage(page);
const data: { hardwareConcurrency: number; userAgent: string } = await page.evaluate(() => {
return {
hardwareConcurrency: navigator.hardwareConcurrency,
userAgent: navigator.userAgent,
};
});
// @ts-expect-error mistypings
const { fingerprint } = browserController!.launchContext!
.fingerprint as BrowserFingerprintWithHeaders;
expect(data.hardwareConcurrency).toBe(fingerprint?.navigator.hardwareConcurrency);
expect(data.userAgent).toBe(fingerprint?.navigator.userAgent);
});
test('should hide webdriver', async () => {
await page.goto(`file://${__dirname}/test.html`);
const webdriver = await page.evaluate(() => {
return navigator.webdriver;
});
// Can be undefined or false, depending on the chrome version.
expect(webdriver).toBeFalsy();
});
});
describe('caching', () => {
const commonOptions = {
browserPlugins: [
new PlaywrightPlugin(playwright.chromium, {
useIncognitoPages: true,
}),
],
};
let browserPoolCache: BrowserPool;
afterEach(async () => {
await browserPoolCache.destroy();
});
test('should use fingerprint cache by default', async () => {
browserPoolCache = new BrowserPool({
...commonOptions,
useFingerprints: true,
});
expect(browserPoolCache.fingerprintCache).toBeDefined();
});
test('should turn off cache', async () => {
browserPoolCache = new BrowserPool({
...commonOptions,
useFingerprints: true,
fingerprintOptions: {
useFingerprintCache: false,
},
});
expect(browserPoolCache.fingerprintCache).toBeUndefined();
});
test('should limit cache size', async () => {
browserPoolCache = new BrowserPool({
...commonOptions,
useFingerprints: true,
fingerprintOptions: {
fingerprintCacheSize: 1,
},
});
// cast to any type in order to access the maxSize property for testing purposes.
const cache: any = browserPoolCache!.fingerprintCache!;
expect(cache.maxSize).toBe(1);
});
test('should cache fingerprints', async () => {
browserPoolCache = new BrowserPool({
...commonOptions,
useFingerprints: true,
preLaunchHooks: [
(_pageId, launchContext) => {
// @ts-expect-error issue caused by generics
launchContext.extend({ session: { id: '123' } });
},
],
});
const mock = vitest.fn();
browserPoolCache.fingerprintInjector!.attachFingerprintToPlaywright = mock;
const page: Page = await browserPoolCache.newPageInNewBrowser();
expect(mock.mock.calls[0][1]).toBeDefined();
const page2: Page = await browserPoolCache.newPageInNewBrowser();
await page.close();
await page2.close();
// expect fingerprint parameter of the first call to equal fingerprint parameter of the second call
expect(mock.mock.calls[0][1]).toBe(mock.mock.calls[1][1]);
});
});
});
describe('generator configuration', () => {
const commonOptions = {
browserPlugins: [
new PlaywrightPlugin(playwright.firefox, {
useIncognitoPages: true,
}),
],
};
let browserPoolConfig: BrowserPool;
afterEach(async () => {
await browserPoolConfig.destroy();
});
test('should use native os and browser', async () => {
browserPoolConfig = new BrowserPool({
...commonOptions,
useFingerprints: true,
});
const oldGet = browserPoolConfig.fingerprintGenerator!.getFingerprint;
const mock = vitest.fn((options) => {
return oldGet.bind(browserPoolConfig.fingerprintGenerator)(options);
});
browserPoolConfig.fingerprintGenerator!.getFingerprint = mock;
const page: Page = await browserPoolConfig.newPage();
await page.close();
const defaultOptions = mock.mock.calls[0][0];
expect(defaultOptions.browsers.includes('firefox')).toBe(true);
let os: string;
switch (process.platform) {
case 'darwin':
os = 'macos';
break;
case 'win32':
os = 'windows';
break;
default:
os = 'linux';
}
expect(defaultOptions.operatingSystems.includes(os)).toBe(true);
});
test('should allow changing options', async () => {
browserPoolConfig = new BrowserPool({
...commonOptions,
useFingerprints: true,
fingerprintOptions: {
fingerprintGeneratorOptions: {
operatingSystems: [OperatingSystemsName.windows],
browsers: [BrowserName.chrome],
},
},
});
const oldGet = browserPoolConfig.fingerprintGenerator!.getFingerprint;
const mock = vitest.fn((options) => {
return oldGet.bind(browserPoolConfig.fingerprintGenerator)(options);
});
browserPoolConfig.fingerprintGenerator!.getFingerprint = mock;
const page: Page = await browserPoolConfig.newPageInNewBrowser();
await page.close();
const [options] = mock.mock.calls[0];
expect(options.operatingSystems.includes('windows')).toBe(true);
expect(options.browsers.includes('chrome')).toBe(true);
});
});
});
describe('events', () => {
test(`should emit ${BROWSER_POOL_EVENTS.BROWSER_LAUNCHED} event`, async () => {
browserPool.maxOpenPagesPerBrowser = 1;
let calls = 0;
let argument;
browserPool.on(BROWSER_POOL_EVENTS.BROWSER_LAUNCHED, (arg) => {
argument = arg;
calls++;
});
await browserPool.newPage();
const page = await browserPool.newPage();
expect(calls).toEqual(2);
expect(argument).toEqual(browserPool.getBrowserControllerByPage(page));
});
test(`should emit ${BROWSER_POOL_EVENTS.BROWSER_RETIRED} event`, async () => {
browserPool.retireBrowserAfterPageCount = 1;
let calls = 0;
let argument;
browserPool.on(BROWSER_POOL_EVENTS.BROWSER_RETIRED, (arg) => {
argument = arg;
calls++;
});
await browserPool.newPage();
const page = await browserPool.newPage();
expect(calls).toEqual(2);
expect(argument).toEqual(browserPool.getBrowserControllerByPage(page));
});
test(`should emit ${BROWSER_POOL_EVENTS.PAGE_CREATED} event`, async () => {
let calls = 0;
let argument;
browserPool.on(BROWSER_POOL_EVENTS.PAGE_CREATED, (arg) => {
argument = arg;
calls++;
});
const page = await browserPool.newPage();
expect(argument).toEqual(page);
const page2 = await browserPool.newPage();
expect(calls).toEqual(2);
expect(argument).toEqual(page2);
});
test(`should emit ${BROWSER_POOL_EVENTS.PAGE_CLOSED} event`, async () => {
let calls = 0;
let argument;
browserPool.on(BROWSER_POOL_EVENTS.PAGE_CLOSED, (arg) => {
argument = arg;
calls++;
});
const page = await browserPool.newPage();
await page.close();
expect(argument).toEqual(page);
const page2 = await browserPool.newPage();
await page2.close();
expect(calls).toEqual(2);
expect(argument).toEqual(page2);
});
});
});
});