Skip to content

Commit 12b3645

Browse files
authored
Add element.SelectAsync and element.EvaluateFunctionAsync (#1292)
* Add element.SelectAsync and element.EvaluateFunctionAsync * ops * Pass param array as array
1 parent 6f813bd commit 12b3645

File tree

4 files changed

+104
-35
lines changed

4 files changed

+104
-35
lines changed

lib/PuppeteerSharp/DOMWorld.cs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -307,22 +307,16 @@ internal async Task FocusAsync(string selector)
307307
await handle.DisposeAsync().ConfigureAwait(false);
308308
}
309309

310-
internal Task<string[]> SelectAsync(string selector, params string[] values)
311-
=> QuerySelectorAsync(selector).EvaluateFunctionAsync<string[]>(@"(element, values) => {
312-
if (element.nodeName.toLowerCase() !== 'select')
313-
throw new Error('Element is not a <select> element.');
314-
315-
const options = Array.from(element.options);
316-
element.value = undefined;
317-
for (const option of options) {
318-
option.selected = values.includes(option.value);
319-
if (option.selected && !element.multiple)
320-
break;
321-
}
322-
element.dispatchEvent(new Event('input', { 'bubbles': true }));
323-
element.dispatchEvent(new Event('change', { 'bubbles': true }));
324-
return options.filter(option => option.selected).map(option => option.value);
325-
}", new[] { values });
310+
internal async Task<string[]> SelectAsync(string selector, params string[] values)
311+
{
312+
if (!((await QuerySelectorAsync(selector).ConfigureAwait(false)) is ElementHandle handle))
313+
{
314+
throw new SelectorException($"No node found for selector: {selector}", selector);
315+
}
316+
var result = await handle.SelectAsync(values).ConfigureAwait(false);
317+
await handle.DisposeAsync();
318+
return result;
319+
}
326320

327321
internal async Task TapAsync(string selector)
328322
{

lib/PuppeteerSharp/ElementHandle.cs

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ public async Task TapAsync()
230230
/// Calls <c>focus</c> <see href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus"/> on the element.
231231
/// </summary>
232232
/// <returns>Task</returns>
233-
public Task FocusAsync() => ExecutionContext.EvaluateFunctionAsync("element => element.focus()", this);
233+
public Task FocusAsync() => EvaluateFunctionAsync("element => element.focus()");
234234

235235
/// <summary>
236236
/// Focuses the element, and sends a <c>keydown</c>, <c>keypress</c>/<c>input</c>, and <c>keyup</c> event for each character in the text.
@@ -281,9 +281,9 @@ public async Task PressAsync(string key, PressOptions options = null)
281281
/// <returns>Task which resolves to <see cref="ElementHandle"/> pointing to the frame element</returns>
282282
public async Task<ElementHandle> QuerySelectorAsync(string selector)
283283
{
284-
var handle = await ExecutionContext.EvaluateFunctionHandleAsync(
284+
var handle = await EvaluateFunctionHandleAsync(
285285
"(element, selector) => element.querySelector(selector)",
286-
this, selector).ConfigureAwait(false);
286+
selector).ConfigureAwait(false);
287287

288288
if (handle is ElementHandle element)
289289
{
@@ -301,9 +301,9 @@ public async Task<ElementHandle> QuerySelectorAsync(string selector)
301301
/// <returns>Task which resolves to ElementHandles pointing to the frame elements</returns>
302302
public async Task<ElementHandle[]> QuerySelectorAllAsync(string selector)
303303
{
304-
var arrayHandle = await ExecutionContext.EvaluateFunctionHandleAsync(
304+
var arrayHandle = await EvaluateFunctionHandleAsync(
305305
"(element, selector) => element.querySelectorAll(selector)",
306-
this, selector).ConfigureAwait(false);
306+
selector).ConfigureAwait(false);
307307

308308
var properties = await arrayHandle.GetPropertiesAsync().ConfigureAwait(false);
309309
await arrayHandle.DisposeAsync().ConfigureAwait(false);
@@ -423,6 +423,36 @@ public Task<bool> IsIntersectingViewportAsync()
423423
return visibleRatio > 0;
424424
}", this);
425425

426+
/// <summary>
427+
/// Triggers a `change` and `input` event once all the provided options have been selected.
428+
/// If there's no `select` element matching `selector`, the method throws an exception.
429+
/// </summary>
430+
/// <example>
431+
/// <code>
432+
/// await handle.SelectAsync("blue"); // single selection
433+
/// await handle.SelectAsync("red", "green", "blue"); // multiple selections
434+
/// </code>
435+
/// </example>
436+
/// <param name="values">Values of options to select. If the `select` has the `multiple` attribute, all values are considered, otherwise only the first one is taken into account.</param>
437+
/// <returns>A task that resolves to an array of option values that have been successfully selected.</returns>
438+
public Task<string[]> SelectAsync(params string[] values)
439+
=> EvaluateFunctionAsync<string[]>(@"(element, values) =>
440+
{
441+
if (element.nodeName.toLowerCase() !== 'select')
442+
throw new Error('Element is not a <select> element.');
443+
444+
const options = Array.from(element.options);
445+
element.value = undefined;
446+
for (const option of options) {
447+
option.selected = values.includes(option.value);
448+
if (option.selected && !element.multiple)
449+
break;
450+
}
451+
element.dispatchEvent(new Event('input', { 'bubbles': true }));
452+
element.dispatchEvent(new Event('change', { 'bubbles': true }));
453+
return options.filter(option => option.selected).map(option => option.value);
454+
}", new[] { values });
455+
426456
private async Task<(decimal x, decimal y)> ClickablePointAsync()
427457
{
428458
GetContentQuadsResponse result = null;
@@ -484,7 +514,7 @@ private IEnumerable<BoxModelPoint> IntersectQuadWithViewport(IEnumerable<BoxMode
484514

485515
private async Task ScrollIntoViewIfNeededAsync()
486516
{
487-
var errorMessage = await ExecutionContext.EvaluateFunctionAsync<string>(@"async(element, pageJavascriptEnabled) => {
517+
var errorMessage = await EvaluateFunctionAsync<string>(@"async(element, pageJavascriptEnabled) => {
488518
if (!element.isConnected)
489519
return 'Node is detached from document';
490520
if (element.nodeType !== Node.ELEMENT_NODE)
@@ -504,7 +534,7 @@ private async Task ScrollIntoViewIfNeededAsync()
504534
if (visibleRatio !== 1.0)
505535
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
506536
return null;
507-
}", this, Page.JavascriptEnabled).ConfigureAwait(false);
537+
}", Page.JavascriptEnabled).ConfigureAwait(false);
508538

509539
if (errorMessage != null)
510540
{

lib/PuppeteerSharp/Extensions.cs

100755100644
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,7 @@ public static async Task<T> EvaluateFunctionAsync<T>(this ElementHandle elementH
5454
throw new SelectorException("Error: failed to find element matching selector");
5555
}
5656

57-
var newArgs = new object[args.Length + 1];
58-
newArgs[0] = elementHandle;
59-
args.CopyTo(newArgs, 1);
60-
var result = await elementHandle.ExecutionContext.EvaluateFunctionAsync<T>(pageFunction, newArgs).ConfigureAwait(false);
57+
var result = await elementHandle.EvaluateFunctionAsync<T>(pageFunction, args).ConfigureAwait(false);
6158
await elementHandle.DisposeAsync().ConfigureAwait(false);
6259
return result;
6360
}
@@ -93,12 +90,7 @@ public static async Task<T> EvaluateFunctionAsync<T>(this Task<JSHandle> arrayHa
9390
/// <returns>Task which resolves to the return value of <c>pageFunction</c></returns>
9491
public static async Task<T> EvaluateFunctionAsync<T>(this JSHandle arrayHandle, string pageFunction, params object[] args)
9592
{
96-
var response = await arrayHandle.JsonValueAsync<object[]>().ConfigureAwait(false);
97-
98-
var newArgs = new object[args.Length + 1];
99-
newArgs[0] = arrayHandle;
100-
args.CopyTo(newArgs, 1);
101-
var result = await arrayHandle.ExecutionContext.EvaluateFunctionAsync<T>(pageFunction, newArgs).ConfigureAwait(false);
93+
var result = await arrayHandle.EvaluateFunctionAsync<T>(pageFunction, args).ConfigureAwait(false);
10294
await arrayHandle.DisposeAsync().ConfigureAwait(false);
10395
return result;
10496
}

lib/PuppeteerSharp/JSHandle.cs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Threading.Tasks;
33
using Microsoft.Extensions.Logging;
44
using Newtonsoft.Json;
5+
using Newtonsoft.Json.Linq;
56
using PuppeteerSharp.Helpers;
67
using PuppeteerSharp.Helpers.Json;
78
using PuppeteerSharp.Messaging;
@@ -55,11 +56,11 @@ internal JSHandle(ExecutionContext context, CDPSession client, RemoteObject remo
5556
/// <returns>Task of <see cref="JSHandle"/></returns>
5657
public async Task<JSHandle> GetPropertyAsync(string propertyName)
5758
{
58-
var objectHandle = await ExecutionContext.EvaluateFunctionHandleAsync(@"(object, propertyName) => {
59+
var objectHandle = await EvaluateFunctionHandleAsync(@"(object, propertyName) => {
5960
const result = { __proto__: null};
6061
result[propertyName] = object[propertyName];
6162
return result;
62-
}", this, propertyName).ConfigureAwait(false);
63+
}", propertyName).ConfigureAwait(false);
6364
var properties = await objectHandle.GetPropertiesAsync().ConfigureAwait(false);
6465
properties.TryGetValue(propertyName, out var result);
6566
await objectHandle.DisposeAsync().ConfigureAwait(false);
@@ -166,6 +167,58 @@ public override string ToString()
166167
return "JSHandle:" + RemoteObjectHelper.ValueFromRemoteObject<object>(RemoteObject)?.ToString();
167168
}
168169

170+
/// <summary>
171+
/// Executes a script in browser context
172+
/// </summary>
173+
/// <param name="pageFunction">Script to be evaluated in browser context</param>
174+
/// <param name="args">Function arguments</param>
175+
/// <remarks>
176+
/// If the script, returns a Promise, then the method would wait for the promise to resolve and return its value.
177+
/// <see cref="JSHandle"/> instances can be passed as arguments
178+
/// </remarks>
179+
/// <returns>Task which resolves to script return value</returns>
180+
public Task<JSHandle> EvaluateFunctionHandleAsync(string pageFunction, params object[] args)
181+
{
182+
var list = new List<object>(args);
183+
list.Insert(0, this);
184+
return ExecutionContext.EvaluateFunctionHandleAsync(pageFunction, list.ToArray());
185+
}
186+
187+
/// <summary>
188+
/// Executes a function in browser context
189+
/// </summary>
190+
/// <param name="script">Script to be evaluated in browser context</param>
191+
/// <param name="args">Arguments to pass to script</param>
192+
/// <remarks>
193+
/// If the script, returns a Promise, then the method would wait for the promise to resolve and return its value.
194+
/// <see cref="JSHandle"/> instances can be passed as arguments
195+
/// </remarks>
196+
/// <returns>Task which resolves to script return value</returns>
197+
public Task<JToken> EvaluateFunctionAsync(string script, params object[] args)
198+
{
199+
var list = new List<object>(args);
200+
list.Insert(0, this);
201+
return ExecutionContext.EvaluateFunctionAsync<JToken>(script, list.ToArray());
202+
}
203+
204+
/// <summary>
205+
/// Executes a function in browser context
206+
/// </summary>
207+
/// <typeparam name="T">The type to deserialize the result to</typeparam>
208+
/// <param name="script">Script to be evaluated in browser context</param>
209+
/// <param name="args">Arguments to pass to script</param>
210+
/// <remarks>
211+
/// If the script, returns a Promise, then the method would wait for the promise to resolve and return its value.
212+
/// <see cref="JSHandle"/> instances can be passed as arguments
213+
/// </remarks>
214+
/// <returns>Task which resolves to script return value</returns>
215+
public Task<T> EvaluateFunctionAsync<T>(string script, params object[] args)
216+
{
217+
var list = new List<object>(args);
218+
list.Insert(0, this);
219+
return ExecutionContext.EvaluateFunctionAsync<T>(script, list.ToArray());
220+
}
221+
169222
internal object FormatArgument(ExecutionContext context)
170223
{
171224
if (ExecutionContext != context)

0 commit comments

Comments
 (0)