Skip to content

Commit 064b9dd

Browse files
authoredDec 20, 2021
feat: add resetMouse command (#1819)

File tree

6 files changed

+223
-102
lines changed

6 files changed

+223
-102
lines changed
 

‎.changeset/shy-singers-love.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@web/test-runner-commands': minor
3+
'@web/test-runner-webdriver': minor
4+
---
5+
6+
Add a resetMouse command that resets the mouse position and releases mouse buttons.

‎packages/test-runner-commands/browser/commands.d.ts

+27-2
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,15 @@ export function setUserAgent(userAgent: string): Promise<void>;
7676
export function sendKeys(payload: SendKeysPayload): Promise<void>;
7777

7878
/**
79-
* Sends an action for the mouse to move it to a specific position or click a mouse button (left, middle or right).
79+
* Sends an action for the mouse to move it to a specific position or click a mouse button (left, middle, or right).
8080
*
81-
* @param payload An object representing a mouse action specified by the `type` property (move, click, down, up) and including properties permitted for this type.
81+
* WARNING: When moving the mouse or holding down a mouse button, the mouse stays in this state as long as
82+
* you do not explicitly move it to another position or release the button. For this reason, it is recommended
83+
* to reset the mouse state with the `resetMouse` command after each test case manipulating the mouse to avoid
84+
* unexpected side effects.
85+
*
86+
* @param payload An object representing a mouse action specified by the `type` property (move, click, down, up)
87+
* and including some properties to configure this action.
8288
*
8389
* @example
8490
* ```ts
@@ -107,6 +113,25 @@ export function sendKeys(payload: SendKeysPayload): Promise<void>;
107113
**/
108114
export function sendMouse(payload: SendMousePayload): Promise<void>;
109115

116+
/**
117+
* Resets the mouse position to (0, 0) and releases mouse buttons.
118+
*
119+
* Use this command to reset the mouse state after mouse manipulations by the `sendMouse` command.
120+
*
121+
* @example
122+
* ```
123+
* it('does something with the mouse', () => {
124+
* await sendMouse({ type: 'move', position: [150, 150] });
125+
* await sendMouse({ type: 'down', button: 'middle' });
126+
* });
127+
*
128+
* afterEach(() => {
129+
* await resetMouse();
130+
* });
131+
* ```
132+
*/
133+
export function resetMouse(): Promise<void>;
134+
110135
/**
111136
* Request a snapshot of the Accessibility Tree of the entire page or starting from
112137
* the element that is obtained via the `selector` property of the `payload` argument.

‎packages/test-runner-commands/browser/commands.mjs

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export function sendMouse(options) {
7373
return executeServerCommand('send-mouse', options);
7474
}
7575

76+
export function resetMouse(options) {
77+
return executeServerCommand('reset-mouse', options);
78+
}
79+
7680
export function a11ySnapshot(options) {
7781
return executeServerCommand('a11y-snapshot', options);
7882
}

‎packages/test-runner-commands/src/sendMousePlugin.ts

+34
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,40 @@ export function sendMousePlugin(): TestRunnerPlugin<SendMousePayload> {
152152
// you might not be able to support all browser launchers
153153
throw new Error(`Sending mouse is not supported for browser type ${session.browser.type}.`);
154154
}
155+
156+
if (command === 'reset-mouse') {
157+
// handle specific behavior for playwright
158+
if (session.browser.type === 'playwright') {
159+
const page = (session.browser as PlaywrightLauncher).getPage(session.id);
160+
await page.mouse.up({ button: 'left' });
161+
await page.mouse.up({ button: 'middle' });
162+
await page.mouse.up({ button: 'right' });
163+
await page.mouse.move(0, 0);
164+
return true;
165+
}
166+
167+
// handle specific behavior for puppeteer
168+
if (session.browser.type === 'puppeteer') {
169+
const page = (session.browser as ChromeLauncher).getPage(session.id);
170+
await page.mouse.up({ button: 'left' });
171+
await page.mouse.up({ button: 'middle' });
172+
await page.mouse.up({ button: 'right' });
173+
await page.mouse.move(0, 0);
174+
return true;
175+
}
176+
177+
// handle specific behavior for webdriver
178+
if (session.browser.type === 'webdriver') {
179+
const page = session.browser as WebdriverLauncher;
180+
await page.resetMouse(session.id);
181+
return true;
182+
}
183+
184+
// you might not be able to support all browser launchers
185+
throw new Error(
186+
`Resetting mouse is not supported for browser type ${session.browser.type}.`,
187+
);
188+
}
155189
},
156190
};
157191
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { sendMouse } from '../../browser/commands.mjs';
1+
import { sendMouse, resetMouse } from '../../browser/commands.mjs';
22
import { expect } from '../chai.js';
33

44
function spyEvent() {
@@ -14,8 +14,6 @@ function spyEvent() {
1414
return callback;
1515
}
1616

17-
let element, x, y;
18-
1917
before(() => {
2018
// The native context menu needs to be prevented from opening at least in WebKit
2119
// where it doesn't get automatically closed that, in turn, blocks the next `mouseup` event.
@@ -24,145 +22,180 @@ before(() => {
2422
});
2523
});
2624

27-
beforeEach(() => {
28-
element = document.createElement('div');
29-
element.style.width = '100px';
30-
element.style.height = '100px';
31-
element.style.margin = '100px';
32-
33-
x = 150; // Horizontal middle of the element.
34-
y = 150; // Vertical middle of the element.
35-
36-
document.body.appendChild(element);
37-
});
25+
describe('sendMouse', () => {
26+
let element, x, y;
3827

39-
afterEach(() => {
40-
element.remove();
41-
});
28+
beforeEach(() => {
29+
element = document.createElement('div');
30+
element.style.width = '100px';
31+
element.style.height = '100px';
32+
element.style.margin = '100px';
4233

43-
describe('move', () => {
44-
let spy;
34+
x = 150; // Horizontal middle of the element.
35+
y = 150; // Vertical middle of the element.
4536

46-
beforeEach(() => {
47-
spy = spyEvent();
48-
document.addEventListener('mousemove', spy);
37+
document.body.appendChild(element);
4938
});
5039

5140
afterEach(() => {
52-
document.removeEventListener('mousemove', spy);
41+
element.remove();
5342
});
5443

55-
it('can move mouse to a position', async () => {
56-
await sendMouse({ type: 'move', position: [x, y] });
44+
describe('move', () => {
45+
let spy;
5746

58-
expect(spy.getLastEvent()).to.include({ x, y });
59-
});
60-
});
47+
beforeEach(() => {
48+
spy = spyEvent();
49+
document.addEventListener('mousemove', spy);
50+
});
6151

62-
describe('click', () => {
63-
let spy;
52+
afterEach(() => {
53+
document.removeEventListener('mousemove', spy);
54+
});
6455

65-
beforeEach(async () => {
66-
spy = spyEvent();
67-
element.addEventListener('mousedown', spy);
68-
element.addEventListener('mouseup', spy);
56+
it('can move mouse to a position', async () => {
57+
await sendMouse({ type: 'move', position: [x, y] });
6958

70-
await sendMouse({ type: 'move', position: [0, 0] });
59+
expect(spy.getLastEvent()).to.include({ x, y });
60+
});
7161
});
7262

73-
it('can click the left mouse button', async () => {
74-
await sendMouse({ type: 'click', position: [x, y], button: 'left' });
63+
describe('click', () => {
64+
let spy;
7565

76-
expect(spy.getEvents()).to.have.lengthOf(2);
77-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
78-
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 0, x, y });
79-
});
66+
beforeEach(async () => {
67+
spy = spyEvent();
68+
element.addEventListener('mousedown', spy);
69+
element.addEventListener('mouseup', spy);
8070

81-
it('should click the left mouse button by default', async () => {
82-
await sendMouse({ type: 'click', position: [x, y] });
71+
await sendMouse({ type: 'move', position: [0, 0] });
72+
});
8373

84-
expect(spy.getEvents()).to.have.lengthOf(2);
85-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
86-
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 0, x, y });
87-
});
74+
it('can click the left mouse button', async () => {
75+
await sendMouse({ type: 'click', position: [x, y], button: 'left' });
8876

89-
it('can click the middle mouse button', async () => {
90-
await sendMouse({ type: 'click', position: [x, y], button: 'middle' });
77+
expect(spy.getEvents()).to.have.lengthOf(2);
78+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
79+
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 0, x, y });
80+
});
9181

92-
expect(spy.getEvents()).to.have.lengthOf(2);
93-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 1, x, y });
94-
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 1, x, y });
95-
});
82+
it('should click the left mouse button by default', async () => {
83+
await sendMouse({ type: 'click', position: [x, y] });
9684

97-
it('can click the right mouse button', async () => {
98-
await sendMouse({ type: 'click', position: [x, y], button: 'right' });
85+
expect(spy.getEvents()).to.have.lengthOf(2);
86+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
87+
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 0, x, y });
88+
});
9989

100-
expect(spy.getEvents()).to.have.lengthOf(2);
101-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 2, x, y });
102-
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 2, x, y });
103-
});
104-
});
90+
it('can click the middle mouse button', async () => {
91+
await sendMouse({ type: 'click', position: [x, y], button: 'middle' });
10592

106-
describe('down and up', () => {
107-
let spy;
93+
expect(spy.getEvents()).to.have.lengthOf(2);
94+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 1, x, y });
95+
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 1, x, y });
96+
});
10897

109-
beforeEach(async () => {
110-
spy = spyEvent();
111-
element.addEventListener('mousedown', spy);
112-
element.addEventListener('mouseup', spy);
98+
it('can click the right mouse button', async () => {
99+
await sendMouse({ type: 'click', position: [x, y], button: 'right' });
113100

114-
await sendMouse({ type: 'move', position: [x, y] });
101+
expect(spy.getEvents()).to.have.lengthOf(2);
102+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 2, x, y });
103+
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 2, x, y });
104+
});
115105
});
116106

117-
it('can down and up the left mouse button', async () => {
118-
await sendMouse({ type: 'down', button: 'left' });
107+
describe('down and up', () => {
108+
let spy;
119109

120-
expect(spy.getEvents()).to.have.lengthOf(1);
121-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
110+
beforeEach(async () => {
111+
spy = spyEvent();
112+
element.addEventListener('mousedown', spy);
113+
element.addEventListener('mouseup', spy);
122114

123-
spy.resetHistory();
124-
await sendMouse({ type: 'up', button: 'left' });
115+
await sendMouse({ type: 'move', position: [x, y] });
116+
});
125117

126-
expect(spy.getEvents()).to.have.lengthOf(1);
127-
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 0, x, y });
128-
});
118+
it('can down and up the left mouse button', async () => {
119+
await sendMouse({ type: 'down', button: 'left' });
129120

130-
it('should down and up the left mouse button by default', async () => {
131-
await sendMouse({ type: 'down' });
121+
expect(spy.getEvents()).to.have.lengthOf(1);
122+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
132123

133-
expect(spy.getEvents()).to.have.lengthOf(1);
134-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
124+
spy.resetHistory();
125+
await sendMouse({ type: 'up', button: 'left' });
135126

136-
spy.resetHistory();
137-
await sendMouse({ type: 'up' });
127+
expect(spy.getEvents()).to.have.lengthOf(1);
128+
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 0, x, y });
129+
});
138130

139-
expect(spy.getEvents()).to.have.lengthOf(1);
140-
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 0, x, y });
141-
});
131+
it('should down and up the left mouse button by default', async () => {
132+
await sendMouse({ type: 'down' });
142133

143-
it('can down and up the middle mouse button', async () => {
144-
await sendMouse({ type: 'down', button: 'middle' });
134+
expect(spy.getEvents()).to.have.lengthOf(1);
135+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 0, x, y });
145136

146-
expect(spy.getEvents()).to.have.lengthOf(1);
147-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 1, x, y });
137+
spy.resetHistory();
138+
await sendMouse({ type: 'up' });
148139

149-
spy.resetHistory();
150-
await sendMouse({ type: 'up', button: 'middle' });
140+
expect(spy.getEvents()).to.have.lengthOf(1);
141+
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 0, x, y });
142+
});
143+
144+
it('can down and up the middle mouse button', async () => {
145+
await sendMouse({ type: 'down', button: 'middle' });
146+
147+
expect(spy.getEvents()).to.have.lengthOf(1);
148+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 1, x, y });
149+
150+
spy.resetHistory();
151+
await sendMouse({ type: 'up', button: 'middle' });
152+
153+
expect(spy.getEvents()).to.have.lengthOf(1);
154+
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 1, x, y });
155+
});
151156

152-
expect(spy.getEvents()).to.have.lengthOf(1);
153-
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 1, x, y });
157+
it('can down and up the right mouse button', async () => {
158+
await sendMouse({ type: 'down', button: 'right' });
159+
160+
expect(spy.getEvents()).to.have.lengthOf(1);
161+
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 2, x, y });
162+
163+
spy.resetHistory();
164+
await sendMouse({ type: 'up', button: 'right' });
165+
166+
expect(spy.getEvents()).to.have.lengthOf(1);
167+
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 2, x, y });
168+
});
154169
});
170+
});
155171

156-
it('can down and up the right mouse button', async () => {
157-
await sendMouse({ type: 'down', button: 'right' });
172+
describe('resetMouse', () => {
173+
let spy;
158174

159-
expect(spy.getEvents()).to.have.lengthOf(1);
160-
expect(spy.getEvents()[0]).to.include({ type: 'mousedown', button: 2, x, y });
175+
beforeEach(() => {
176+
spy = spyEvent();
177+
document.addEventListener('mouseup', spy);
178+
document.addEventListener('mousemove', spy);
179+
});
180+
181+
afterEach(() => {
182+
document.addEventListener('mouseup', spy);
183+
document.removeEventListener('mousemove', spy);
184+
});
185+
186+
it('can reset mouse', async () => {
187+
await sendMouse({ type: 'move', position: [100, 100] });
188+
await sendMouse({ type: 'down', button: 'left' });
189+
await sendMouse({ type: 'down', button: 'right' });
190+
await sendMouse({ type: 'down', button: 'middle' });
161191

162192
spy.resetHistory();
163-
await sendMouse({ type: 'up', button: 'right' });
193+
await resetMouse();
164194

165-
expect(spy.getEvents()).to.have.lengthOf(1);
166-
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 2, x, y });
195+
expect(spy.getEvents()).to.have.lengthOf(4);
196+
expect(spy.getEvents()[0]).to.include({ type: 'mouseup', button: 0 });
197+
expect(spy.getEvents()[1]).to.include({ type: 'mouseup', button: 1 });
198+
expect(spy.getEvents()[2]).to.include({ type: 'mouseup', button: 2 });
199+
expect(spy.getEvents()[3]).to.include({ type: 'mousemove', x: 0, y: 0 });
167200
});
168201
});

‎packages/test-runner-webdriver/src/webdriverLauncher.ts

+19
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,25 @@ export class WebdriverLauncher implements BrowserLauncher {
203203
]);
204204
}
205205

206+
resetMouse(sessionId: string) {
207+
if (!this.driverManager) {
208+
throw new Error('Not initialized');
209+
}
210+
211+
return this.driverManager.performActions(sessionId, [
212+
{
213+
type: 'pointer',
214+
id: 'finger1',
215+
actions: [
216+
{ type: 'pointerUp', button: getMouseButtonCode('left') },
217+
{ type: 'pointerUp', button: getMouseButtonCode('middle') },
218+
{ type: 'pointerUp', button: getMouseButtonCode('right') },
219+
{ type: 'pointerMove', duration: 0, x: 0, y: 0 },
220+
],
221+
},
222+
]);
223+
}
224+
206225
sendKeys(sessionId: string, keys: string[]) {
207226
if (!this.driverManager) {
208227
throw new Error('Not initialized');

0 commit comments

Comments
 (0)
Please sign in to comment.