Skip to content

Commit 73f7ea8

Browse files
authored
Use secondary world to drive clicks (#928)
* Use secondary world to drive clicks I also wired the Page with the Frame and the DOMWorld in some missing methods * self cr
1 parent 7a1de99 commit 73f7ea8

File tree

4 files changed

+100
-59
lines changed

4 files changed

+100
-59
lines changed

lib/PuppeteerSharp.Tests/InputTests/InputTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ public async Task ShouldTypeIntoTheTextarea()
152152
Assert.Equal("Type in this text!", await Page.EvaluateExpressionAsync<string>("result"));
153153
}
154154

155+
[Fact]
156+
public async Task ShouldClickTheButtonIfWindowNodeIsRemoved()
157+
{
158+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/button.html");
159+
await Page.EvaluateExpressionAsync("delete window.Node");
160+
await Page.ClickAsync("button");
161+
Assert.Equal("Clicked", await Page.EvaluateExpressionAsync("result"));
162+
}
163+
155164
[Fact]
156165
public async Task ShouldClickTheButtonAfterNavigation()
157166
{
@@ -452,6 +461,15 @@ public async Task ShouldFireContextmenuEventOnRightClick()
452461
Assert.Equal("context menu", await Page.EvaluateExpressionAsync<string>("document.querySelector('#button-8').textContent"));
453462
}
454463

464+
[Fact]
465+
public async Task ShouldTriggerHoverStateWithRemovedWindowNode()
466+
{
467+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/scrollable.html");
468+
await Page.EvaluateExpressionAsync("delete window.Node");
469+
await Page.HoverAsync("#button-6");
470+
Assert.Equal("button-6", await Page.EvaluateExpressionAsync("document.querySelector('button:hover').id"));
471+
}
472+
455473
[Fact]
456474
public async Task ShouldSetModifierKeysOnClick()
457475
{

lib/PuppeteerSharp/DOMWorld.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,17 @@ internal async Task HoverAsync(string selector)
291291
await handle.DisposeAsync().ConfigureAwait(false);
292292
}
293293

294+
internal async Task FocusAsync(string selector)
295+
{
296+
var handle = await QuerySelectorAsync(selector).ConfigureAwait(false);
297+
if (handle == null)
298+
{
299+
throw new SelectorException($"No node found for selector: {selector}", selector);
300+
}
301+
await handle.FocusAsync().ConfigureAwait(false);
302+
await handle.DisposeAsync().ConfigureAwait(false);
303+
}
304+
294305
internal Task<string[]> SelectAsync(string selector, params string[] values)
295306
=> QuerySelectorAsync(selector).EvaluateFunctionAsync<string[]>(@"(element, values) => {
296307
if (element.nodeName.toLowerCase() !== 'select')

lib/PuppeteerSharp/Frame.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
using System.Collections.Generic;
33
using System.Threading.Tasks;
44
using Newtonsoft.Json.Linq;
5-
5+
using PuppeteerSharp.Input;
6+
67
namespace PuppeteerSharp
78
{
89
/// <summary>
@@ -386,6 +387,52 @@ public Task SetContentAsync(string html, NavigationOptions options = null)
386387
/// <returns>page's title</returns>
387388
/// <seealso cref="Page.GetTitleAsync"/>
388389
public Task<string> GetTitleAsync() => SecondaryWorld.GetTitleAsync();
390+
391+
/// <summary>
392+
/// Fetches an element with <paramref name="selector"/>, scrolls it into view if needed, and then uses <see cref="Page.Mouse"/> to click in the center of the element.
393+
/// </summary>
394+
/// <param name="selector">A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.</param>
395+
/// <param name="options">click options</param>
396+
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
397+
/// <returns>Task which resolves when the element matching <paramref name="selector"/> is successfully clicked</returns>
398+
public Task ClickAsync(string selector, ClickOptions options = null)
399+
=> SecondaryWorld.ClickAsync(selector, options);
400+
401+
/// <summary>
402+
/// Fetches an element with <paramref name="selector"/>, scrolls it into view if needed, and then uses <see cref="Page.Mouse"/> to hover over the center of the element.
403+
/// </summary>
404+
/// <param name="selector">A selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.</param>
405+
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
406+
/// <returns>Task which resolves when the element matching <paramref name="selector"/> is successfully hovered</returns>
407+
public Task HoverAsync(string selector) => SecondaryWorld.HoverAsync(selector);
408+
409+
/// <summary>
410+
/// Fetches an element with <paramref name="selector"/> and focuses it
411+
/// </summary>
412+
/// <param name="selector">A selector to search for element to focus. If there are multiple elements satisfying the selector, the first will be focused.</param>
413+
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
414+
/// <returns>Task which resolves when the element matching <paramref name="selector"/> is successfully focused</returns>
415+
public Task FocusAsync(string selector) => SecondaryWorld.FocusAsync(selector);
416+
417+
/// <summary>
418+
/// Sends a <c>keydown</c>, <c>keypress</c>/<c>input</c>, and <c>keyup</c> event for each character in the text.
419+
/// </summary>
420+
/// <param name="selector">A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used.</param>
421+
/// <param name="text">A text to type into a focused element</param>
422+
/// <param name="options"></param>
423+
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
424+
/// <remarks>
425+
/// To press a special key, like <c>Control</c> or <c>ArrowDown</c> use <see cref="Keyboard.PressAsync(string, PressOptions)"/>
426+
/// </remarks>
427+
/// <example>
428+
/// <code>
429+
/// page.TypeAsync("#mytextarea", "Hello"); // Types instantly
430+
/// page.TypeAsync("#mytextarea", "World", new TypeOptions { Delay = 100 }); // Types slower, like a user
431+
/// </code>
432+
/// </example>
433+
/// <returns>Task</returns>
434+
public Task TypeAsync(string selector, string text, TypeOptions options = null)
435+
=> SecondaryWorld.TypeAsync(selector, text, options);
389436

390437
internal void OnLoadingStopped()
391438
{

lib/PuppeteerSharp/Page.cs

Lines changed: 23 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,50 +1108,43 @@ public Task SetCacheEnabledAsync(bool enabled = true)
11081108
/// <param name="options">click options</param>
11091109
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
11101110
/// <returns>Task which resolves when the element matching <paramref name="selector"/> is successfully clicked</returns>
1111-
public async Task ClickAsync(string selector, ClickOptions options = null)
1112-
{
1113-
var handle = await QuerySelectorAsync(selector).ConfigureAwait(false);
1114-
if (handle == null)
1115-
{
1116-
throw new SelectorException($"No node found for selector: {selector}", selector);
1117-
}
1118-
await handle.ClickAsync(options).ConfigureAwait(false);
1119-
await handle.DisposeAsync().ConfigureAwait(false);
1120-
}
1111+
public Task ClickAsync(string selector, ClickOptions options = null) => FrameManager.MainFrame.ClickAsync(selector, options);
11211112

11221113
/// <summary>
11231114
/// Fetches an element with <paramref name="selector"/>, scrolls it into view if needed, and then uses <see cref="Page.Mouse"/> to hover over the center of the element.
11241115
/// </summary>
11251116
/// <param name="selector">A selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered.</param>
11261117
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
11271118
/// <returns>Task which resolves when the element matching <paramref name="selector"/> is successfully hovered</returns>
1128-
public async Task HoverAsync(string selector)
1129-
{
1130-
var handle = await QuerySelectorAsync(selector).ConfigureAwait(false);
1131-
if (handle == null)
1132-
{
1133-
throw new SelectorException($"No node found for selector: {selector}", selector);
1134-
}
1135-
await handle.HoverAsync().ConfigureAwait(false);
1136-
await handle.DisposeAsync().ConfigureAwait(false);
1137-
}
1119+
public Task HoverAsync(string selector) => FrameManager.MainFrame.HoverAsync(selector);
11381120

11391121
/// <summary>
11401122
/// Fetches an element with <paramref name="selector"/> and focuses it
11411123
/// </summary>
11421124
/// <param name="selector">A selector to search for element to focus. If there are multiple elements satisfying the selector, the first will be focused.</param>
11431125
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
11441126
/// <returns>Task which resolves when the element matching <paramref name="selector"/> is successfully focused</returns>
1145-
public async Task FocusAsync(string selector)
1146-
{
1147-
var handle = await QuerySelectorAsync(selector).ConfigureAwait(false);
1148-
if (handle == null)
1149-
{
1150-
throw new SelectorException($"No node found for selector: {selector}", selector);
1151-
}
1152-
await handle.FocusAsync().ConfigureAwait(false);
1153-
await handle.DisposeAsync().ConfigureAwait(false);
1154-
}
1127+
public Task FocusAsync(string selector) => FrameManager.MainFrame.FocusAsync(selector);
1128+
1129+
/// <summary>
1130+
/// Sends a <c>keydown</c>, <c>keypress</c>/<c>input</c>, and <c>keyup</c> event for each character in the text.
1131+
/// </summary>
1132+
/// <param name="selector">A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used.</param>
1133+
/// <param name="text">A text to type into a focused element</param>
1134+
/// <param name="options"></param>
1135+
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
1136+
/// <remarks>
1137+
/// To press a special key, like <c>Control</c> or <c>ArrowDown</c> use <see cref="Keyboard.PressAsync(string, PressOptions)"/>
1138+
/// </remarks>
1139+
/// <example>
1140+
/// <code>
1141+
/// page.TypeAsync("#mytextarea", "Hello"); // Types instantly
1142+
/// page.TypeAsync("#mytextarea", "World", new TypeOptions { Delay = 100 }); // Types slower, like a user
1143+
/// </code>
1144+
/// </example>
1145+
/// <returns>Task</returns>
1146+
public Task TypeAsync(string selector, string text, TypeOptions options = null)
1147+
=> FrameManager.MainFrame.TypeAsync(selector, text, options);
11551148

11561149
/// <summary>
11571150
/// Executes a script in browser context
@@ -1274,34 +1267,6 @@ public Task<Response> ReloadAsync(int? timeout = null, WaitUntilNavigation[] wai
12741267
public Task<string[]> SelectAsync(string selector, params string[] values)
12751268
=> MainFrame.SelectAsync(selector, values);
12761269

1277-
/// <summary>
1278-
/// Sends a <c>keydown</c>, <c>keypress</c>/<c>input</c>, and <c>keyup</c> event for each character in the text.
1279-
/// </summary>
1280-
/// <param name="selector">A selector of an element to type into. If there are multiple elements satisfying the selector, the first will be used.</param>
1281-
/// <param name="text">A text to type into a focused element</param>
1282-
/// <param name="options"></param>
1283-
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
1284-
/// <remarks>
1285-
/// To press a special key, like <c>Control</c> or <c>ArrowDown</c> use <see cref="Keyboard.PressAsync(string, PressOptions)"/>
1286-
/// </remarks>
1287-
/// <example>
1288-
/// <code>
1289-
/// page.TypeAsync("#mytextarea", "Hello"); // Types instantly
1290-
/// page.TypeAsync("#mytextarea", "World", new TypeOptions { Delay = 100 }); // Types slower, like a user
1291-
/// </code>
1292-
/// </example>
1293-
/// <returns>Task</returns>
1294-
public async Task TypeAsync(string selector, string text, TypeOptions options = null)
1295-
{
1296-
var handle = await QuerySelectorAsync(selector).ConfigureAwait(false);
1297-
if (handle == null)
1298-
{
1299-
throw new SelectorException($"No node found for selector: {selector}", selector);
1300-
}
1301-
await handle.TypeAsync(text, options).ConfigureAwait(false);
1302-
await handle.DisposeAsync().ConfigureAwait(false);
1303-
}
1304-
13051270
/// <summary>
13061271
/// Waits for a timeout
13071272
/// </summary>

0 commit comments

Comments
 (0)