diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts
index 924fbf73ff..f700bacc69 100644
--- a/packages/playwright-core/src/server/injected/recorder.ts
+++ b/packages/playwright-core/src/server/injected/recorder.ts
@@ -75,8 +75,10 @@ class Recorder {
addEventListener(document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true),
addEventListener(document, 'mousemove', event => this._onMouseMove(event as MouseEvent), true),
addEventListener(document, 'mouseleave', event => this._onMouseLeave(event as MouseEvent), true),
- addEventListener(document, 'focus', () => this._onFocus(true), true),
- addEventListener(document, 'scroll', () => {
+ addEventListener(document, 'focus', event => event.isTrusted && this._onFocus(true), true),
+ addEventListener(document, 'scroll', event => {
+ if (!event.isTrusted)
+ return;
this._hoveredModel = null;
this._highlight.hideActionPoint();
this._updateHighlight();
@@ -156,6 +158,8 @@ class Recorder {
}
private _onClick(event: MouseEvent) {
+ if (!event.isTrusted)
+ return;
if (this._mode === 'inspecting')
globalThis.__pw_recorderSetSelector(this._hoveredModel ? this._hoveredModel.selector : '');
if (this._shouldIgnoreMouseEvent(event))
@@ -204,6 +208,8 @@ class Recorder {
}
private _onMouseDown(event: MouseEvent) {
+ if (!event.isTrusted)
+ return;
if (this._shouldIgnoreMouseEvent(event))
return;
if (!this._performingAction)
@@ -212,6 +218,8 @@ class Recorder {
}
private _onMouseUp(event: MouseEvent) {
+ if (!event.isTrusted)
+ return;
if (this._shouldIgnoreMouseEvent(event))
return;
if (!this._performingAction)
@@ -219,6 +227,8 @@ class Recorder {
}
private _onMouseMove(event: MouseEvent) {
+ if (!event.isTrusted)
+ return;
if (this._mode === 'none')
return;
const target = this._deepEventTarget(event);
@@ -229,6 +239,8 @@ class Recorder {
}
private _onMouseLeave(event: MouseEvent) {
+ if (!event.isTrusted)
+ return;
// Leaving iframe.
if (window.top !== window && this._deepEventTarget(event).nodeType === Node.DOCUMENT_NODE) {
this._hoveredElement = null;
@@ -339,6 +351,8 @@ class Recorder {
}
private _onKeyDown(event: KeyboardEvent) {
+ if (!event.isTrusted)
+ return;
if (this._mode === 'inspecting') {
consumeEvent(event);
return;
@@ -376,6 +390,8 @@ class Recorder {
}
private _onKeyUp(event: KeyboardEvent) {
+ if (!event.isTrusted)
+ return;
if (this._mode === 'none')
return;
if (!this._shouldGenerateKeyPressFor(event))
diff --git a/tests/library/inspector/cli-codegen-1.spec.ts b/tests/library/inspector/cli-codegen-1.spec.ts
index 340306949a..f8c6a42960 100644
--- a/tests/library/inspector/cli-codegen-1.spec.ts
+++ b/tests/library/inspector/cli-codegen-1.spec.ts
@@ -31,7 +31,7 @@ test.describe('cli codegen', () => {
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('button', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -52,6 +52,27 @@ test.describe('cli codegen', () => {
expect(message.text()).toBe('click');
});
+
+ test('should ignore programmatic events', async ({ page, openRecorder }) => {
+ const recorder = await openRecorder();
+
+ await recorder.setContentAndWait(``);
+
+ const locator = await recorder.hoverOverElement('button');
+ expect(locator).toBe(`getByRole('button', { name: 'Submit' })`);
+
+ await page.dispatchEvent('button', 'click', { detail: 1 });
+
+ await Promise.all([
+ page.waitForEvent('console', msg => msg.type() !== 'error'),
+ recorder.waitForOutput('JavaScript', 'click'),
+ recorder.trustedClick()
+ ]);
+
+ const clicks = recorder.sources().get('Playwright Test').actions.filter(l => l.includes('Submit'));
+ expect(clicks.length).toBe(1);
+ });
+
test('should click after same-document navigation', async ({ page, openRecorder, server }) => {
const recorder = await openRecorder();
@@ -74,7 +95,7 @@ test.describe('cli codegen', () => {
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('button', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect(sources.get('JavaScript').text).toContain(`
@@ -95,16 +116,14 @@ test.describe('cli codegen', () => {
`);
- const locator = await recorder.waitForHighlight(() => recorder.page.hover('canvas', {
+ const locator = await recorder.hoverOverElement('canvas', {
position: { x: 250, y: 250 },
- }));
+ });
expect(locator).toBe(`locator('canvas')`);
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- recorder.page.click('canvas', {
- position: { x: 250, y: 250 },
- })
+ recorder.trustedClick(),
]);
expect(sources.get('JavaScript').text).toContain(`
@@ -154,7 +173,7 @@ test.describe('cli codegen', () => {
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('button', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -182,13 +201,12 @@ test.describe('cli codegen', () => {
// Force highlight.
await recorder.hoverOverElement('span');
-
// Append text after highlight.
await page.evaluate(() => {
const div = document.createElement('div');
div.setAttribute('onclick', "console.log('click')");
div.textContent = ' Some long text here ';
- document.documentElement.appendChild(div);
+ document.body.appendChild(div);
});
const locator = await recorder.hoverOverElement('div');
@@ -200,7 +218,7 @@ test.describe('cli codegen', () => {
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('div', 'click', { detail: 1 })
+ recorder.trustedMove('div').then(() => recorder.trustedClick()),
]);
expect(sources.get('JavaScript').text).toContain(`
await page.getByText('Some long text here').click();`);
@@ -585,7 +603,7 @@ test.describe('cli codegen', () => {
const [popup, sources] = await Promise.all([
page.context().waitForEvent('page'),
recorder.waitForOutput('JavaScript', 'waitForEvent'),
- page.dispatchEvent('a', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -628,7 +646,7 @@ test.describe('cli codegen', () => {
const [, sources] = await Promise.all([
page.waitForNavigation(),
recorder.waitForOutput('JavaScript', '.click()'),
- page.dispatchEvent('a', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
diff --git a/tests/library/inspector/cli-codegen-3.spec.ts b/tests/library/inspector/cli-codegen-3.spec.ts
index 4546708a13..21c635ff15 100644
--- a/tests/library/inspector/cli-codegen-3.spec.ts
+++ b/tests/library/inspector/cli-codegen-3.spec.ts
@@ -33,7 +33,7 @@ test.describe('cli codegen', () => {
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('button', 'click', { detail: 1 })
+ recorder.trustedClick()
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -68,7 +68,7 @@ test.describe('cli codegen', () => {
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('button', 'click', { detail: 1 })
+ recorder.trustedClick()
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -240,7 +240,7 @@ test.describe('cli codegen', () => {
const [message, sources] = await Promise.all([
page.waitForEvent('console', msg => msg.type() !== 'error'),
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('div', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -271,7 +271,7 @@ test.describe('cli codegen', () => {
const [sources] = await Promise.all([
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('input', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -300,7 +300,7 @@ test.describe('cli codegen', () => {
const [sources] = await Promise.all([
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('input', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -329,7 +329,7 @@ test.describe('cli codegen', () => {
const [sources] = await Promise.all([
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('input', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
@@ -358,7 +358,7 @@ test.describe('cli codegen', () => {
const [sources] = await Promise.all([
recorder.waitForOutput('JavaScript', 'click'),
- page.dispatchEvent('input', 'click', { detail: 1 })
+ recorder.trustedClick(),
]);
expect.soft(sources.get('JavaScript').text).toContain(`
diff --git a/tests/library/inspector/inspectorTest.ts b/tests/library/inspector/inspectorTest.ts
index d6577b1594..62cd6b51dd 100644
--- a/tests/library/inspector/inspectorTest.ts
+++ b/tests/library/inspector/inspectorTest.ts
@@ -171,8 +171,22 @@ class Recorder {
return new Promise(f => callback = f);
}
- async hoverOverElement(selector: string): Promise {
- return this.waitForHighlight(() => this.page.dispatchEvent(selector, 'mousemove', { detail: 1 }));
+ async hoverOverElement(selector: string, options?: { position?: { x: number, y: number }}): Promise {
+ return this.waitForHighlight(async () => {
+ const box = await this.page.locator(selector).first().boundingBox();
+ const offset = options?.position || { x: box.width / 2, y: box.height / 2 };
+ await this.page.mouse.move(box.x + offset.x, box.y + offset.y);
+ });
+ }
+
+ async trustedMove(selector: string) {
+ const box = await this.page.locator(selector).first().boundingBox();
+ await this.page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
+ }
+
+ async trustedClick() {
+ await this.page.mouse.down();
+ await this.page.mouse.up();
}
async focusElement(selector: string): Promise {