Skip to content

Commit ba32a24

Browse files
authored
chore: push action in context to the recorder app (#36611)
1 parent 090e5aa commit ba32a24

File tree

13 files changed

+119
-75
lines changed

13 files changed

+119
-75
lines changed

packages/injected/src/recorder/recorder.ts

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ interface RecorderTool {
6060
onMouseLeave?(event: MouseEvent): void;
6161
onFocus?(event: Event): void;
6262
onScroll?(event: Event): void;
63-
onLoad?(event: Event): void;
6463
}
6564

6665
class NoneTool implements RecorderTool {
@@ -633,10 +632,6 @@ class JsonRecordActionTool implements RecorderTool {
633632
return 'pointer';
634633
}
635634

636-
install() {
637-
this._pushSnapshot();
638-
}
639-
640635
onClick(event: MouseEvent) {
641636
// in webkit, sliding a range element may trigger a click event with a different target if the mouse is released outside the element bounding box.
642637
// So we check the hovered element instead, and if it is a range input, we skip click handling
@@ -769,20 +764,6 @@ class JsonRecordActionTool implements RecorderTool {
769764
});
770765
}
771766

772-
onLoad() {
773-
this._pushSnapshot();
774-
}
775-
776-
private _pushSnapshot() {
777-
const { ariaSnapshot } = this._ariaSnapshot(this._recorder.document.body);
778-
this._recorder.recordAction({
779-
selector: '',
780-
name: 'assertSnapshot',
781-
signals: [],
782-
ariaSnapshot,
783-
});
784-
}
785-
786767
private _shouldIgnoreMouseEvent(event: MouseEvent): boolean {
787768
const target = this._recorder.deepEventTarget(event);
788769
const nodeName = target.nodeName;
@@ -1331,7 +1312,6 @@ export class Recorder {
13311312
addEventListener(this.document, 'mouseenter', event => this._onMouseEnter(event as MouseEvent), true),
13321313
addEventListener(this.document, 'focus', event => this._onFocus(event), true),
13331314
addEventListener(this.document, 'scroll', event => this._onScroll(event), true),
1334-
addEventListener(this.injectedScript.window, 'load', event => this._onLoad(event), true),
13351315
];
13361316

13371317
this.highlight.install();
@@ -1515,12 +1495,6 @@ export class Recorder {
15151495
this._currentTool.onFocus?.(event);
15161496
}
15171497

1518-
private _onLoad(event: Event) {
1519-
if (!event.isTrusted)
1520-
return;
1521-
this._currentTool.onLoad?.(event);
1522-
}
1523-
15241498
private _onScroll(event: Event) {
15251499
if (!event.isTrusted)
15261500
return;

packages/playwright-core/src/client/browserContext.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ import type * as api from '../../types/types';
4747
import type { URLMatch } from '../utils/isomorphic/urlMatch';
4848
import type { Platform } from './platform';
4949
import type * as channels from '@protocol/channels';
50+
import type * as actions from '@recorder/actions';
51+
52+
interface RecorderEventSink {
53+
actionAdded(page: Page, actionInContext: actions.ActionInContext): void;
54+
actionUpdated(page: Page, actionInContext: actions.ActionInContext): void;
55+
signalAdded(page: Page, signal: actions.SignalInContext): void;
56+
}
5057

5158
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel> implements api.BrowserContext {
5259
_pages = new Set<Page>();
@@ -71,7 +78,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
7178
_closingStatus: 'none' | 'closing' | 'closed' = 'none';
7279
private _closeReason: string | undefined;
7380
private _harRouters: HarRouter[] = [];
74-
private _onRecorderEventSink: ((event: string, data: any) => void) | undefined;
81+
private _onRecorderEventSink: RecorderEventSink | undefined;
7582

7683
static from(context: channels.BrowserContextChannel): BrowserContext {
7784
return (context as any)._object;
@@ -141,7 +148,14 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
141148
this._channel.on('requestFailed', ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, Page.fromNullable(page)));
142149
this._channel.on('requestFinished', params => this._onRequestFinished(params));
143150
this._channel.on('response', ({ response, page }) => this._onResponse(network.Response.from(response), Page.fromNullable(page)));
144-
this._channel.on('recorderEvent', ({ event, data }) => this._onRecorderEventSink?.(event, data));
151+
this._channel.on('recorderEvent', ({ event, data, page }) => {
152+
if (event === 'actionAdded')
153+
this._onRecorderEventSink?.actionAdded(Page.from(page), data as actions.ActionInContext);
154+
else if (event === 'actionUpdated')
155+
this._onRecorderEventSink?.actionUpdated(Page.from(page), data as actions.ActionInContext);
156+
else if (event === 'signalAdded')
157+
this._onRecorderEventSink?.signalAdded(Page.from(page), data as actions.SignalInContext);
158+
});
145159
this._closedPromise = new Promise(f => this.once(Events.BrowserContext.Close, f));
146160

147161
this._setEventToSubscriptionMapping(new Map<string, channels.BrowserContextUpdateSubscriptionParams['event']>([
@@ -501,9 +515,9 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
501515
await this._closedPromise;
502516
}
503517

504-
async _enableRecorder(params: channels.BrowserContextEnableRecorderParams, callback?: (event: string, data: any) => void) {
505-
if (callback)
506-
this._onRecorderEventSink = callback;
518+
async _enableRecorder(params: channels.BrowserContextEnableRecorderParams, eventSink?: RecorderEventSink) {
519+
if (eventSink)
520+
this._onRecorderEventSink = eventSink;
507521
await this._channel.enableRecorder(params);
508522
}
509523

packages/playwright-core/src/protocol/validator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -980,8 +980,9 @@ scheme.BrowserContextResponseEvent = tObject({
980980
page: tOptional(tChannel(['Page'])),
981981
});
982982
scheme.BrowserContextRecorderEventEvent = tObject({
983-
event: tString,
983+
event: tEnum(['actionAdded', 'actionUpdated', 'signalAdded']),
984984
data: tAny,
985+
page: tChannel(['Page']),
985986
});
986987
scheme.BrowserContextAddCookiesParams = tObject({
987988
cookies: tArray(tType('SetNetworkCookie')),

packages/playwright-core/src/server/debugController.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,10 @@ function wireListeners(recorder: Recorder, debugController: DebugController) {
221221
actions.push(action);
222222
actionsChanged();
223223
});
224-
recorder.on(RecorderEvent.SignalAdded, (signal: actions.Signal) => {
225-
const lastAction = actions[actions.length - 1];
224+
recorder.on(RecorderEvent.SignalAdded, (signal: actions.SignalInContext) => {
225+
const lastAction = actions.findLast(a => a.frame.pageGuid === signal.frame.pageGuid);
226226
if (lastAction)
227-
lastAction.action.signals.push(signal);
227+
lastAction.action.signals.push(signal.signal);
228228
actionsChanged();
229229
});
230230
}

packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
200200
page: PageDispatcher.fromNullable(this, request.frame()?._page.initializedOrUndefined()),
201201
});
202202
});
203-
this.addObjectListener(BrowserContext.Events.RecorderEvent, ({ event, data }: { event: string, data: any }) => {
204-
this._dispatchEvent('recorderEvent', { event, data });
203+
this.addObjectListener(BrowserContext.Events.RecorderEvent, ({ event, data, page }: { event: 'actionAdded' | 'actionUpdated' | 'signalAdded', data: any, page: Page }) => {
204+
this._dispatchEvent('recorderEvent', { event, data, page: PageDispatcher.from(this, page) });
205205
});
206206
}
207207

packages/playwright-core/src/server/recorder.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import type * as channels from '@protocol/channels';
4040
import type * as actions from '@recorder/actions';
4141
import type { CallLog, CallLogStatus, ElementInfo, Mode, OverlayState, Source, UIState } from '@recorder/recorderTypes';
4242
import type { RegisteredListener } from '../utils';
43-
import type { Signal } from '../../../recorder/src/actions';
4443

4544
const recorderSymbol = Symbol('recorderSymbol');
4645

@@ -65,7 +64,7 @@ export type RecorderEventMap = {
6564
[RecorderEvent.CallLogsUpdated]: [callLogs: CallLog[]];
6665
[RecorderEvent.UserSourcesChanged]: [sources: Source[]];
6766
[RecorderEvent.ActionAdded]: [action: actions.ActionInContext];
68-
[RecorderEvent.SignalAdded]: [signal: actions.Signal];
67+
[RecorderEvent.SignalAdded]: [signal: actions.SignalInContext];
6968
[RecorderEvent.PageNavigated]: [url: string];
7069
[RecorderEvent.ContextClosed]: [];
7170
};
@@ -120,14 +119,15 @@ export class Recorder extends EventEmitter<RecorderEventMap> implements Instrume
120119
this._recorderMode = params.recorderMode ?? 'default';
121120
this.handleSIGINT = params.handleSIGINT;
122121

123-
this._signalProcessor = new RecorderSignalProcessor();
124-
this._signalProcessor.on('action', (actionInContext: actions.ActionInContext) => {
125-
if (this._enabled)
126-
this.emit(RecorderEvent.ActionAdded, actionInContext);
127-
});
128-
this._signalProcessor.on('signal', (signal: Signal) => {
129-
if (this._enabled)
130-
this.emit(RecorderEvent.SignalAdded, signal);
122+
this._signalProcessor = new RecorderSignalProcessor({
123+
addAction: (actionInContext: actions.ActionInContext) => {
124+
if (this._enabled)
125+
this.emit(RecorderEvent.ActionAdded, actionInContext);
126+
},
127+
addSignal: (signal: actions.SignalInContext) => {
128+
if (this._enabled)
129+
this.emit(RecorderEvent.SignalAdded, signal);
130+
},
131131
});
132132

133133
context.on(BrowserContext.Events.BeforeClose, () => {
@@ -491,13 +491,15 @@ export class Recorder extends EventEmitter<RecorderEventMap> implements Instrume
491491

492492
private _describeMainFrame(page: Page): actions.FrameDescription {
493493
return {
494+
pageGuid: page.guid,
494495
pageAlias: this._pageAliases.get(page)!,
495496
framePath: [],
496497
};
497498
}
498499

499500
private async _describeFrame(frame: Frame): Promise<actions.FrameDescription> {
500501
return {
502+
pageGuid: frame._page.guid,
501503
pageAlias: this._pageAliases.get(frame._page)!,
502504
framePath: await generateFrameSelector(frame),
503505
};

packages/playwright-core/src/server/recorder/recorderApp.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export class RecorderApp {
228228
this._onActionAdded(action);
229229
});
230230

231-
recorder.on(RecorderEvent.SignalAdded, (signal: actions.Signal) => {
231+
recorder.on(RecorderEvent.SignalAdded, (signal: actions.SignalInContext) => {
232232
this._onSignalAdded(signal);
233233
});
234234

@@ -266,10 +266,10 @@ export class RecorderApp {
266266
this._updateActions();
267267
}
268268

269-
private _onSignalAdded(signal: actions.Signal) {
270-
const lastAction = this._actions[this._actions.length - 1];
269+
private _onSignalAdded(signal: actions.SignalInContext) {
270+
const lastAction = this._actions.findLast(a => a.frame.pageGuid === signal.frame.pageGuid);
271271
if (lastAction)
272-
lastAction.action.signals.push(signal);
272+
lastAction.action.signals.push(signal.signal);
273273
this._updateActions();
274274
}
275275

@@ -367,16 +367,24 @@ export class ProgrammaticRecorderApp {
367367
static async run(inspectedContext: BrowserContext, recorder: Recorder) {
368368
let lastAction: actions.ActionInContext | null = null;
369369
recorder.on(RecorderEvent.ActionAdded, action => {
370+
const page = findPageByGuid(inspectedContext, action.frame.pageGuid);
371+
if (!page)
372+
return;
370373
if (!lastAction || !shouldMergeAction(action, lastAction))
371-
inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'actionAdded', data: action });
374+
inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'actionAdded', data: action, page });
372375
else
373-
inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'actionUpdated', data: action });
376+
inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'actionUpdated', data: action, page });
374377
lastAction = action;
375378
});
376379
recorder.on(RecorderEvent.SignalAdded, signal => {
377-
inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'signalAdded', data: signal });
380+
const page = findPageByGuid(inspectedContext, signal.frame.pageGuid);
381+
inspectedContext.emit(BrowserContext.Events.RecorderEvent, { event: 'signalAdded', data: signal, page });
378382
});
379383
}
380384
}
381385

386+
function findPageByGuid(context: BrowserContext, guid: string) {
387+
return context.pages().find(p => p.guid === guid);
388+
}
389+
382390
const recorderAppSymbol = Symbol('recorderApp');

packages/playwright-core/src/server/recorder/recorderSignalProcessor.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,35 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { EventEmitter } from 'events';
18-
1917
import { isUnderTest } from '../utils/debug';
2018
import { monotonicTime } from '../../utils/isomorphic/time';
19+
import { generateFrameSelector } from './recorderUtils';
2120

2221
import type { Signal } from '../../../../recorder/src/actions';
2322
import type { Frame } from '../frames';
2423
import type * as actions from '@recorder/actions';
2524

26-
export class RecorderSignalProcessor extends EventEmitter {
25+
export interface ProcessorDelegate {
26+
addAction(actionInContext: actions.ActionInContext): void;
27+
addSignal(signalInContext: actions.SignalInContext): void;
28+
}
29+
30+
export class RecorderSignalProcessor {
31+
private _delegate: ProcessorDelegate;
2732
private _lastAction: actions.ActionInContext | null = null;
2833

29-
addAction(actionInContext: actions.ActionInContext, callback?: () => Promise<void>) {
34+
constructor(actionSink: ProcessorDelegate) {
35+
this._delegate = actionSink;
36+
}
37+
38+
addAction(actionInContext: actions.ActionInContext) {
3039
this._lastAction = actionInContext;
31-
this.emit('action', actionInContext);
40+
this._delegate.addAction(actionInContext);
3241
}
3342

3443
signal(pageAlias: string, frame: Frame, signal: Signal) {
44+
const timestamp = monotonicTime();
3545
if (signal.name === 'navigation' && frame._page.mainFrame() === frame) {
36-
const timestamp = monotonicTime();
3746
const lastAction = this._lastAction;
3847
const signalThreshold = isUnderTest() ? 500 : 5000;
3948

@@ -48,6 +57,7 @@ export class RecorderSignalProcessor extends EventEmitter {
4857
if (generateGoto) {
4958
this.addAction({
5059
frame: {
60+
pageGuid: frame._page.guid,
5161
pageAlias,
5262
framePath: [],
5363
},
@@ -63,6 +73,17 @@ export class RecorderSignalProcessor extends EventEmitter {
6373
return;
6474
}
6575

66-
this.emit('signal', signal);
76+
generateFrameSelector(frame).then(framePath => {
77+
const signalInContext: actions.SignalInContext = {
78+
frame: {
79+
pageGuid: frame._page.guid,
80+
pageAlias,
81+
framePath,
82+
},
83+
signal,
84+
timestamp,
85+
};
86+
this._delegate.addSignal(signalInContext);
87+
});
6788
}
6889
}

packages/playwright-mdd/src/recorderLoop.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,31 @@ import { runOneShot } from './loop';
2222

2323
import type { BrowserContext } from '../../playwright-core/src/client/browserContext';
2424
import type * as actions from '@recorder/actions';
25+
import type * as playwright from 'playwright-core';
2526

2627
export async function runRecorderLoop() {
2728
const browser = await chromium.launch({ headless: false });
2829
const context = await browser.newContext() as BrowserContext;
2930
await context._enableRecorder({
3031
mode: 'recording',
3132
recorderMode: 'api',
32-
}, async (event, data) => {
33-
if (event !== 'actionAdded')
34-
return;
35-
const action = data.action as actions.Action;
36-
if (action.name !== 'click' && action.name !== 'press') {
37-
console.log('============= action', action.name);
38-
return;
39-
}
40-
const response = await runOneShot(prompt(action)).catch(e => {
41-
console.error(e);
42-
});
43-
console.log(response);
33+
}, {
34+
actionAdded: (page: playwright.Page, actionInContext: actions.ActionInContext) => {
35+
const action = actionInContext.action;
36+
if (action.name !== 'click' && action.name !== 'press')
37+
return;
38+
runOneShot(prompt(action)).then(response => {
39+
console.log(response);
40+
}).catch(e => {
41+
console.error(e);
42+
});
43+
},
44+
actionUpdated: (page: playwright.Page, actionInContext: actions.ActionInContext) => {
45+
console.log('actionUpdated', actionInContext);
46+
},
47+
signalAdded: (page: playwright.Page, signal: actions.SignalInContext) => {
48+
console.log('signalAdded', signal);
49+
},
4450
});
4551
const page = await context.newPage();
4652
await page.goto('https://playwright.dev/');

packages/protocol/src/channels.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1717,8 +1717,9 @@ export type BrowserContextResponseEvent = {
17171717
page?: PageChannel,
17181718
};
17191719
export type BrowserContextRecorderEventEvent = {
1720-
event: string,
1720+
event: 'actionAdded' | 'actionUpdated' | 'signalAdded',
17211721
data: any,
1722+
page: PageChannel,
17221723
};
17231724
export type BrowserContextAddCookiesParams = {
17241725
cookies: SetNetworkCookie[],

0 commit comments

Comments
 (0)