From a17b7ffaf63f9ab127bef605a260c18504ad64e3 Mon Sep 17 00:00:00 2001 From: Alex Rudenko Date: Thu, 9 Mar 2023 15:00:25 +0100 Subject: [PATCH] chore: allow reading stderr of the browser process (#9813) Co-authored-by: Nikolay Vitkov <34244704+Lightning00Blade@users.noreply.github.com> --- packages/browsers/src/launcher.ts | 64 +++++++++++++++++++++ packages/browsers/test/src/launcher.spec.ts | 28 ++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/browsers/src/launcher.ts b/packages/browsers/src/launcher.ts index 6915fab962c..952aae1e9d9 100644 --- a/packages/browsers/src/launcher.ts +++ b/packages/browsers/src/launcher.ts @@ -17,6 +17,7 @@ import childProcess from 'child_process'; import os from 'os'; import path from 'path'; +import readline from 'readline'; import { Browser, @@ -88,6 +89,9 @@ export function launch(opts: LaunchOptions): Process { return new Process(opts); } +export const CDP_WEBSOCKET_ENDPOINT_REGEX = + /^DevTools listening on (ws:\/\/.*)$/; + class Process { #executablePath; #args: string[]; @@ -245,6 +249,66 @@ class Process { } this.#clearListeners(); } + + waitForLineOutput(regex: RegExp, timeout?: number): Promise { + if (!this.#browserProcess.stderr) { + throw new Error('`browserProcess` does not have stderr.'); + } + const rl = readline.createInterface(this.#browserProcess.stderr); + let stderr = ''; + + return new Promise((resolve, reject) => { + rl.on('line', onLine); + rl.on('close', onClose); + this.#browserProcess.on('exit', onClose); + this.#browserProcess.on('error', onClose); + const timeoutId = timeout ? setTimeout(onTimeout, timeout) : 0; + + const cleanup = (): void => { + if (timeoutId) { + clearTimeout(timeoutId); + } + rl.off('line', onLine); + rl.off('close', onClose); + this.#browserProcess.off('exit', onClose); + this.#browserProcess.off('error', onClose); + }; + + function onClose(error?: Error): void { + cleanup(); + reject( + new Error( + [ + `Failed to launch the browser process!${ + error ? ' ' + error.message : '' + }`, + stderr, + ].join('\n') + ) + ); + } + + function onTimeout(): void { + cleanup(); + reject( + new Error( + `Timed out after ${timeout} ms while waiting for the WS endpoint URL to appear in stdout!` + ) + ); + } + + function onLine(line: string): void { + stderr += line + '\n'; + const match = line.match(regex); + if (!match) { + return; + } + cleanup(); + // The RegExp matches, so this will obviously exist. + resolve(match[1]!); + } + }); + } } const PROCESS_ERROR_EXPLANATION = `Puppeteer was unable to kill the process which ran the browser binary. diff --git a/packages/browsers/test/src/launcher.spec.ts b/packages/browsers/test/src/launcher.spec.ts index ac57cf46f82..d2f967ebace 100644 --- a/packages/browsers/test/src/launcher.spec.ts +++ b/packages/browsers/test/src/launcher.spec.ts @@ -21,7 +21,11 @@ import path from 'path'; import {Browser, BrowserPlatform} from '../../lib/cjs/browsers/browsers.js'; import {fetch} from '../../lib/cjs/fetch.js'; -import {computeExecutablePath, launch} from '../../lib/cjs/launcher.js'; +import { + CDP_WEBSOCKET_ENDPOINT_REGEX, + computeExecutablePath, + launch, +} from '../../lib/cjs/launcher.js'; import {testChromeBuildId, testFirefoxBuildId} from './versions.js'; @@ -91,6 +95,7 @@ describe('launcher', () => { const process = launch({ executablePath, args: [ + '--headless=new', '--use-mock-keychain', '--disable-features=DialMediaRouteProvider', `--user-data-dir=${path.join(tmpDir, 'profile')}`, @@ -98,6 +103,27 @@ describe('launcher', () => { }); await process.close(); }); + + it('should allow parsing stderr output of the browser process', async () => { + const executablePath = computeExecutablePath({ + cacheDir: tmpDir, + browser: Browser.CHROME, + buildId: testChromeBuildId, + }); + const process = launch({ + executablePath, + args: [ + '--headless=new', + '--use-mock-keychain', + '--disable-features=DialMediaRouteProvider', + '--remote-debugging-port=9222', + `--user-data-dir=${path.join(tmpDir, 'profile')}`, + ], + }); + const url = await process.waitForLineOutput(CDP_WEBSOCKET_ENDPOINT_REGEX); + await process.close(); + assert.ok(url.startsWith('ws://127.0.0.1:9222/devtools/browser')); + }); }); describe('Firefox', function () {