Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ClickOptions.Offset (Optional) #1988

Merged
merged 1 commit into from Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 53 additions & 0 deletions lib/PuppeteerSharp.Tests/JSHandleTests/ClickTests.cs
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using PuppeteerSharp.Tests.Attributes;
using PuppeteerSharp.Xunit;
using Xunit;
using Xunit.Abstractions;

namespace PuppeteerSharp.Tests.JSHandleTests
{
[Collection(TestConstants.TestFixtureCollectionName)]
public class ClickTests : PuppeteerPageBaseTest
{
public ClickTests(ITestOutputHelper output) : base(output)
{
}

[PuppeteerTest("jshandle.spec.ts", "JSHandle.click", "should work")]
[SkipBrowserFact(skipFirefox: true)]
public async Task ShouldWork()
{
var clicks = new List<BoxModelPoint>();

await Page.ExposeFunctionAsync("reportClick", (int x, int y) =>
{
clicks.Add(new BoxModelPoint { X = x, Y = y });

return true;
});

await Page.EvaluateExpressionAsync(@"document.body.style.padding = '0';
document.body.style.margin = '0';
document.body.innerHTML = '<div style=""cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;""></div>';
document.body.addEventListener('click', e => {
window.reportClick(e.clientX, e.clientY);
});");

var divHandle = await Page.QuerySelectorAsync("div");

await divHandle.ClickAsync();
await divHandle.ClickAsync(new Input.ClickOptions { OffSet = new Offset(10, 15) });

await TestUtils.ShortWaitForCollectionToHaveAtLeastNElementsAsync(clicks, 2);

// margin + middle point offset
Assert.Equal(clicks[0].X, 45 + 60);
Assert.Equal(clicks[0].Y, 45 + 30);

// margin + offset
Assert.Equal(clicks[1].X, 30 + 10);
Assert.Equal(clicks[1].Y, 30 + 15);
}
}
}
74 changes: 74 additions & 0 deletions lib/PuppeteerSharp.Tests/JSHandleTests/ClickablePointTests.cs
@@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Policy;
using System.Threading.Tasks;
using PuppeteerSharp.Tests.Attributes;
using PuppeteerSharp.Xunit;
using Xunit;
using Xunit.Abstractions;

namespace PuppeteerSharp.Tests.JSHandleTests
{
[Collection(TestConstants.TestFixtureCollectionName)]
public class ClickablePointTests : PuppeteerPageBaseTest
{
public ClickablePointTests(ITestOutputHelper output) : base(output)
{
}

[PuppeteerTest("jshandle.spec.ts", "JSHandle.clickablePoint", "should work")]
[PuppeteerFact]
public async Task ShouldWork()
{
await Page.EvaluateExpressionAsync(@"document.body.style.padding = '0';
document.body.style.margin = '0';
document.body.innerHTML = '<div style=""cursor: pointer; width: 120px; height: 60px; margin: 30px; padding: 15px;""></div>';
");

await Page.EvaluateExpressionAsync("new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));");

var divHandle = await Page.QuerySelectorAsync("div");

var clickablePoint = await divHandle.ClickablePointAsync();

// margin + middle point offset
Assert.Equal(45 + 60, clickablePoint.X);
Assert.Equal(45 + 30, clickablePoint.Y);

clickablePoint = await divHandle.ClickablePointAsync(new Offset { X = 10, Y = 15 });

// margin + offset
Assert.Equal(30 + 10, clickablePoint.X);
Assert.Equal(30 + 15, clickablePoint.Y);
}

[PuppeteerTest("jshandle.spec.ts", "JSHandle.clickablePoint", "should work for iframes")]
[PuppeteerFact]
public async Task ShouldWorkForIFrames()
{
await Page.EvaluateExpressionAsync(@"document.body.style.padding = '10px';
document.body.style.margin = '10px';
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>`
");

await Page.EvaluateExpressionAsync("new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));");

var frame = Page.FirstChildFrame();

var divHandle = await frame.QuerySelectorAsync("div");

var clickablePoint = await divHandle.ClickablePointAsync();

// iframe pos + margin + middle point offset
Assert.Equal(20 + 45 + 60, clickablePoint.X);
Assert.Equal(20 + 45 + 30, clickablePoint.Y);

clickablePoint = await divHandle.ClickablePointAsync(new Offset { X = 10, Y = 15 });

// iframe pos + margin + offset
Assert.Equal(20 + 30 + 10, clickablePoint.X);
Assert.Equal(20 + 30 + 15, clickablePoint.Y);
}
}
}
13 changes: 13 additions & 0 deletions lib/PuppeteerSharp.Tests/TestUtils.cs
@@ -1,3 +1,4 @@
using System.Collections;
using System.IO;
using System.Text;
using System.Threading.Tasks;
Expand All @@ -6,6 +7,18 @@ namespace PuppeteerSharp.Tests
{
public static class TestUtils
{
public static async Task ShortWaitForCollectionToHaveAtLeastNElementsAsync(ICollection collection, int minLength, int attempts = 3, int timeout = 50)
{
for (var i = 0; i < attempts; i++)
{
if (collection.Count >= minLength)
{
break;
}
await Task.Delay(timeout);
}
}

public static string FindParentDirectory(string directory)
{
var current = Directory.GetCurrentDirectory();
Expand Down
4 changes: 2 additions & 2 deletions lib/PuppeteerSharp/ElementHandle.cs
Expand Up @@ -179,7 +179,7 @@ public async Task HoverAsync()
public async Task ClickAsync(ClickOptions options = null)
{
await ScrollIntoViewIfNeededAsync().ConfigureAwait(false);
var clickablePoint = await ClickablePointAsync().ConfigureAwait(false);
var clickablePoint = await ClickablePointAsync(options?.OffSet).ConfigureAwait(false);
await Page.Mouse.ClickAsync(clickablePoint.X, clickablePoint.Y, options).ConfigureAwait(false);
}

Expand Down Expand Up @@ -469,7 +469,7 @@ public async Task DragAndDropAsync(IElementHandle target, int delay = 0)
}

/// <inheritdoc/>
public async Task<BoxModelPoint> ClickablePointAsync(BoxModelPoint? offset = null)
public async Task<BoxModelPoint> ClickablePointAsync(Offset? offset = null)
{
GetContentQuadsResponse result = null;

Expand Down
2 changes: 1 addition & 1 deletion lib/PuppeteerSharp/IElementHandle.cs
Expand Up @@ -30,7 +30,7 @@ public interface IElementHandle : IJSHandle
/// <param name="offset">Optional offset</param>
/// <exception cref="PuppeteerException">When the node is not visible or not an HTMLElement</exception>
/// <returns>A <see cref="Task"/> that resolves to the clickable point</returns>
public Task<BoxModelPoint> ClickablePointAsync(BoxModelPoint? offset = null);
public Task<BoxModelPoint> ClickablePointAsync(Offset? offset = null);

/// <summary>
/// Scrolls element into view if needed, and then uses <see cref="PuppeteerSharp.IPage.Mouse"/> to click in the center of the element.
Expand Down
5 changes: 5 additions & 0 deletions lib/PuppeteerSharp/Input/ClickOptions.cs
Expand Up @@ -19,5 +19,10 @@ public class ClickOptions
/// The button to use for the click. Defaults to <see cref="MouseButton.Left"/>
/// </summary>
public MouseButton Button { get; set; } = MouseButton.Left;

/// <summary>
/// Offset for the clickable point relative to the top-left corner of the border-box.
/// </summary>
public Offset? OffSet { get; set; }
}
}
29 changes: 29 additions & 0 deletions lib/PuppeteerSharp/Offset.cs
@@ -0,0 +1,29 @@
namespace PuppeteerSharp
{
/// <summary>
/// Offset used in conjunction with <see cref="ElementHandle.ClickablePointAsync(Offset?)"/>
/// </summary>
public struct Offset
{
/// <summary>
/// Initializes a new instance of the <see cref="Offset"/> struct.
/// </summary>
/// <param name="x">x-offset for the clickable point relative to the top-left corner of the border box.</param>
/// <param name="y">y-offset for the clickable point relative to the top-left corner of the border box.</param>
public Offset(decimal x, decimal y)
{
X = x;
Y = y;
}

/// <summary>
/// x-offset for the clickable point relative to the top-left corner of the border box.
/// </summary>
public decimal X { get; set; }

/// <summary>
/// y-offset for the clickable point relative to the top-left corner of the border box.
/// </summary>
public decimal Y { get; set; }
}
}