mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: pass explicit recorder app factory (#32349)
This commit is contained in:
parent
0b5456d00b
commit
ec681ca78c
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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({}));
|
||||||
|
@ -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')
|
||||||
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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() {
|
||||||
|
@ -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 }) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user