mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: ignore untrusted clicks when recording (#18796)
Fixes https://github.com/microsoft/playwright/issues/18776
This commit is contained in:
parent
1b0a8122ba
commit
59418aa6f3
@ -75,8 +75,10 @@ class Recorder {
|
|||||||
addEventListener(document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true),
|
addEventListener(document, 'mouseup', event => this._onMouseUp(event as MouseEvent), true),
|
||||||
addEventListener(document, 'mousemove', event => this._onMouseMove(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, 'mouseleave', event => this._onMouseLeave(event as MouseEvent), true),
|
||||||
addEventListener(document, 'focus', () => this._onFocus(true), true),
|
addEventListener(document, 'focus', event => event.isTrusted && this._onFocus(true), true),
|
||||||
addEventListener(document, 'scroll', () => {
|
addEventListener(document, 'scroll', event => {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
this._hoveredModel = null;
|
this._hoveredModel = null;
|
||||||
this._highlight.hideActionPoint();
|
this._highlight.hideActionPoint();
|
||||||
this._updateHighlight();
|
this._updateHighlight();
|
||||||
@ -156,6 +158,8 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onClick(event: MouseEvent) {
|
private _onClick(event: MouseEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
if (this._mode === 'inspecting')
|
if (this._mode === 'inspecting')
|
||||||
globalThis.__pw_recorderSetSelector(this._hoveredModel ? this._hoveredModel.selector : '');
|
globalThis.__pw_recorderSetSelector(this._hoveredModel ? this._hoveredModel.selector : '');
|
||||||
if (this._shouldIgnoreMouseEvent(event))
|
if (this._shouldIgnoreMouseEvent(event))
|
||||||
@ -204,6 +208,8 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onMouseDown(event: MouseEvent) {
|
private _onMouseDown(event: MouseEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
if (this._shouldIgnoreMouseEvent(event))
|
if (this._shouldIgnoreMouseEvent(event))
|
||||||
return;
|
return;
|
||||||
if (!this._performingAction)
|
if (!this._performingAction)
|
||||||
@ -212,6 +218,8 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onMouseUp(event: MouseEvent) {
|
private _onMouseUp(event: MouseEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
if (this._shouldIgnoreMouseEvent(event))
|
if (this._shouldIgnoreMouseEvent(event))
|
||||||
return;
|
return;
|
||||||
if (!this._performingAction)
|
if (!this._performingAction)
|
||||||
@ -219,6 +227,8 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onMouseMove(event: MouseEvent) {
|
private _onMouseMove(event: MouseEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
if (this._mode === 'none')
|
if (this._mode === 'none')
|
||||||
return;
|
return;
|
||||||
const target = this._deepEventTarget(event);
|
const target = this._deepEventTarget(event);
|
||||||
@ -229,6 +239,8 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onMouseLeave(event: MouseEvent) {
|
private _onMouseLeave(event: MouseEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
// Leaving iframe.
|
// Leaving iframe.
|
||||||
if (window.top !== window && this._deepEventTarget(event).nodeType === Node.DOCUMENT_NODE) {
|
if (window.top !== window && this._deepEventTarget(event).nodeType === Node.DOCUMENT_NODE) {
|
||||||
this._hoveredElement = null;
|
this._hoveredElement = null;
|
||||||
@ -339,6 +351,8 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onKeyDown(event: KeyboardEvent) {
|
private _onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
if (this._mode === 'inspecting') {
|
if (this._mode === 'inspecting') {
|
||||||
consumeEvent(event);
|
consumeEvent(event);
|
||||||
return;
|
return;
|
||||||
@ -376,6 +390,8 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _onKeyUp(event: KeyboardEvent) {
|
private _onKeyUp(event: KeyboardEvent) {
|
||||||
|
if (!event.isTrusted)
|
||||||
|
return;
|
||||||
if (this._mode === 'none')
|
if (this._mode === 'none')
|
||||||
return;
|
return;
|
||||||
if (!this._shouldGenerateKeyPressFor(event))
|
if (!this._shouldGenerateKeyPressFor(event))
|
||||||
|
@ -31,7 +31,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('button', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -52,6 +52,27 @@ test.describe('cli codegen', () => {
|
|||||||
expect(message.text()).toBe('click');
|
expect(message.text()).toBe('click');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('should ignore programmatic events', async ({ page, openRecorder }) => {
|
||||||
|
const recorder = await openRecorder();
|
||||||
|
|
||||||
|
await recorder.setContentAndWait(`<button onclick="console.log('click')">Submit</button>`);
|
||||||
|
|
||||||
|
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 }) => {
|
test('should click after same-document navigation', async ({ page, openRecorder, server }) => {
|
||||||
const recorder = await openRecorder();
|
const recorder = await openRecorder();
|
||||||
|
|
||||||
@ -74,7 +95,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('button', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
@ -95,16 +116,14 @@ test.describe('cli codegen', () => {
|
|||||||
</script>
|
</script>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const locator = await recorder.waitForHighlight(() => recorder.page.hover('canvas', {
|
const locator = await recorder.hoverOverElement('canvas', {
|
||||||
position: { x: 250, y: 250 },
|
position: { x: 250, y: 250 },
|
||||||
}));
|
});
|
||||||
expect(locator).toBe(`locator('canvas')`);
|
expect(locator).toBe(`locator('canvas')`);
|
||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
recorder.page.click('canvas', {
|
recorder.trustedClick(),
|
||||||
position: { x: 250, y: 250 },
|
|
||||||
})
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
@ -154,7 +173,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('button', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -182,13 +201,12 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
// Force highlight.
|
// Force highlight.
|
||||||
await recorder.hoverOverElement('span');
|
await recorder.hoverOverElement('span');
|
||||||
|
|
||||||
// Append text after highlight.
|
// Append text after highlight.
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.setAttribute('onclick', "console.log('click')");
|
div.setAttribute('onclick', "console.log('click')");
|
||||||
div.textContent = ' Some long text here ';
|
div.textContent = ' Some long text here ';
|
||||||
document.documentElement.appendChild(div);
|
document.body.appendChild(div);
|
||||||
});
|
});
|
||||||
|
|
||||||
const locator = await recorder.hoverOverElement('div');
|
const locator = await recorder.hoverOverElement('div');
|
||||||
@ -200,7 +218,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('div', 'click', { detail: 1 })
|
recorder.trustedMove('div').then(() => recorder.trustedClick()),
|
||||||
]);
|
]);
|
||||||
expect(sources.get('JavaScript').text).toContain(`
|
expect(sources.get('JavaScript').text).toContain(`
|
||||||
await page.getByText('Some long text here').click();`);
|
await page.getByText('Some long text here').click();`);
|
||||||
@ -585,7 +603,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [popup, sources] = await Promise.all([
|
const [popup, sources] = await Promise.all([
|
||||||
page.context().waitForEvent('page'),
|
page.context().waitForEvent('page'),
|
||||||
recorder.waitForOutput('JavaScript', 'waitForEvent'),
|
recorder.waitForOutput('JavaScript', 'waitForEvent'),
|
||||||
page.dispatchEvent('a', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -628,7 +646,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [, sources] = await Promise.all([
|
const [, sources] = await Promise.all([
|
||||||
page.waitForNavigation(),
|
page.waitForNavigation(),
|
||||||
recorder.waitForOutput('JavaScript', '.click()'),
|
recorder.waitForOutput('JavaScript', '.click()'),
|
||||||
page.dispatchEvent('a', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
|
@ -33,7 +33,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('button', 'click', { detail: 1 })
|
recorder.trustedClick()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -68,7 +68,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('button', 'click', { detail: 1 })
|
recorder.trustedClick()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -240,7 +240,7 @@ test.describe('cli codegen', () => {
|
|||||||
const [message, sources] = await Promise.all([
|
const [message, sources] = await Promise.all([
|
||||||
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
page.waitForEvent('console', msg => msg.type() !== 'error'),
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('div', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -271,7 +271,7 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
const [sources] = await Promise.all([
|
const [sources] = await Promise.all([
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('input', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -300,7 +300,7 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
const [sources] = await Promise.all([
|
const [sources] = await Promise.all([
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('input', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -329,7 +329,7 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
const [sources] = await Promise.all([
|
const [sources] = await Promise.all([
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('input', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
@ -358,7 +358,7 @@ test.describe('cli codegen', () => {
|
|||||||
|
|
||||||
const [sources] = await Promise.all([
|
const [sources] = await Promise.all([
|
||||||
recorder.waitForOutput('JavaScript', 'click'),
|
recorder.waitForOutput('JavaScript', 'click'),
|
||||||
page.dispatchEvent('input', 'click', { detail: 1 })
|
recorder.trustedClick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect.soft(sources.get('JavaScript').text).toContain(`
|
expect.soft(sources.get('JavaScript').text).toContain(`
|
||||||
|
@ -171,8 +171,22 @@ class Recorder {
|
|||||||
return new Promise(f => callback = f);
|
return new Promise(f => callback = f);
|
||||||
}
|
}
|
||||||
|
|
||||||
async hoverOverElement(selector: string): Promise<string> {
|
async hoverOverElement(selector: string, options?: { position?: { x: number, y: number }}): Promise<string> {
|
||||||
return this.waitForHighlight(() => this.page.dispatchEvent(selector, 'mousemove', { detail: 1 }));
|
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<string> {
|
async focusElement(selector: string): Promise<string> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user