Skip to content

Commit 16425fe

Browse files
authored
Add ClickOptions.Offset (Optional) (#1988)
- Add new Offset struct and replace BoxModelPoint usage for ClickablePointAsync - Add new Test case based on JSHandle.click should work - Add JSHandle.clickablePoint tests
1 parent df2f9b4 commit 16425fe

File tree

7 files changed

+177
-3
lines changed

7 files changed

+177
-3
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Collections.Generic;
2+
using System.Threading.Tasks;
3+
using PuppeteerSharp.Tests.Attributes;
4+
using PuppeteerSharp.Xunit;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
8+
namespace PuppeteerSharp.Tests.JSHandleTests
9+
{
10+
[Collection(TestConstants.TestFixtureCollectionName)]
11+
public class ClickTests : PuppeteerPageBaseTest
12+
{
13+
public ClickTests(ITestOutputHelper output) : base(output)
14+
{
15+
}
16+
17+
[PuppeteerTest("jshandle.spec.ts", "JSHandle.click", "should work")]
18+
[SkipBrowserFact(skipFirefox: true)]
19+
public async Task ShouldWork()
20+
{
21+
var clicks = new List<BoxModelPoint>();
22+
23+
await Page.ExposeFunctionAsync("reportClick", (int x, int y) =>
24+
{
25+
clicks.Add(new BoxModelPoint { X = x, Y = y });
26+
27+
return true;
28+
});
29+
30+
await Page.EvaluateExpressionAsync(@"document.body.style.padding = '0';
31+
document.body.style.margin = '0';
32+
document.body.innerHTML = '<div style=""cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;""></div>';
33+
document.body.addEventListener('click', e => {
34+
window.reportClick(e.clientX, e.clientY);
35+
});");
36+
37+
var divHandle = await Page.QuerySelectorAsync("div");
38+
39+
await divHandle.ClickAsync();
40+
await divHandle.ClickAsync(new Input.ClickOptions { OffSet = new Offset(10, 15) });
41+
42+
await TestUtils.ShortWaitForCollectionToHaveAtLeastNElementsAsync(clicks, 2);
43+
44+
// margin + middle point offset
45+
Assert.Equal(clicks[0].X, 45 + 60);
46+
Assert.Equal(clicks[0].Y, 45 + 30);
47+
48+
// margin + offset
49+
Assert.Equal(clicks[1].X, 30 + 10);
50+
Assert.Equal(clicks[1].Y, 30 + 15);
51+
}
52+
}
53+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Security.Policy;
5+
using System.Threading.Tasks;
6+
using PuppeteerSharp.Tests.Attributes;
7+
using PuppeteerSharp.Xunit;
8+
using Xunit;
9+
using Xunit.Abstractions;
10+
11+
namespace PuppeteerSharp.Tests.JSHandleTests
12+
{
13+
[Collection(TestConstants.TestFixtureCollectionName)]
14+
public class ClickablePointTests : PuppeteerPageBaseTest
15+
{
16+
public ClickablePointTests(ITestOutputHelper output) : base(output)
17+
{
18+
}
19+
20+
[PuppeteerTest("jshandle.spec.ts", "JSHandle.clickablePoint", "should work")]
21+
[PuppeteerFact]
22+
public async Task ShouldWork()
23+
{
24+
await Page.EvaluateExpressionAsync(@"document.body.style.padding = '0';
25+
document.body.style.margin = '0';
26+
document.body.innerHTML = '<div style=""cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;""></div>';
27+
");
28+
29+
await Page.EvaluateExpressionAsync("new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));");
30+
31+
var divHandle = await Page.QuerySelectorAsync("div");
32+
33+
var clickablePoint = await divHandle.ClickablePointAsync();
34+
35+
// margin + middle point offset
36+
Assert.Equal(45 + 60, clickablePoint.X);
37+
Assert.Equal(45 + 30, clickablePoint.Y);
38+
39+
clickablePoint = await divHandle.ClickablePointAsync(new Offset { X = 10, Y = 15 });
40+
41+
// margin + offset
42+
Assert.Equal(30 + 10, clickablePoint.X);
43+
Assert.Equal(30 + 15, clickablePoint.Y);
44+
}
45+
46+
[PuppeteerTest("jshandle.spec.ts", "JSHandle.clickablePoint", "should work for iframes")]
47+
[PuppeteerFact]
48+
public async Task ShouldWorkForIFrames()
49+
{
50+
await Page.EvaluateExpressionAsync(@"document.body.style.padding = '10px';
51+
document.body.style.margin = '10px';
52+
document.body.innerHTML = `<iframe style=""border: none; margin: 0; padding: 0;"" seamless sandbox srcdoc=""<style>* { margin: 0; padding: 0;}</style><div style='cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;' />""></iframe>`
53+
");
54+
55+
await Page.EvaluateExpressionAsync("new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));");
56+
57+
var frame = Page.FirstChildFrame();
58+
59+
var divHandle = await frame.QuerySelectorAsync("div");
60+
61+
var clickablePoint = await divHandle.ClickablePointAsync();
62+
63+
// iframe pos + margin + middle point offset
64+
Assert.Equal(20 + 45 + 60, clickablePoint.X);
65+
Assert.Equal(20 + 45 + 30, clickablePoint.Y);
66+
67+
clickablePoint = await divHandle.ClickablePointAsync(new Offset { X = 10, Y = 15 });
68+
69+
// iframe pos + margin + offset
70+
Assert.Equal(20 + 30 + 10, clickablePoint.X);
71+
Assert.Equal(20 + 30 + 15, clickablePoint.Y);
72+
}
73+
}
74+
}

lib/PuppeteerSharp.Tests/TestUtils.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections;
12
using System.IO;
23
using System.Text;
34
using System.Threading.Tasks;
@@ -6,6 +7,18 @@ namespace PuppeteerSharp.Tests
67
{
78
public static class TestUtils
89
{
10+
public static async Task ShortWaitForCollectionToHaveAtLeastNElementsAsync(ICollection collection, int minLength, int attempts = 3, int timeout = 50)
11+
{
12+
for (var i = 0; i < attempts; i++)
13+
{
14+
if (collection.Count >= minLength)
15+
{
16+
break;
17+
}
18+
await Task.Delay(timeout);
19+
}
20+
}
21+
922
public static string FindParentDirectory(string directory)
1023
{
1124
var current = Directory.GetCurrentDirectory();

lib/PuppeteerSharp/ElementHandle.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ public async Task HoverAsync()
179179
public async Task ClickAsync(ClickOptions options = null)
180180
{
181181
await ScrollIntoViewIfNeededAsync().ConfigureAwait(false);
182-
var clickablePoint = await ClickablePointAsync().ConfigureAwait(false);
182+
var clickablePoint = await ClickablePointAsync(options?.OffSet).ConfigureAwait(false);
183183
await Page.Mouse.ClickAsync(clickablePoint.X, clickablePoint.Y, options).ConfigureAwait(false);
184184
}
185185

@@ -469,7 +469,7 @@ public async Task DragAndDropAsync(IElementHandle target, int delay = 0)
469469
}
470470

471471
/// <inheritdoc/>
472-
public async Task<BoxModelPoint> ClickablePointAsync(BoxModelPoint? offset = null)
472+
public async Task<BoxModelPoint> ClickablePointAsync(Offset? offset = null)
473473
{
474474
GetContentQuadsResponse result = null;
475475

lib/PuppeteerSharp/IElementHandle.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public interface IElementHandle : IJSHandle
3030
/// <param name="offset">Optional offset</param>
3131
/// <exception cref="PuppeteerException">When the node is not visible or not an HTMLElement</exception>
3232
/// <returns>A <see cref="Task"/> that resolves to the clickable point</returns>
33-
public Task<BoxModelPoint> ClickablePointAsync(BoxModelPoint? offset = null);
33+
public Task<BoxModelPoint> ClickablePointAsync(Offset? offset = null);
3434

3535
/// <summary>
3636
/// Scrolls element into view if needed, and then uses <see cref="PuppeteerSharp.IPage.Mouse"/> to click in the center of the element.

lib/PuppeteerSharp/Input/ClickOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,10 @@ public class ClickOptions
1919
/// The button to use for the click. Defaults to <see cref="MouseButton.Left"/>
2020
/// </summary>
2121
public MouseButton Button { get; set; } = MouseButton.Left;
22+
23+
/// <summary>
24+
/// Offset for the clickable point relative to the top-left corner of the border-box.
25+
/// </summary>
26+
public Offset? OffSet { get; set; }
2227
}
2328
}

lib/PuppeteerSharp/Offset.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace PuppeteerSharp
2+
{
3+
/// <summary>
4+
/// Offset used in conjunction with <see cref="ElementHandle.ClickablePointAsync(Offset?)"/>
5+
/// </summary>
6+
public struct Offset
7+
{
8+
/// <summary>
9+
/// Initializes a new instance of the <see cref="Offset"/> struct.
10+
/// </summary>
11+
/// <param name="x">x-offset for the clickable point relative to the top-left corner of the border box.</param>
12+
/// <param name="y">y-offset for the clickable point relative to the top-left corner of the border box.</param>
13+
public Offset(decimal x, decimal y)
14+
{
15+
X = x;
16+
Y = y;
17+
}
18+
19+
/// <summary>
20+
/// x-offset for the clickable point relative to the top-left corner of the border box.
21+
/// </summary>
22+
public decimal X { get; set; }
23+
24+
/// <summary>
25+
/// y-offset for the clickable point relative to the top-left corner of the border box.
26+
/// </summary>
27+
public decimal Y { get; set; }
28+
}
29+
}

0 commit comments

Comments
 (0)