chore: pass explicit recorder app factory (#32349)

This commit is contained in:
Pavel Feldman 2024-08-27 20:24:19 -07:00 committed by GitHub
parent 0b5456d00b
commit ec681ca78c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 39 additions and 43 deletions

View File

@ -571,7 +571,6 @@ async function codegen(options: Options & { target: string, output?: string, tes
mode: 'recording', mode: 'recording',
testIdAttributeName, testIdAttributeName,
outputFile: outputFile ? path.resolve(outputFile) : undefined, outputFile: outputFile ? path.resolve(outputFile) : undefined,
handleSIGINT: false,
}); });
await openPage(context, url); await openPage(context, url);
} }

View File

@ -481,7 +481,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
mode?: 'recording' | 'inspecting', mode?: 'recording' | 'inspecting',
testIdAttributeName?: string, testIdAttributeName?: string,
outputFile?: string, outputFile?: string,
handleSIGINT?: boolean,
}) { }) {
await this._channel.recorderSupplementEnable(params); await this._channel.recorderSupplementEnable(params);
} }

View File

@ -961,7 +961,6 @@ scheme.BrowserContextRecorderSupplementEnableParams = tObject({
device: tOptional(tString), device: tOptional(tString),
saveStorage: tOptional(tString), saveStorage: tOptional(tString),
outputFile: tOptional(tString), outputFile: tOptional(tString),
handleSIGINT: tOptional(tBoolean),
omitCallTracking: tOptional(tBoolean), omitCallTracking: tOptional(tBoolean),
}); });
scheme.BrowserContextRecorderSupplementEnableResult = tOptional(tObject({})); scheme.BrowserContextRecorderSupplementEnableResult = tOptional(tObject({}));

View File

@ -43,6 +43,7 @@ import { BrowserContextAPIRequestContext } from './fetch';
import type { Artifact } from './artifact'; import type { Artifact } from './artifact';
import { Clock } from './clock'; import { Clock } from './clock';
import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor'; import { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
import { RecorderApp } from './recorder/recorderApp';
export abstract class BrowserContext extends SdkObject { export abstract class BrowserContext extends SdkObject {
static Events = { static Events = {
@ -130,13 +131,15 @@ export abstract class BrowserContext extends SdkObject {
// When PWDEBUG=1, show inspector for each context. // When PWDEBUG=1, show inspector for each context.
if (debugMode() === 'inspector') if (debugMode() === 'inspector')
await Recorder.show(this, { pauseOnNextStatement: true }); await Recorder.show(this, RecorderApp.factory(this), { pauseOnNextStatement: true });
// When paused, show inspector. // When paused, show inspector.
if (this._debugger.isPaused()) if (this._debugger.isPaused())
Recorder.showInspector(this); Recorder.showInspector(this, RecorderApp.factory(this));
this._debugger.on(Debugger.Events.PausedStateChanged, () => { this._debugger.on(Debugger.Events.PausedStateChanged, () => {
Recorder.showInspector(this); if (this._debugger.isPaused())
Recorder.showInspector(this, RecorderApp.factory(this));
}); });
if (debugMode() === 'console') if (debugMode() === 'console')

View File

@ -52,7 +52,6 @@ export class DebugController extends SdkObject {
initialize(codegenId: string, sdkLanguage: Language) { initialize(codegenId: string, sdkLanguage: Language) {
this._codegenId = codegenId; this._codegenId = codegenId;
this._sdkLanguage = sdkLanguage; this._sdkLanguage = sdkLanguage;
Recorder.setAppFactory(async () => new InspectingRecorderApp(this));
} }
setAutoCloseAllowed(allowed: boolean) { setAutoCloseAllowed(allowed: boolean) {
@ -62,7 +61,6 @@ export class DebugController extends SdkObject {
dispose() { dispose() {
this.setReportStateChanged(false); this.setReportStateChanged(false);
this.setAutoCloseAllowed(false); this.setAutoCloseAllowed(false);
Recorder.setAppFactory(undefined);
} }
setReportStateChanged(enabled: boolean) { setReportStateChanged(enabled: boolean) {
@ -199,7 +197,7 @@ export class DebugController extends SdkObject {
const contexts = new Set<BrowserContext>(); const contexts = new Set<BrowserContext>();
for (const page of this._playwright.allPages()) for (const page of this._playwright.allPages())
contexts.add(page.context()); contexts.add(page.context());
const result = await Promise.all([...contexts].map(c => Recorder.show(c, { omitCallTracking: true }))); const result = await Promise.all([...contexts].map(c => Recorder.show(c, () => Promise.resolve(new InspectingRecorderApp(this)), { omitCallTracking: true })));
return result.filter(Boolean) as Recorder[]; return result.filter(Boolean) as Recorder[];
} }

View File

@ -39,6 +39,7 @@ import type { Dialog } from '../dialog';
import type { ConsoleMessage } from '../console'; import type { ConsoleMessage } from '../console';
import { serializeError } from '../errors'; import { serializeError } from '../errors';
import { ElementHandleDispatcher } from './elementHandlerDispatcher'; import { ElementHandleDispatcher } from './elementHandlerDispatcher';
import { RecorderApp } from '../recorder/recorderApp';
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel { export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
_type_EventTarget = true; _type_EventTarget = true;
@ -291,7 +292,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
} }
async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> { async recorderSupplementEnable(params: channels.BrowserContextRecorderSupplementEnableParams): Promise<void> {
await Recorder.show(this._context, params); await Recorder.show(this._context, RecorderApp.factory(this._context), params);
} }
async pause(params: channels.BrowserContextPauseParams, metadata: CallMetadata) { async pause(params: channels.BrowserContextPauseParams, metadata: CallMetadata) {

View File

@ -26,12 +26,13 @@ import { type Language } from './codegen/types';
import { Debugger } from './debugger'; import { Debugger } from './debugger';
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation'; import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
import { ContextRecorder, generateFrameSelector } from './recorder/contextRecorder'; import { ContextRecorder, generateFrameSelector } from './recorder/contextRecorder';
import type { IRecorderApp } from './recorder/recorderApp'; import { type IRecorderApp } from './recorder/recorderApp';
import { EmptyRecorderApp, RecorderApp } from './recorder/recorderApp';
import { buildFullSelector, metadataToCallLog } from './recorder/recorderUtils'; import { buildFullSelector, metadataToCallLog } from './recorder/recorderUtils';
const recorderSymbol = Symbol('recorderSymbol'); const recorderSymbol = Symbol('recorderSymbol');
export type RecorderAppFactory = (recorder: Recorder) => Promise<IRecorderApp>;
export class Recorder implements InstrumentationListener { export class Recorder implements InstrumentationListener {
private _context: BrowserContext; private _context: BrowserContext;
private _mode: Mode; private _mode: Mode;
@ -43,40 +44,38 @@ export class Recorder implements InstrumentationListener {
private _userSources = new Map<string, Source>(); private _userSources = new Map<string, Source>();
private _debugger: Debugger; private _debugger: Debugger;
private _contextRecorder: ContextRecorder; private _contextRecorder: ContextRecorder;
private _handleSIGINT: boolean | undefined;
private _omitCallTracking = false; private _omitCallTracking = false;
private _currentLanguage: Language; private _currentLanguage: Language;
private static recorderAppFactory: ((recorder: Recorder) => Promise<IRecorderApp>) | undefined; static showInspector(context: BrowserContext, recorderAppFactory: RecorderAppFactory) {
static setAppFactory(recorderAppFactory: ((recorder: Recorder) => Promise<IRecorderApp>) | undefined) {
Recorder.recorderAppFactory = recorderAppFactory;
}
static showInspector(context: BrowserContext) {
const params: channels.BrowserContextRecorderSupplementEnableParams = {}; const params: channels.BrowserContextRecorderSupplementEnableParams = {};
if (isUnderTest()) if (isUnderTest())
params.language = process.env.TEST_INSPECTOR_LANGUAGE; params.language = process.env.TEST_INSPECTOR_LANGUAGE;
Recorder.show(context, params).catch(() => {}); Recorder.show(context, recorderAppFactory, params).catch(() => {});
} }
static show(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise<Recorder> { static show(context: BrowserContext, recorderAppFactory: RecorderAppFactory, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise<Recorder> {
let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder>; let recorderPromise = (context as any)[recorderSymbol] as Promise<Recorder>;
if (!recorderPromise) { if (!recorderPromise) {
const recorder = new Recorder(context, params); recorderPromise = Recorder._create(context, recorderAppFactory, params);
recorderPromise = recorder.install().then(() => recorder);
(context as any)[recorderSymbol] = recorderPromise; (context as any)[recorderSymbol] = recorderPromise;
} }
return recorderPromise; return recorderPromise;
} }
private static async _create(context: BrowserContext, recorderAppFactory: RecorderAppFactory, params: channels.BrowserContextRecorderSupplementEnableParams = {}): Promise<Recorder> {
const recorder = new Recorder(context, params);
const recorderApp = await recorderAppFactory(recorder);
await recorder._install(recorderApp);
return recorder;
}
constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) { constructor(context: BrowserContext, params: channels.BrowserContextRecorderSupplementEnableParams) {
this._mode = params.mode || 'none'; this._mode = params.mode || 'none';
this._contextRecorder = new ContextRecorder(context, params); this._contextRecorder = new ContextRecorder(context, params);
this._context = context; this._context = context;
this._omitCallTracking = !!params.omitCallTracking; this._omitCallTracking = !!params.omitCallTracking;
this._debugger = context.debugger(); this._debugger = context.debugger();
this._handleSIGINT = params.handleSIGINT;
context.instrumentation.addListener(this, context); context.instrumentation.addListener(this, context);
this._currentLanguage = this._contextRecorder.languageName(); this._currentLanguage = this._contextRecorder.languageName();
@ -86,14 +85,7 @@ export class Recorder implements InstrumentationListener {
} }
} }
private static async defaultRecorderAppFactory(recorder: Recorder) { private async _install(recorderApp: IRecorderApp) {
if (process.env.PW_CODEGEN_NO_INSPECTOR)
return new EmptyRecorderApp();
return await RecorderApp.open(recorder, recorder._context, recorder._handleSIGINT);
}
async install() {
const recorderApp = await (Recorder.recorderAppFactory || Recorder.defaultRecorderAppFactory)(this);
this._recorderApp = recorderApp; this._recorderApp = recorderApp;
recorderApp.once('close', () => { recorderApp.once('close', () => {
this._debugger.resume(false); this._debugger.resume(false);
@ -140,7 +132,7 @@ export class Recorder implements InstrumentationListener {
this._context.once(BrowserContext.Events.Close, () => { this._context.once(BrowserContext.Events.Close, () => {
this._contextRecorder.dispose(); this._contextRecorder.dispose();
this._context.instrumentation.removeListener(this); this._context.instrumentation.removeListener(this);
recorderApp.close().catch(() => {}); this._recorderApp?.close().catch(() => {});
}); });
this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], primaryFileName: string }) => { this._contextRecorder.on(ContextRecorder.Events.Change, (data: { sources: Source[], primaryFileName: string }) => {
this._recorderSources = data.sources; this._recorderSources = data.sources;
@ -201,7 +193,7 @@ export class Recorder implements InstrumentationListener {
this._pausedStateChanged(); this._pausedStateChanged();
this._debugger.on(Debugger.Events.PausedStateChanged, () => this._pausedStateChanged()); this._debugger.on(Debugger.Events.PausedStateChanged, () => this._pausedStateChanged());
(this._context as any).recorderAppForTest = recorderApp; (this._context as any).recorderAppForTest = this._recorderApp;
} }
_pausedStateChanged() { _pausedStateChanged() {

View File

@ -24,7 +24,7 @@ import type { CallLog, EventData, Mode, Source } from '@recorder/recorderTypes';
import { isUnderTest } from '../../utils'; import { isUnderTest } from '../../utils';
import { mime } from '../../utilsBundle'; import { mime } from '../../utilsBundle';
import { syncLocalStorageWithSettings } from '../launchApp'; import { syncLocalStorageWithSettings } from '../launchApp';
import type { Recorder } from '../recorder'; import type { Recorder, RecorderAppFactory } from '../recorder';
import type { BrowserContext } from '../browserContext'; import type { BrowserContext } from '../browserContext';
import { launchApp } from '../launchApp'; import { launchApp } from '../launchApp';
@ -113,7 +113,15 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
await mainFrame.goto(serverSideCallMetadata(), 'https://playwright/index.html'); await mainFrame.goto(serverSideCallMetadata(), 'https://playwright/index.html');
} }
static async open(recorder: Recorder, inspectedContext: BrowserContext, handleSIGINT: boolean | undefined): Promise<IRecorderApp> { static factory(context: BrowserContext): RecorderAppFactory {
return async recorder => {
if (process.env.PW_CODEGEN_NO_INSPECTOR)
return new EmptyRecorderApp();
return await RecorderApp._open(recorder, context);
};
}
private static async _open(recorder: Recorder, inspectedContext: BrowserContext): Promise<IRecorderApp> {
const sdkLanguage = inspectedContext.attribution.playwright.options.sdkLanguage; const sdkLanguage = inspectedContext.attribution.playwright.options.sdkLanguage;
const headed = !!inspectedContext._browser.options.headful; const headed = !!inspectedContext._browser.options.headful;
const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)({ sdkLanguage: 'javascript', isInternalPlaywright: true }); const recorderPlaywright = (require('../playwright').createPlaywright as typeof import('../playwright').createPlaywright)({ sdkLanguage: 'javascript', isInternalPlaywright: true });
@ -125,7 +133,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
noDefaultViewport: true, noDefaultViewport: true,
headless: !!process.env.PWTEST_CLI_HEADLESS || (isUnderTest() && !headed), headless: !!process.env.PWTEST_CLI_HEADLESS || (isUnderTest() && !headed),
useWebSocket: !!process.env.PWTEST_RECORDER_PORT, useWebSocket: !!process.env.PWTEST_RECORDER_PORT,
handleSIGINT, handleSIGINT: false,
args: process.env.PWTEST_RECORDER_PORT ? [`--remote-debugging-port=${process.env.PWTEST_RECORDER_PORT}`] : [], args: process.env.PWTEST_RECORDER_PORT ? [`--remote-debugging-port=${process.env.PWTEST_RECORDER_PORT}`] : [],
executablePath: inspectedContext._browser.options.isChromium ? inspectedContext._browser.options.customExecutablePath : undefined, executablePath: inspectedContext._browser.options.isChromium ? inspectedContext._browser.options.customExecutablePath : undefined,
} }
@ -170,11 +178,11 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
async setSelector(selector: string, userGesture?: boolean): Promise<void> { async setSelector(selector: string, userGesture?: boolean): Promise<void> {
if (userGesture) { if (userGesture) {
if (this._recorder.mode() === 'inspecting') { if (this._recorder?.mode() === 'inspecting') {
this._recorder.setMode('standby'); this._recorder.setMode('standby');
this._page.bringToFront(); this._page.bringToFront();
} else { } else {
this._recorder.setMode('recording'); this._recorder?.setMode('recording');
} }
} }
await this._page.mainFrame().evaluateExpression(((data: { selector: string, userGesture?: boolean }) => { await this._page.mainFrame().evaluateExpression(((data: { selector: string, userGesture?: boolean }) => {

View File

@ -1753,7 +1753,6 @@ export type BrowserContextRecorderSupplementEnableParams = {
device?: string, device?: string,
saveStorage?: string, saveStorage?: string,
outputFile?: string, outputFile?: string,
handleSIGINT?: boolean,
omitCallTracking?: boolean, omitCallTracking?: boolean,
}; };
export type BrowserContextRecorderSupplementEnableOptions = { export type BrowserContextRecorderSupplementEnableOptions = {
@ -1766,7 +1765,6 @@ export type BrowserContextRecorderSupplementEnableOptions = {
device?: string, device?: string,
saveStorage?: string, saveStorage?: string,
outputFile?: string, outputFile?: string,
handleSIGINT?: boolean,
omitCallTracking?: boolean, omitCallTracking?: boolean,
}; };
export type BrowserContextRecorderSupplementEnableResult = void; export type BrowserContextRecorderSupplementEnableResult = void;

View File

@ -1189,7 +1189,6 @@ BrowserContext:
device: string? device: string?
saveStorage: string? saveStorage: string?
outputFile: string? outputFile: string?
handleSIGINT: boolean?
omitCallTracking: boolean? omitCallTracking: boolean?
newCDPSession: newCDPSession: