Skip to content

Commit 588c439

Browse files
authored
Mouse improvements (#2291)
* some progress * some progress * Feature complete * some changes * Change current state order * reimplementation * Dispose Mouse * Fix tests compatibility * fix deadlock
1 parent afae69b commit 588c439

File tree

8 files changed

+566
-77
lines changed

8 files changed

+566
-77
lines changed

lib/PuppeteerSharp.Tests/MouseTests/MouseTests.cs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Threading.Tasks;
45
using PuppeteerSharp.Input;
56
using PuppeteerSharp.Tests.Attributes;
@@ -222,6 +223,219 @@ await Page.EvaluateFunctionAsync(@"() => {
222223
}, await Page.EvaluateExpressionAsync<DomPointInternal>("result"));
223224
}
224225

226+
[PuppeteerTest("mouse.spec.ts", "Mouse", "should throw if buttons are pressed incorrectly")]
227+
[Skip(SkipAttribute.Targets.Firefox)]
228+
public async Task ShouldThrowIfButtonsArePressedIncorrectly()
229+
{
230+
await Page.GoToAsync(TestConstants.EmptyPage);
231+
232+
await Page.Mouse.DownAsync();
233+
Assert.ThrowsAsync<PuppeteerException>(async () => await Page.Mouse.DownAsync());
234+
}
235+
236+
[PuppeteerTest("mouse.spec.ts", "Mouse", "should not throw if clicking in parallel")]
237+
[Skip(SkipAttribute.Targets.Firefox)]
238+
public async Task ShouldNotThrowIfClickingInParallel()
239+
{
240+
await Page.GoToAsync(TestConstants.EmptyPage);
241+
await AddMouseDataListenersAsync(Page);
242+
243+
await Task.WhenAll(
244+
Page.Mouse.ClickAsync(0, 5),
245+
Page.Mouse.ClickAsync(6, 10));
246+
247+
var data = await Page.EvaluateExpressionAsync<ClickData[]>("window.clicks");
248+
249+
Assert.AreEqual(
250+
new ClickData[]
251+
{
252+
new()
253+
{
254+
Type = "mousedown",
255+
Buttons = 1,
256+
Detail = 1,
257+
ClientX = 0,
258+
ClientY = 5,
259+
IsTrusted = true,
260+
Button = 0,
261+
},
262+
new()
263+
{
264+
Type = "mouseup",
265+
Buttons = 0,
266+
Detail = 1,
267+
ClientX = 0,
268+
ClientY = 5,
269+
IsTrusted = true,
270+
Button = 0,
271+
},
272+
new()
273+
{
274+
Type = "click",
275+
Buttons = 0,
276+
Detail = 1,
277+
ClientX = 0,
278+
ClientY = 5,
279+
IsTrusted = true,
280+
Button = 0,
281+
},
282+
},
283+
data.Take(3));
284+
285+
Assert.AreEqual(
286+
new ClickData[]
287+
{
288+
new()
289+
{
290+
Type = "mousedown",
291+
Buttons = 1,
292+
Detail = 1,
293+
ClientX = 6,
294+
ClientY = 10,
295+
IsTrusted = true,
296+
Button = 0,
297+
},
298+
new()
299+
{
300+
Type = "mouseup",
301+
Buttons = 0,
302+
Detail = 1,
303+
ClientX = 6,
304+
ClientY = 10,
305+
IsTrusted = true,
306+
Button = 0,
307+
},
308+
new()
309+
{
310+
Type = "click",
311+
Buttons = 0,
312+
Detail = 1,
313+
ClientX = 6,
314+
ClientY = 10,
315+
IsTrusted = true,
316+
Button = 0,
317+
},
318+
},
319+
data.Skip(3).Take(3));
320+
}
321+
322+
[PuppeteerTest("mouse.spec.ts", "Mouse", "should reset properly")]
323+
[Skip(SkipAttribute.Targets.Firefox)]
324+
public async Task ShouldResetProperly()
325+
{
326+
await Page.GoToAsync(TestConstants.EmptyPage);
327+
await Page.Mouse.MoveAsync(5, 5);
328+
329+
await Task.WhenAll(
330+
Page.Mouse.DownAsync(new ClickOptions() { Button = MouseButton.Left }),
331+
Page.Mouse.DownAsync(new ClickOptions() { Button = MouseButton.Middle }),
332+
Page.Mouse.DownAsync(new ClickOptions() { Button = MouseButton.Right }));
333+
334+
await AddMouseDataListenersAsync(Page, true);
335+
await Page.Mouse.ResetAsync();
336+
337+
var data = await Page.EvaluateExpressionAsync<ClickData[]>("window.clicks");
338+
339+
Assert.AreEqual(
340+
new ClickData[]
341+
{
342+
new()
343+
{
344+
Type = "mouseup",
345+
Buttons = 6,
346+
Detail = 1,
347+
ClientX = 5,
348+
ClientY = 5,
349+
IsTrusted = true,
350+
Button = 0,
351+
},
352+
new()
353+
{
354+
Type = "click",
355+
Buttons = 6,
356+
Detail = 1,
357+
ClientX = 5,
358+
ClientY = 5,
359+
IsTrusted = true,
360+
Button = 0,
361+
},
362+
new()
363+
{
364+
Type = "mouseup",
365+
Buttons = 2,
366+
Detail = 0,
367+
ClientX = 5,
368+
ClientY = 5,
369+
IsTrusted = true,
370+
Button = 1,
371+
},
372+
new()
373+
{
374+
Type = "mouseup",
375+
Buttons = 0,
376+
Detail = 0,
377+
ClientX = 5,
378+
ClientY = 5,
379+
IsTrusted = true,
380+
Button = 2,
381+
},
382+
new()
383+
{
384+
Type = "mousemove",
385+
Buttons = 0,
386+
Detail = 0,
387+
ClientX = 0,
388+
ClientY = 0,
389+
IsTrusted = true,
390+
Button = 0,
391+
},
392+
},
393+
data);
394+
}
395+
396+
private Task AddMouseDataListenersAsync(IPage page, bool includeMove = false)
397+
{
398+
return Page.EvaluateFunctionAsync(@"(includeMove) => {
399+
const clicks = [];
400+
const mouseEventListener = (event) => {
401+
clicks.push({
402+
type: event.type,
403+
detail: event.detail,
404+
clientX: event.clientX,
405+
clientY: event.clientY,
406+
isTrusted: event.isTrusted,
407+
button: event.button,
408+
buttons: event.buttons,
409+
});
410+
};
411+
document.addEventListener('mousedown', mouseEventListener);
412+
if (includeMove) {
413+
document.addEventListener('mousemove', mouseEventListener);
414+
}
415+
document.addEventListener('mouseup', mouseEventListener);
416+
document.addEventListener('click', mouseEventListener);
417+
window.clicks = clicks;
418+
}",
419+
includeMove);
420+
}
421+
422+
internal struct ClickData
423+
{
424+
public string Type { get; set; }
425+
426+
public int Detail { get; set; }
427+
428+
public int ClientX { get; set; }
429+
430+
public int ClientY { get; set; }
431+
432+
public bool IsTrusted { get; set; }
433+
434+
public int Button { get; set; }
435+
436+
public int Buttons { get; set; }
437+
}
438+
225439
internal struct WheelEventInternal
226440
{
227441
public WheelEventInternal(decimal deltaX, decimal deltaY)

lib/PuppeteerSharp/Input/IMouse.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
using System;
12
using System.Threading.Tasks;
23

34
namespace PuppeteerSharp.Input
45
{
56
/// <summary>
67
/// Provides methods to interact with the mouse.
78
/// </summary>
8-
public interface IMouse
9+
public interface IMouse : IDisposable
910
{
1011
/// <summary>
1112
/// Shortcut for <see cref="MoveAsync(decimal, decimal, MoveOptions)"/>, <see cref="DownAsync(ClickOptions)"/> and <see cref="UpAsync(ClickOptions)"/>.
@@ -94,5 +95,11 @@ public interface IMouse
9495
/// <param name="deltaY">Delta Y.</param>
9596
/// <returns>Task.</returns>
9697
Task WheelAsync(decimal deltaX, decimal deltaY);
98+
99+
/// <summary>
100+
/// Resets the mouse to the default state: No buttons pressed; position at (0,0).
101+
/// </summary>
102+
/// <returns>Task.</returns>
103+
Task ResetAsync();
97104
}
98105
}

0 commit comments

Comments
 (0)