diff --git a/package.json b/package.json index be2ef01ab..fff9ed3b9 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "chalk": "^4.0.0", "fs-extra": "^4.0.2", "minimist": "^1.2.0", - "puppeteer": "^21.0.3", + "puppeteer": "^22.12.1", "string-width": "^4.2.0", "util.promisify": "^1.1.1", "xvfb": "^0.4.0" @@ -21,7 +21,6 @@ "@types/jest": "^27.4.1", "@types/minimist": "^1.2.2", "@types/node": "^12.16.3", - "@types/puppeteer": "^5.4.4", "jest": "^29.6.2", "typescript": "^4.6.3" }, diff --git a/packages/api/package.json b/packages/api/package.json index 98468ec39..bfae81817 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -35,8 +35,8 @@ "chalk": "^4.0.0", "fs-extra": "^4.0.2", "minimist": "^1.2.8", - "puppeteer": "^21.0.3", - "puppeteer-core": "^21.0.3", + "puppeteer": "^22.12.1", + "puppeteer-core": "^22.12.1", "string-width": "^4.2.0", "util.promisify": "^1.1.1", "xvfb": "^0.4.0" @@ -46,7 +46,6 @@ "@types/jest": "^27.4.1", "@types/minimist": "^1.2.5", "@types/node": "^12.16.3", - "@types/puppeteer": "^5.4.4", "jest": "^29.6.2", "ts-jest": "^29.1.1", "typescript": "^4.6.3" diff --git a/packages/api/src/__tests__/heap/examples/example-5.test.ts b/packages/api/src/__tests__/heap/examples/example-5.test.ts index c93cce5a4..f31ebe99f 100644 --- a/packages/api/src/__tests__/heap/examples/example-5.test.ts +++ b/packages/api/src/__tests__/heap/examples/example-5.test.ts @@ -32,4 +32,4 @@ test('memory test', async () => { heap = await takeNodeMinimalHeap(); // if rabbitHole does not add new references, the obj can be GCed expect(heap.hasObjectWithClassName('TestObject')).toBe(false); -}, 30000); +}, 60000); diff --git a/packages/api/src/__tests__/heap/examples/example-7.test.ts b/packages/api/src/__tests__/heap/examples/example-7.test.ts index 37dce785c..6c2904c2e 100644 --- a/packages/api/src/__tests__/heap/examples/example-7.test.ts +++ b/packages/api/src/__tests__/heap/examples/example-7.test.ts @@ -27,4 +27,4 @@ test('memory test', async () => { // expect object with marker "memlab-mark-2" can be GCed expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false); -}, 30000); +}, 60000); diff --git a/packages/cli/package.json b/packages/cli/package.json index 0a3665cb8..8944c5994 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -36,8 +36,8 @@ "chalk": "^4.0.0", "fs-extra": "^4.0.2", "minimist": "^1.2.8", - "puppeteer": "^21.0.3", - "puppeteer-core": "^21.0.3", + "puppeteer": "^22.12.1", + "puppeteer-core": "^22.12.1", "string-width": "^4.2.0", "util.promisify": "^1.1.1", "xvfb": "^0.4.0" @@ -48,7 +48,6 @@ "@types/jest": "^27.4.1", "@types/minimist": "^1.2.5", "@types/node": "^12.16.3", - "@types/puppeteer": "^5.4.4", "jest": "^29.6.2", "ts-jest": "^29.1.1", "typescript": "^4.6.3" diff --git a/packages/core/package.json b/packages/core/package.json index 10ed78dc9..e97535219 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -36,8 +36,8 @@ "chalk": "^4.0.0", "fs-extra": "^4.0.2", "minimist": "^1.2.8", - "puppeteer": "^21.0.3", - "puppeteer-core": "^21.0.3", + "puppeteer": "^22.12.1", + "puppeteer-core": "^22.12.1", "string-width": "^4.2.0", "util.promisify": "^1.1.1", "xvfb": "^0.4.0" @@ -47,7 +47,6 @@ "@types/jest": "^27.4.1", "@types/minimist": "^1.2.5", "@types/node": "^12.16.3", - "@types/puppeteer": "^5.4.4", "jest": "^29.6.2", "ts-jest": "^29.1.1", "typescript": "^4.6.3" diff --git a/packages/core/src/lib/Config.ts b/packages/core/src/lib/Config.ts index fb90af510..861e0122b 100644 --- a/packages/core/src/lib/Config.ts +++ b/packages/core/src/lib/Config.ts @@ -24,6 +24,7 @@ import type { PuppeteerConfig, } from './Types'; +import os from 'os'; import path from 'path'; import modes from '../modes/RunningModes'; import info from './Console'; @@ -87,6 +88,7 @@ export class MemLabConfig { _isHeadfulBrowser: boolean; _disableWebSecurity: boolean; _browser: string; + _chromeUserDataDir: string; snapshotHasDetachedness: boolean; specifiedEngine: boolean; @@ -103,7 +105,6 @@ export class MemLabConfig { workDir: string; browserDir: string; dataBaseDir: string; - userDataDir: string; curDataDir: string; webSourceDir: string; debugDataDir: string; @@ -263,6 +264,7 @@ export class MemLabConfig { this.analysisMode = constant.unset; this.focusFiberNodeId = -1; this.isFB = constant.isFB; + this._chromeUserDataDir = os.tmpdir(); // assuming the Evn doesn't support Xvfb before checking this.machineSupportsXVFB = false; // by default we want to use Xvfb if the Env supports it @@ -280,7 +282,7 @@ export class MemLabConfig { ignoreHTTPSErrors: true, // Support running on Windows ignoreDefaultArgs: ['--disable-extensions'], - userDataDir: this.userDataDir, + userDataDir: this._chromeUserDataDir, // Chromium in ContinuousTest needs this args args: [ '--no-sandbox', @@ -602,6 +604,17 @@ export class MemLabConfig { this.targetTab = tab; } + set userDataDir(chromeUserDataDir: string) { + this._chromeUserDataDir = chromeUserDataDir; + if (this.puppeteerConfig != null) { + this.puppeteerConfig.userDataDir = chromeUserDataDir; + } + } + + get userDataDir(): string { + return this._chromeUserDataDir; + } + set scenario(scenario: Optional) { this._scenario = scenario; if (scenario == null) { diff --git a/packages/e2e/package.json b/packages/e2e/package.json index a443978f1..d14bd7cf7 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -31,8 +31,8 @@ "chalk": "^4.0.0", "fs-extra": "^4.0.2", "minimist": "^1.2.8", - "puppeteer": "^21.0.3", - "puppeteer-core": "^21.0.3", + "puppeteer": "^22.12.1", + "puppeteer-core": "^22.12.1", "string-width": "^4.2.0", "util.promisify": "^1.1.1", "xvfb": "^0.4.0" @@ -44,7 +44,6 @@ "@types/jest": "^27.4.1", "@types/minimist": "^1.2.5", "@types/node": "^12.16.3", - "@types/puppeteer": "^5.4.4", "jest": "^29.6.2", "ts-jest": "^29.1.1", "typescript": "^4.6.3" diff --git a/packages/e2e/src/E2EInteractionManager.ts b/packages/e2e/src/E2EInteractionManager.ts index 59cfaaf82..2a576c8f5 100644 --- a/packages/e2e/src/E2EInteractionManager.ts +++ b/packages/e2e/src/E2EInteractionManager.ts @@ -55,6 +55,21 @@ const { getURLParameter, } = E2EUtils; +type PuppeteerDataHandler = (data: {chunk: string}) => void; +type PuppeteerProgressHandler = (data: { + done: number; + total: number; + finished?: boolean; +}) => void; +type DataHandler = PuppeteerDataHandler | PuppeteerProgressHandler; + +// some synced implementation of memlab based on this codebase +// may be using an old version of puppeteer that doesn't have page.$ +interface SessionWithRemoveListenerFromAllVersions { + removeListener?: (type: string, dataHandler: DataHandler) => void; + removeAllListeners: (type: string) => void; +} + export default class E2EInteractionManager { private mainThreadCdpsession: Optional; private page: Page; @@ -389,16 +404,12 @@ export default class E2EInteractionManager { ) { const writeStream = fs.createWriteStream(file, {encoding: 'UTF-8'}); let lastChunk = ''; - const dataHandler = (data: {chunk: string}) => { + const dataHandler: PuppeteerDataHandler = data => { writeStream.write(data.chunk); lastChunk = data.chunk; }; - const progressHandler = (data: { - done: number; - total: number; - finished?: boolean; - }) => { + const progressHandler: PuppeteerProgressHandler = data => { const percent = ((100 * data.done) / data.total) | 0; if (!config.isContinuousTest) { info.overwrite(`heap snapshot ${percent}% complete`); @@ -415,14 +426,35 @@ export default class E2EInteractionManager { }); checkLastSnapshotChunk(lastChunk); - session.removeListener('HeapProfiler.addHeapSnapshotChunk', dataHandler); - session.removeListener( + this.removeListener( + session, + 'HeapProfiler.addHeapSnapshotChunk', + dataHandler, + ); + this.removeListener( + session, 'HeapProfiler.reportHeapSnapshotProgress', progressHandler, ); writeStream.end(); } + // this implement may remove all dataHandler of the specified eventType + private removeListener( + session: SessionWithRemoveListenerFromAllVersions, + eventType: string, + dataHandler: DataHandler, + ) { + if (typeof session.removeListener === 'function') { + session.removeListener(eventType, dataHandler); + return; + } + if (typeof session.removeAllListeners === 'function') { + session.removeAllListeners(eventType); + return; + } + } + private async saveHeapSnapshotToFile(file: string): Promise { info.beginSection('heap snapshot'); const start = Date.now(); diff --git a/packages/e2e/src/lib/operations/InteractionUtils.ts b/packages/e2e/src/lib/operations/InteractionUtils.ts index cfc7187c2..bf2ec7db7 100644 --- a/packages/e2e/src/lib/operations/InteractionUtils.ts +++ b/packages/e2e/src/lib/operations/InteractionUtils.ts @@ -14,7 +14,11 @@ import {info, config} from '@memlab/core'; import path from 'path'; import type {Page, ElementHandle} from 'puppeteer'; -import type {CheckPageLoadCallback, OperationArgs} from '@memlab/core'; +import type { + CheckPageLoadCallback, + OperationArgs, + Optional, +} from '@memlab/core'; function waitFor(delay: number): Promise { return new Promise(resolve => { @@ -213,8 +217,55 @@ async function getElementsContainingText( text: string, ): Promise[]> { const xpath = `//*[not(self::script)][contains(text(), '${text}')]`; - const elements = await page.$x(xpath); - return elements as ElementHandle[]; + return findBySelector(page, xpath); +} + +// some synced implementation of memlab based on this codebase +// may be using an old version of puppeteer that doesn't have page.$ +interface PageWithSelectorAllVersions { + $?: (xpath: string) => Promise[]>>; + $x?: (xpath: string) => Promise[]>>; +} + +async function findBySelector( + page: Page, + xpath: string, +): Promise[]> { + const _page = page as unknown as PageWithSelectorAllVersions; + if (typeof _page.$x === 'function') { + return ((await _page.$x(xpath)) ?? []) as ElementHandle[]; + } + + // evaluate in browser and return the result as a JSHandle + // to an array of elements in the browser's context + const elements = await page.evaluateHandle(xpath => { + const xpathResult = document.evaluate( + xpath, + document, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE, + null, + ); + + const nodes = []; + let node = xpathResult.iterateNext(); + while (node) { + nodes.push(node); + node = xpathResult.iterateNext(); + } + return nodes; + }, xpath); + + // Convert the elements to ElementHandle[] + const elementHandles = await elements.getProperties(); + const ret = []; + for (const elementHandle of elementHandles.values()) { + const element = elementHandle.asElement(); + if (element != null) { + ret.push(element as ElementHandle); + } + } + return ret; } export default { diff --git a/packages/heap-analysis/package.json b/packages/heap-analysis/package.json index 2e5aff5e6..f9a51eaf0 100644 --- a/packages/heap-analysis/package.json +++ b/packages/heap-analysis/package.json @@ -27,8 +27,8 @@ "chalk": "^4.0.0", "fs-extra": "^4.0.2", "minimist": "^1.2.8", - "puppeteer": "^21.0.3", - "puppeteer-core": "^21.0.3", + "puppeteer": "^22.12.1", + "puppeteer-core": "^22.12.1", "string-width": "^4.2.0", "util.promisify": "^1.1.1", "xvfb": "^0.4.0" @@ -38,7 +38,6 @@ "@types/jest": "^27.4.1", "@types/minimist": "^1.2.5", "@types/node": "^12.16.3", - "@types/puppeteer": "^5.4.4", "jest": "^29.6.2", "ts-jest": "^29.1.1", "typescript": "^4.6.3" diff --git a/packages/memlab/package.json b/packages/memlab/package.json index 38d46d9e0..891291175 100644 --- a/packages/memlab/package.json +++ b/packages/memlab/package.json @@ -36,8 +36,8 @@ "chalk": "^4.0.0", "fs-extra": "^4.0.2", "minimist": "^1.2.8", - "puppeteer": "^21.0.3", - "puppeteer-core": "^21.0.3", + "puppeteer": "^22.12.1", + "puppeteer-core": "^22.12.1", "string-width": "^4.2.0", "util.promisify": "^1.1.1", "xvfb": "^0.4.0" @@ -47,7 +47,6 @@ "@types/jest": "^27.4.1", "@types/minimist": "^1.2.5", "@types/node": "^12.16.3", - "@types/puppeteer": "^5.4.4", "jest": "^29.6.2", "ts-jest": "^29.1.1", "typescript": "^4.6.3"