Skip to content
Merged
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
9 changes: 9 additions & 0 deletions packages/playwright-core/src/server/debugController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,15 @@ function wireListeners(recorder: Recorder, debugController: DebugController) {
actions.push(action);
actionsChanged();
});
recorder.on(RecorderEvent.ModeChanged, (mode: Mode) => {
// Recording session has ended (mode switched to 'none' when the client
// stops recording, or 'standby' when the recorder toolbar pauses it):
// drop the accumulated actions, so that the next session does not leak
// them into the emitted source (the client would re-insert the stale
// last action into the editor).
if (mode === 'none' || mode === 'standby')
actions.length = 0;
});
recorder.on(RecorderEvent.SignalAdded, (signal: actions.SignalInContext) => {
const lastAction = actions.findLast(a => a.pageGuid === signal.pageGuid);
if (lastAction)
Expand Down
51 changes: 51 additions & 0 deletions tests/library/debug-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,57 @@ test('test', async ({ page }) => {
});
});

test('should not leak actions from the previous recording session', async ({ backend, connectedBrowser }, testInfo) => {
testInfo.annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/42218' });

const events: any[] = [];
backend.on('sourceChanged', event => events.push(event));

// Session 1: "Record at cursor" and record a click.
await backend.setRecorderMode({ mode: 'recording' }, undefined);
const context = await connectedBrowser.newContextForReuse();
const [page] = context.pages();
await page.setContent('<button>Submit</button>');
await page.getByRole('button', { name: 'Submit' }).click();
await expect.poll(() => events[events.length - 1]?.actions).toEqual([
` await page.goto('about:blank');`,
` await page.getByRole('button', { name: 'Submit' }).click();`,
]);

// Stop recording, like "Record at cursor" toggle off.
await backend.setRecorderMode({ mode: 'none' }, undefined);

// Session 2 on the same page: "Record at cursor" again, record another click.
// Avoid navigation here (setContent is recorded as a goto on Firefox),
// the assertion below should only contain the click.
events.length = 0;
await backend.setRecorderMode({ mode: 'recording' }, undefined);
await page.evaluate(() => {
document.body.innerHTML = '<button>Other</button>';
});
await page.getByRole('button', { name: 'Other' }).click();

// New session starts from scratch: previous session's actions must not be
// re-sent, otherwise the client re-inserts the stale last action into the editor.
await expect.poll(() => events[events.length - 1]?.actions).toEqual([
` await page.getByRole('button', { name: 'Other' }).click();`,
]);

// Pause via the recorder toolbar's Record toggle ("standby") must also
// start a new session. 'standby' is not settable via the protocol, only
// through the toolbar UI.
await page.click('x-pw-tool-item.record');
events.length = 0;
await backend.setRecorderMode({ mode: 'recording' }, undefined);
await page.evaluate(() => {
document.body.innerHTML = '<button>Third</button>';
});
await page.getByRole('button', { name: 'Third' }).click();
await expect.poll(() => events[events.length - 1]?.actions).toEqual([
` await page.getByRole('button', { name: 'Third' }).click();`,
]);
});

test('should reset routes before reuse', async ({ server, connectedBrowserFactory }) => {
const browser1 = await connectedBrowserFactory();
const context1 = await browser1.newContextForReuse();
Expand Down
Loading