Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/playwright-core/src/client/DEPS.list
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[*]
../protocol/
../utils/isomorphic
../utils/isomorphic/codegen
41 changes: 29 additions & 12 deletions packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { headersObjectToArray } from '../utils/isomorphic/headers';
import { urlMatchesEqual } from '../utils/isomorphic/urlMatch';
import { isRegExp, isString } from '../utils/isomorphic/rtti';
import { rewriteErrorMessage } from '../utils/isomorphic/stackTrace';
import { generateCode, languageSet } from '../utils/isomorphic/codegen/codegen';

import type { BrowserContextOptions, Headers, SetStorageState, StorageState, WaitForEventOptions } from './types';
import type * as structs from '../../types/structs';
Expand Down Expand Up @@ -78,7 +79,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
_closingStatus: 'none' | 'closing' | 'closed' = 'none';
private _closeReason: string | undefined;
private _harRouters: HarRouter[] = [];
private _onRecorderEventSink: RecorderEventSink | undefined;
private _onRecorderEventListener?: (args: channels.BrowserContextRecorderEventEvent) => void;

static from(context: channels.BrowserContextChannel): BrowserContext {
return (context as any)._object;
Expand Down Expand Up @@ -148,14 +149,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
this._channel.on('requestFailed', ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, Page.fromNullable(page)));
this._channel.on('requestFinished', params => this._onRequestFinished(params));
this._channel.on('response', ({ response, page }) => this._onResponse(network.Response.from(response), Page.fromNullable(page)));
this._channel.on('recorderEvent', ({ event, data, page, code }) => {
if (event === 'actionAdded')
this._onRecorderEventSink?.actionAdded?.(Page.from(page), data as actions.ActionInContext, code);
else if (event === 'actionUpdated')
this._onRecorderEventSink?.actionUpdated?.(Page.from(page), data as actions.ActionInContext, code);
else if (event === 'signalAdded')
this._onRecorderEventSink?.signalAdded?.(Page.from(page), data as actions.SignalInContext);
});
this._channel.on('recorderEvent', args => this._onRecorderEventListener?.(args));
this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f));

this._setEventToSubscriptionMapping(new Map<string, channels.BrowserContextUpdateSubscriptionParams['event']>([
Expand Down Expand Up @@ -520,13 +514,36 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
}

async _enableRecorder(params: channels.BrowserContextEnableRecorderParams, eventSink?: RecorderEventSink) {
if (eventSink)
this._onRecorderEventSink = eventSink;
if (eventSink) {
const languages = [...languageSet()];
const languageGeneratorOptions = {
browserName: this._browser?._name ?? 'chromium',
launchOptions: { headless: false, ...params.launchOptions, tracesDir: undefined },
contextOptions: { ...params.contextOptions },
deviceName: params.device,
saveStorage: params.saveStorage,
};
const languageGenerator = languages.find(l => l.id === params.language) ?? languages.find(l => l.id === 'playwright-test')!;

this._onRecorderEventListener = ({ event, data, page }) => {
if (event === 'actionAdded') {
const action = data as actions.ActionInContext;
const { actionTexts } = generateCode([action], languageGenerator, languageGeneratorOptions);
eventSink.actionAdded?.(Page.from(page), action, actionTexts.join('\n'));
} else if (event === 'actionUpdated') {
const action = data as actions.ActionInContext;
const { actionTexts } = generateCode([action], languageGenerator, languageGeneratorOptions);
eventSink.actionUpdated?.(Page.from(page), action, actionTexts.join('\n'));
} else if (event === 'signalAdded') {
eventSink.signalAdded?.(Page.from(page), data as actions.SignalInContext);
}
};
}
await this._channel.enableRecorder(params);
}

async _disableRecorder() {
this._onRecorderEventSink = undefined;
this._onRecorderEventListener = undefined;
await this._channel.disableRecorder();
}
}
Expand Down
1 change: 0 additions & 1 deletion packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,6 @@ scheme.BrowserContextRecorderEventEvent = tObject({
event: tEnum(['actionAdded', 'actionUpdated', 'signalAdded']),
data: tAny,
page: tChannel(['Page']),
code: tString,
});
scheme.BrowserContextAddCookiesParams = tObject({
cookies: tArray(tType('SetNetworkCookie')),
Expand Down
1 change: 1 addition & 0 deletions packages/playwright-core/src/server/DEPS.list
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
../protocol/
../utils
../utils/isomorphic/
../utils/isomorphic/codegen
../utilsBundle.ts
../zipBundle.ts
./
Expand Down
5 changes: 0 additions & 5 deletions packages/playwright-core/src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export abstract class BrowserContext extends SdkObject {
RequestContinued: 'requestcontinued',
BeforeClose: 'beforeclose',
VideoStarted: 'videostarted',
RecorderEvent: 'recorderevent',
};

readonly _pageBindings = new Map<string, PageBinding>();
Expand Down Expand Up @@ -130,10 +129,6 @@ export abstract class BrowserContext extends SdkObject {
if (debugMode() === 'inspector')
await RecorderApp.show(this, { pauseOnNextStatement: true });

// When paused, show inspector.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did this go?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was dead code. We are initializing the browser context, cannot be already paused here.

if (this._debugger.isPaused())
RecorderApp.showInspectorNoReply(this);

this._debugger.on(Debugger.Events.PausedStateChanged, () => {
if (this._debugger.isPaused())
RecorderApp.showInspectorNoReply(this);
Expand Down
3 changes: 0 additions & 3 deletions packages/playwright-core/src/server/codegen/DEPS.list

This file was deleted.

37 changes: 0 additions & 37 deletions packages/playwright-core/src/server/codegen/languages.ts

This file was deleted.

38 changes: 0 additions & 38 deletions packages/playwright-core/src/server/codegen/types.ts

This file was deleted.

12 changes: 7 additions & 5 deletions packages/playwright-core/src/server/debugController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ import { asLocator } from '../utils';
import { parseAriaSnapshotUnsafe } from '../utils/isomorphic/ariaSnapshot';
import { yaml } from '../utilsBundle';
import { unsafeLocatorOrSelectorAsSelector } from '../utils/isomorphic/locatorParser';
import { generateCode } from './codegen/language';
import { collapseActions } from './recorder/recorderUtils';
import { JavaScriptLanguageGenerator } from './codegen/javascript';
import { generateCode } from '../utils/isomorphic/codegen/codegen';
import { JavaScriptLanguageGenerator } from '../utils/isomorphic/codegen/javascript';

import type { Language } from '../utils';
import type { BrowserContext } from './browserContext';
Expand Down Expand Up @@ -176,8 +175,7 @@ function wireListeners(recorder: Recorder, debugController: DebugController) {
const languageGenerator = new JavaScriptLanguageGenerator(/* isPlaywrightTest */true);

const actionsChanged = () => {
const aa = collapseActions(actions);
const { header, footer, text, actionTexts } = generateCode(aa, languageGenerator, {
const { header, footer, text, actionTexts } = generateCode(actions, languageGenerator, {
browserName: 'chromium',
launchOptions: {},
contextOptions: {},
Expand All @@ -199,6 +197,10 @@ function wireListeners(recorder: Recorder, debugController: DebugController) {
actions.push(action);
actionsChanged();
});
recorder.on(RecorderEvent.ActionUpdated, (action: actions.ActionInContext) => {
actions[actions.length - 1] = action;
actionsChanged();
});
recorder.on(RecorderEvent.SignalAdded, (signal: actions.SignalInContext) => {
const lastAction = actions.findLast(a => a.frame.pageGuid === signal.frame.pageGuid);
if (lastAction)
Expand Down
3 changes: 0 additions & 3 deletions packages/playwright-core/src/server/debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import { methodMetainfo } from '../utils/isomorphic/protocolMetainfo';

import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';

const symbol = Symbol('Debugger');

export class Debugger extends EventEmitter implements InstrumentationListener {
private _pauseOnNextStatement = false;
private _pausedCallsMetadata = new Map<CallMetadata, { resolve: () => void, sdkObject: SdkObject }>();
Expand All @@ -39,7 +37,6 @@ export class Debugger extends EventEmitter implements InstrumentationListener {
constructor(context: BrowserContext) {
super();
this._context = context;
(this._context as any)[symbol] = this;
this._enabled = debugMode() === 'inspector';
if (this._enabled)
this.pauseOnNextStatement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ import { WebSocketRouteDispatcher } from './webSocketRouteDispatcher';
import { WritableStreamDispatcher } from './writableStreamDispatcher';
import { createGuid } from '../utils/crypto';
import { urlMatches } from '../../utils/isomorphic/urlMatch';
import { Recorder } from '../recorder';
import { Recorder, RecorderEvent } from '../recorder';
import { RecorderApp } from '../recorder/recorderApp';
import { eventsHelper, RegisteredListener } from '../utils/eventsHelper';

import type { Artifact } from '../artifact';
import type { ConsoleMessage } from '../console';
Expand All @@ -45,6 +46,7 @@ import type { InitScript, Page, PageBinding } from '../page';
import type { DispatcherScope } from './dispatcher';
import type * as channels from '@protocol/channels';
import type { Progress } from '@protocol/progress';
import type * as actions from '@recorder/actions';

export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channels.BrowserContextChannel, DispatcherScope> implements channels.BrowserContextChannel {
_type_EventTarget = true;
Expand All @@ -59,6 +61,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
private _requestInterceptor: RouteHandler;
private _interceptionUrlMatchers: (string | RegExp)[] = [];
private _routeWebSocketInitScript: InitScript | undefined;
private _recorderListeners: RegisteredListener[] = [];

static from(parentScope: DispatcherScope, context: BrowserContext): BrowserContextDispatcher {
const result = parentScope.connection.existingDispatcher<BrowserContextDispatcher>(context);
Expand Down Expand Up @@ -200,9 +203,6 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
page: PageDispatcher.fromNullable(this, request.frame()?._page.initializedOrUndefined()),
});
});
this.addObjectListener(BrowserContext.Events.RecorderEvent, ({ event, data, page, code }: { event: 'actionAdded' | 'actionUpdated' | 'signalAdded', data: any, page: Page, code: string }) => {
this._dispatchEvent('recorderEvent', { event, data, code, page: PageDispatcher.from(this, page) });
});
}

private _shouldDispatchNetworkEvent(request: Request, event: channels.BrowserContextUpdateSubscriptionParams['event'] & channels.PageUpdateSubscriptionParams['event']): boolean {
Expand Down Expand Up @@ -340,13 +340,37 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
}

async enableRecorder(params: channels.BrowserContextEnableRecorderParams, progress: Progress): Promise<void> {
await RecorderApp.show(this._context, params);
if (params.recorderMode !== 'api') {
await RecorderApp.show(this._context, params);
return;
}

const findPageByGuid = (guid: string) => this._context.pages().find(p => p.guid === guid);
const recorder = await Recorder.forContext(this._context, params);
this._recorderListeners = [
eventsHelper.addEventListener(recorder, RecorderEvent.ActionAdded, (action: actions.ActionInContext) => {
const page = findPageByGuid(action.frame.pageGuid);
if (page)
this._dispatchEvent('recorderEvent', { event: 'actionAdded', data: action, page: PageDispatcher.from(this, page) });
}),
eventsHelper.addEventListener(recorder, RecorderEvent.ActionUpdated, (action: actions.ActionInContext) => {
const page = findPageByGuid(action.frame.pageGuid);
if (page)
this._dispatchEvent('recorderEvent', { event: 'actionUpdated', data: action, page: PageDispatcher.from(this, page) });
}),
eventsHelper.addEventListener(recorder, RecorderEvent.SignalAdded, (signal: actions.SignalInContext) => {
const page = findPageByGuid(signal.frame.pageGuid);
if (page)
this._dispatchEvent('recorderEvent', { event: 'signalAdded', data: signal, page: PageDispatcher.from(this, page) });
}),
];
}

async disableRecorder(params: channels.BrowserContextDisableRecorderParams, progress: Progress): Promise<void> {
const recorder = Recorder.existingForContext(this._context);
if (recorder)
recorder.setMode('none');
eventsHelper.removeEventListeners(this._recorderListeners);
}

async pause(params: channels.BrowserContextPauseParams, progress: Progress) {
Expand Down Expand Up @@ -442,5 +466,6 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
if (this._clockPaused)
this._context.clock.resumeNoReply();
this._clockPaused = false;
eventsHelper.removeEventListeners(this._recorderListeners);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Dispatcher } from './dispatcher';
import { SdkObject } from '../../server/instrumentation';
import * as localUtils from '../localUtils';
import { getUserAgent } from '../utils/userAgent';
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
import { deviceDescriptors as descriptors } from '../../utils/isomorphic/deviceDescriptors';
import { JsonPipeDispatcher } from '../dispatchers/jsonPipeDispatcher';
import { Progress } from '../progress';
import { SocksInterceptor } from '../socksInterceptor';
Expand Down
12 changes: 8 additions & 4 deletions packages/playwright-core/src/server/recorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ import { Frame } from './frames';
import { Page } from './page';
import { performAction } from './recorder/recorderRunner';

import type { Language } from './codegen/types';
import type { CallMetadata, InstrumentationListener, SdkObject } from './instrumentation';
import type { Point } from '../utils/isomorphic/types';
import type { Point } from '@isomorphic/types';
import type { AriaTemplateNode } from '@isomorphic/ariaSnapshot';
import type { Language } from '@isomorphic/locatorGenerators';
import type * as channels from '@protocol/channels';
import type * as actions from '@recorder/actions';
import type { CallLog, CallLogStatus, ElementInfo, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
Expand All @@ -52,6 +52,7 @@ export const RecorderEvent = {
CallLogsUpdated: 'callLogsUpdated',
UserSourcesChanged: 'userSourcesChanged',
ActionAdded: 'actionAdded',
ActionUpdated: 'actionUpdated',
SignalAdded: 'signalAdded',
PageNavigated: 'pageNavigated',
ContextClosed: 'contextClosed',
Expand All @@ -64,13 +65,13 @@ export type RecorderEventMap = {
[RecorderEvent.CallLogsUpdated]: [callLogs: CallLog[]];
[RecorderEvent.UserSourcesChanged]: [sources: Source[]];
[RecorderEvent.ActionAdded]: [action: actions.ActionInContext];
[RecorderEvent.ActionUpdated]: [action: actions.ActionInContext];
[RecorderEvent.SignalAdded]: [signal: actions.SignalInContext];
[RecorderEvent.PageNavigated]: [url: string];
[RecorderEvent.ContextClosed]: [];
};

export class Recorder extends EventEmitter<RecorderEventMap> implements InstrumentationListener {
readonly handleSIGINT: boolean | undefined;
private _context: BrowserContext;
private _params: channels.BrowserContextEnableRecorderParams;
private _mode: Mode;
Expand Down Expand Up @@ -117,13 +118,16 @@ export class Recorder extends EventEmitter<RecorderEventMap> implements Instrume
this._params = params;
this._mode = params.mode || 'none';
this._recorderMode = params.recorderMode ?? 'default';
this.handleSIGINT = params.handleSIGINT;

this._signalProcessor = new RecorderSignalProcessor({
addAction: (actionInContext: actions.ActionInContext) => {
if (this._enabled)
this.emit(RecorderEvent.ActionAdded, actionInContext);
},
updateAction: (actionInContext: actions.ActionInContext) => {
if (this._enabled)
this.emit(RecorderEvent.ActionUpdated, actionInContext);
},
addSignal: (signal: actions.SignalInContext) => {
if (this._enabled)
this.emit(RecorderEvent.SignalAdded, signal);
Expand Down
Loading