diff --git a/docs/api/puppeteer.mouse.md b/docs/api/puppeteer.mouse.md index 681c3243..9f5710eb 100644 --- a/docs/api/puppeteer.mouse.md +++ b/docs/api/puppeteer.mouse.md @@ -87,5 +87,6 @@ await browser | [dragOver(target, data)](./puppeteer.mouse.dragover.md) | | Dispatches a dragover event. | | [drop(target, data)](./puppeteer.mouse.drop.md) | | Performs a dragenter, dragover, and drop in sequence. | | [move(x, y, options)](./puppeteer.mouse.move.md) | | Moves the mouse to the given coordinate. | +| [reset()](./puppeteer.mouse.reset.md) | | Resets the mouse to the default state: No buttons pressed; position at (0,0). | | [up(options)](./puppeteer.mouse.up.md) | | Releases the mouse. | | [wheel(options)](./puppeteer.mouse.wheel.md) | | Dispatches a mousewheel event. | diff --git a/docs/api/puppeteer.mouse.reset.md b/docs/api/puppeteer.mouse.reset.md new file mode 100644 index 00000000..70347535 --- /dev/null +++ b/docs/api/puppeteer.mouse.reset.md @@ -0,0 +1,19 @@ +--- +sidebar_label: Mouse.reset +--- + +# Mouse.reset() method + +Resets the mouse to the default state: No buttons pressed; position at (0,0). + +#### Signature: + +```typescript +class Mouse { + reset(): Promise; +} +``` + +**Returns:** + +Promise<void> diff --git a/packages/puppeteer-core/src/common/Input.ts b/packages/puppeteer-core/src/common/Input.ts index 4af29bd5..6cb489a1 100644 --- a/packages/puppeteer-core/src/common/Input.ts +++ b/packages/puppeteer-core/src/common/Input.ts @@ -598,6 +598,29 @@ export class Mouse { } } + /** + * Resets the mouse to the default state: No buttons pressed; position at + * (0,0). + */ + async reset(): Promise { + const actions = []; + for (const [flag, button] of [ + [MouseButtonFlag.Left, MouseButton.Left], + [MouseButtonFlag.Middle, MouseButton.Middle], + [MouseButtonFlag.Right, MouseButton.Right], + [MouseButtonFlag.Forward, MouseButton.Forward], + [MouseButtonFlag.Back, MouseButton.Back], + ] as const) { + if (this.#state.buttons & flag) { + actions.push(this.up({button: button})); + } + } + if (this.#state.position.x !== 0 || this.#state.position.y !== 0) { + actions.push(this.move(0, 0)); + } + await Promise.all(actions); + } + /** * Moves the mouse to the given coordinate. * diff --git a/test/TestExpectations.json b/test/TestExpectations.json index 907ce17f..b0060990 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -1457,6 +1457,12 @@ "parameters": ["chrome", "webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[mouse.spec] Mouse should reset properly", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["cdp", "firefox"], + "expectations": ["SKIP"] + }, { "testIdPattern": "[mouse.spec] Mouse should select the text with mouse", "platforms": ["win32"], diff --git a/test/src/mouse.spec.ts b/test/src/mouse.spec.ts index 6d7aec47..93fc885a 100644 --- a/test/src/mouse.spec.ts +++ b/test/src/mouse.spec.ts @@ -16,6 +16,8 @@ import os from 'os'; import expect from 'expect'; +import {MouseButton} from 'puppeteer-core'; +import {Page} from 'puppeteer-core/internal/api/Page.js'; import {KeyInput} from 'puppeteer-core/internal/common/USKeyboardLayout.js'; import { @@ -24,6 +26,16 @@ import { setupTestPageAndContextHooks, } from './mocha-utils.js'; +interface ClickData { + type: string; + detail: number; + clientX: number; + clientY: number; + isTrusted: boolean; + button: number; + buttons: number; +} + interface Dimensions { x: number; y: number; @@ -267,21 +279,16 @@ describe('Mouse', function () { await page.mouse.down(); await expect(page.mouse.down()).rejects.toBeInstanceOf(Error); }); - it('should not throw if clicking in parallel', async () => { - const {page, server} = getTestState(); - await page.goto(server.EMPTY_PAGE); - interface ClickData { - type: string; - detail: number; - clientX: number; - clientY: number; - isTrusted: boolean; - button: number; - buttons: number; - } + interface AddMouseDataListenersOptions { + includeMove?: boolean; + } - await page.evaluate(() => { + const addMouseDataListeners = ( + page: Page, + options: AddMouseDataListenersOptions = {} + ) => { + return page.evaluate(({includeMove}) => { const clicks: ClickData[] = []; const mouseEventListener = (event: MouseEvent) => { clicks.push({ @@ -295,10 +302,20 @@ describe('Mouse', function () { }); }; document.addEventListener('mousedown', mouseEventListener); + if (includeMove) { + document.addEventListener('mousemove', mouseEventListener); + } document.addEventListener('mouseup', mouseEventListener); document.addEventListener('click', mouseEventListener); (window as unknown as {clicks: ClickData[]}).clicks = clicks; - }); + }, options); + }; + + it('should not throw if clicking in parallel', async () => { + const {page, server} = getTestState(); + + await page.goto(server.EMPTY_PAGE); + await addMouseDataListeners(page); await Promise.all([page.mouse.click(0, 5), page.mouse.click(6, 10)]); @@ -351,4 +368,68 @@ describe('Mouse', function () { }, }); }); + + it('should reset properly', async () => { + const {page, server} = getTestState(); + + await page.goto(server.EMPTY_PAGE); + + await page.mouse.move(5, 5); + await Promise.all([ + page.mouse.down({button: MouseButton.Left}), + page.mouse.down({button: MouseButton.Middle}), + page.mouse.down({button: MouseButton.Right}), + ]); + + await addMouseDataListeners(page, {includeMove: true}); + await page.mouse.reset(); + + const data = await page.evaluate(() => { + return (window as unknown as {clicks: ClickData[]}).clicks; + }); + const commonAttrs = { + isTrusted: true, + clientY: 5, + clientX: 5, + }; + expect(data).toMatchObject([ + { + ...commonAttrs, + button: 0, + buttons: 6, + detail: 1, + type: 'mouseup', + }, + { + ...commonAttrs, + button: 0, + buttons: 6, + detail: 1, + type: 'click', + }, + { + ...commonAttrs, + button: 1, + buttons: 2, + detail: 0, + type: 'mouseup', + }, + { + ...commonAttrs, + button: 2, + buttons: 0, + detail: 0, + type: 'mouseup', + }, + { + ...commonAttrs, + button: 0, + buttons: 0, + clientX: 0, + clientY: 0, + detail: 0, + type: 'mousemove', + }, + ]); + }); });