chore: move output parsing to concrete browsers (#32129)

This commit is contained in:
Yury Semikhatsky 2024-08-13 12:20:41 -07:00 committed by GitHub
parent 0588834307
commit 6cc53cfce6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 14 deletions

View file

@ -38,12 +38,18 @@ import { helper } from './helper';
import { RecentLogsCollector } from '../utils/debugLogger'; import { RecentLogsCollector } from '../utils/debugLogger';
import type { CallMetadata } from './instrumentation'; import type { CallMetadata } from './instrumentation';
import { SdkObject } from './instrumentation'; import { SdkObject } from './instrumentation';
import { ManualPromise } from '../utils/manualPromise';
import { type ProtocolError, isProtocolError } from './protocolError'; import { type ProtocolError, isProtocolError } from './protocolError';
export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' + export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' +
'Set either \'headless: true\' or use \'xvfb-run <your-playwright-app>\' before running Playwright.\n\n<3 Playwright Team'; 'Set either \'headless: true\' or use \'xvfb-run <your-playwright-app>\' before running Playwright.\n\n<3 Playwright Team';
export interface BrowserReadyState {
onBrowserOutput(message: string): void;
onBrowserExit(): void;
waitUntilReady(): Promise<{ wsEndpoint?: string }>;
}
export abstract class BrowserType extends SdkObject { export abstract class BrowserType extends SdkObject {
private _name: BrowserName; private _name: BrowserName;
@ -190,8 +196,7 @@ export abstract class BrowserType extends SdkObject {
await registry.validateHostRequirementsForExecutablesIfNeeded([registryExecutable], this.attribution.playwright.options.sdkLanguage); await registry.validateHostRequirementsForExecutablesIfNeeded([registryExecutable], this.attribution.playwright.options.sdkLanguage);
} }
const waitForWSEndpoint = (options.useWebSocket || options.args?.some(a => a.startsWith('--remote-debugging-port'))) ? new ManualPromise<string>() : undefined; const readyState = this.readyState(options);
const waitForJuggler = this._name === 'firefox' ? new ManualPromise<void>() : undefined;
// Note: it is important to define these variables before launchProcess, so that we don't get // Note: it is important to define these variables before launchProcess, so that we don't get
// "Cannot access 'browserServer' before initialization" if something went wrong. // "Cannot access 'browserServer' before initialization" if something went wrong.
let transport: ConnectionTransport | undefined = undefined; let transport: ConnectionTransport | undefined = undefined;
@ -204,13 +209,7 @@ export abstract class BrowserType extends SdkObject {
handleSIGTERM, handleSIGTERM,
handleSIGHUP, handleSIGHUP,
log: (message: string) => { log: (message: string) => {
if (waitForWSEndpoint) { readyState?.onBrowserOutput(message);
const match = message.match(/DevTools listening on (.*)/);
if (match)
waitForWSEndpoint.resolve(match[1]);
}
if (waitForJuggler && message.includes('Juggler listening to the pipe'))
waitForJuggler.resolve();
progress.log(message); progress.log(message);
browserLogsCollector.log(message); browserLogsCollector.log(message);
}, },
@ -226,7 +225,7 @@ export abstract class BrowserType extends SdkObject {
}, },
onExit: (exitCode, signal) => { onExit: (exitCode, signal) => {
// Unblock launch when browser prematurely exits. // Unblock launch when browser prematurely exits.
waitForJuggler?.resolve(); readyState?.onBrowserExit();
if (browserProcess && browserProcess.onclose) if (browserProcess && browserProcess.onclose)
browserProcess.onclose(exitCode, signal); browserProcess.onclose(exitCode, signal);
}, },
@ -251,8 +250,7 @@ export abstract class BrowserType extends SdkObject {
kill kill
}; };
progress.cleanupWhenAborted(() => closeOrKill(progress.timeUntilDeadline())); progress.cleanupWhenAborted(() => closeOrKill(progress.timeUntilDeadline()));
const wsEndpoint = await waitForWSEndpoint; const wsEndpoint = (await readyState?.waitUntilReady())?.wsEndpoint;
await waitForJuggler;
if (options.useWebSocket) { if (options.useWebSocket) {
transport = await WebSocketTransport.connect(progress, wsEndpoint!); transport = await WebSocketTransport.connect(progress, wsEndpoint!);
} else { } else {
@ -308,6 +306,10 @@ export abstract class BrowserType extends SdkObject {
return this.doRewriteStartupLog(error); return this.doRewriteStartupLog(error);
} }
readyState(options: types.LaunchOptions): BrowserReadyState|undefined {
return undefined;
}
abstract defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[]; abstract defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[];
abstract connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<Browser>; abstract connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<Browser>;
abstract amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env; abstract amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env;

View file

@ -24,6 +24,7 @@ import type { Env } from '../../utils/processLauncher';
import { gracefullyCloseSet } from '../../utils/processLauncher'; import { gracefullyCloseSet } from '../../utils/processLauncher';
import { kBrowserCloseMessageId } from './crConnection'; import { kBrowserCloseMessageId } from './crConnection';
import { BrowserType, kNoXServerRunningError } from '../browserType'; import { BrowserType, kNoXServerRunningError } from '../browserType';
import type { BrowserReadyState } from '../browserType';
import type { ConnectionTransport, ProtocolRequest } from '../transport'; import type { ConnectionTransport, ProtocolRequest } from '../transport';
import { WebSocketTransport } from '../transport'; import { WebSocketTransport } from '../transport';
import { CRDevTools } from './crDevTools'; import { CRDevTools } from './crDevTools';
@ -349,6 +350,29 @@ export class Chromium extends BrowserType {
chromeArguments.push(...args); chromeArguments.push(...args);
return chromeArguments; return chromeArguments;
} }
override readyState(options: types.LaunchOptions): BrowserReadyState | undefined {
if (options.useWebSocket || options.args?.some(a => a.startsWith('--remote-debugging-port')))
return new ChromiumReadyState();
return undefined;
}
}
class ChromiumReadyState implements BrowserReadyState {
private readonly _wsEndpoint = new ManualPromise<string|undefined>();
onBrowserOutput(message: string): void {
const match = message.match(/DevTools listening on (.*)/);
if (match)
this._wsEndpoint.resolve(match[1]);
}
onBrowserExit(): void {
this._wsEndpoint.resolve(undefined);
}
async waitUntilReady(): Promise<{ wsEndpoint?: string }> {
const wsEndpoint = await this._wsEndpoint;
return { wsEndpoint };
}
} }
async function urlToWSEndpoint(progress: Progress, endpointURL: string, headers: { [key: string]: string; }) { async function urlToWSEndpoint(progress: Progress, endpointURL: string, headers: { [key: string]: string; }) {

View file

@ -20,11 +20,12 @@ import path from 'path';
import { FFBrowser } from './ffBrowser'; import { FFBrowser } from './ffBrowser';
import { kBrowserCloseMessageId } from './ffConnection'; import { kBrowserCloseMessageId } from './ffConnection';
import { BrowserType, kNoXServerRunningError } from '../browserType'; import { BrowserType, kNoXServerRunningError } from '../browserType';
import type { BrowserReadyState } from '../browserType';
import type { Env } from '../../utils/processLauncher'; import type { Env } from '../../utils/processLauncher';
import type { ConnectionTransport } from '../transport'; import type { ConnectionTransport } from '../transport';
import type { BrowserOptions } from '../browser'; import type { BrowserOptions } from '../browser';
import type * as types from '../types'; import type * as types from '../types';
import { wrapInASCIIBox } from '../../utils'; import { ManualPromise, wrapInASCIIBox } from '../../utils';
import type { SdkObject } from '../instrumentation'; import type { SdkObject } from '../instrumentation';
import type { ProtocolError } from '../protocolError'; import type { ProtocolError } from '../protocolError';
@ -88,5 +89,26 @@ export class Firefox extends BrowserType {
firefoxArguments.push('-silent'); firefoxArguments.push('-silent');
return firefoxArguments; return firefoxArguments;
} }
override readyState(options: types.LaunchOptions): BrowserReadyState | undefined {
return new JugglerReadyState();
}
}
class JugglerReadyState implements BrowserReadyState {
private readonly _jugglerPromise = new ManualPromise<void>();
onBrowserOutput(message: string): void {
if (message.includes('Juggler listening to the pipe'))
this._jugglerPromise.resolve();
}
onBrowserExit(): void {
// Unblock launch when browser prematurely exits.
this._jugglerPromise.resolve();
}
async waitUntilReady(): Promise<{ wsEndpoint?: string }> {
await this._jugglerPromise;
return { };
}
} }