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',
+ },
+ ]);
+ });
});