Skip to content

Commit 5817ee4

Browse files
authored
Implement Page.SelectAsync (#199)
1 parent 2900d12 commit 5817ee4

File tree

4 files changed

+130
-2
lines changed

4 files changed

+130
-2
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Xunit;
4+
5+
namespace PuppeteerSharp.Tests.Page
6+
{
7+
[Collection("PuppeteerLoaderFixture collection")]
8+
public class SelectTests : PuppeteerPageBaseTest
9+
{
10+
[Fact]
11+
public async Task ShouldSelectSingleOption()
12+
{
13+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
14+
await Page.SelectAsync("select", "blue");
15+
Assert.Equal(new string[] { "blue" }, await Page.EvaluateExpressionAsync<string[]>("result.onInput"));
16+
Assert.Equal(new string[] { "blue" }, await Page.EvaluateExpressionAsync<string[]>("result.onChange"));
17+
}
18+
19+
[Fact]
20+
public async Task ShouldSelectMultipleOptions()
21+
{
22+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
23+
await Page.EvaluateExpressionAsync("makeMultiple()");
24+
await Page.SelectAsync("select", "blue", "green", "red");
25+
Assert.Equal(new string[] { "blue", "green", "red" },
26+
await Page.EvaluateExpressionAsync<string[]>("result.onInput"));
27+
Assert.Equal(new string[] { "blue", "green", "red" },
28+
await Page.EvaluateExpressionAsync<string[]>("result.onChange"));
29+
}
30+
31+
[Fact]
32+
public async Task ShouldRespectEventBubbling()
33+
{
34+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
35+
await Page.SelectAsync("select", "blue");
36+
Assert.Equal(new string[] { "blue" }, await Page.EvaluateExpressionAsync<string[]>("result.onBubblingInput"));
37+
Assert.Equal(new string[] { "blue" }, await Page.EvaluateExpressionAsync<string[]>("result.onBubblingChange"));
38+
}
39+
40+
[Fact]
41+
public async Task ShouldThrowWhenElementIsNotASelect()
42+
{
43+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
44+
var exception = await Assert.ThrowsAsync<EvaluationFailedException>(async () => await Page.SelectAsync("body", ""));
45+
Assert.Contains("Element is not a <select> element.", exception.Message);
46+
}
47+
48+
[Fact]
49+
public async Task ShouldReturnEmptyArrayOnNoMatchedValues()
50+
{
51+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
52+
var result = await Page.SelectAsync("select", "42", "abc");
53+
Assert.Empty(result);
54+
}
55+
56+
[Fact]
57+
public async Task ShouldReturnAnArrayOfMatchedValues()
58+
{
59+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
60+
await Page.EvaluateExpressionAsync("makeMultiple()");
61+
var result = await Page.SelectAsync("select", "blue", "black", "magenta");
62+
Array.Sort(result);
63+
Assert.Equal(new string[] { "black", "blue", "magenta" }, result);
64+
}
65+
66+
[Fact]
67+
public async Task ShouldReturnAnArrayOfOneElementWhenMultipleIsNotSet()
68+
{
69+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
70+
Assert.Single(await Page.SelectAsync("select", "42", "blue", "black", "magenta"));
71+
}
72+
73+
[Fact]
74+
public async Task ShouldReturnEmptyArrayOnNoValues()
75+
{
76+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
77+
Assert.Empty(await Page.SelectAsync("select"));
78+
}
79+
80+
[Fact]
81+
public async Task ShouldDeselectAllOptionsWhenPassedNoValuesForAMultipleSelect()
82+
{
83+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
84+
await Page.EvaluateExpressionAsync("makeMultiple()");
85+
await Page.SelectAsync("select", "blue", "black", "magenta");
86+
await Page.SelectAsync("select");
87+
Assert.True(await Page.QuerySelectorAsync("select").EvaluateFunctionAsync<bool>(
88+
"select => Array.from(select.options).every(option => !option.selected)"));
89+
}
90+
91+
[Fact]
92+
public async Task ShouldDeselectAllOptionsWhenPassedNoValuesForASelectWithoutMultiple()
93+
{
94+
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
95+
await Page.SelectAsync("select", "blue", "black", "magenta");
96+
await Page.SelectAsync("select");
97+
Assert.True(await Page.QuerySelectorAsync("select").EvaluateFunctionAsync<bool>(
98+
"select => Array.from(select.options).every(option => !option.selected)"));
99+
}
100+
}
101+
}

lib/PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<PackageReference Include="xunit" Version="2.3.1" />
1010
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
1111
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0002" />
12-
<PackageReference Include="PdfSharp" Version="1.50.4845-RC2a" NoWarn="NU1701"/>
12+
<PackageReference Include="PdfSharp" Version="1.50.4845-RC2a" NoWarn="NU1701" />
1313
</ItemGroup>
1414
<ItemGroup>
1515
<Folder Include="Issues\" />

lib/PuppeteerSharp/Frame.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Text;
55
using System.Text.RegularExpressions;
66
using System.Threading.Tasks;
7+
using System.Linq;
78

89
namespace PuppeteerSharp
910
{
@@ -133,7 +134,7 @@ internal async Task<ElementHandle> QuerySelectorAsync(string selector)
133134
var value = await document.QuerySelectorAsync(selector);
134135
return value;
135136
}
136-
137+
137138
internal Task<object> EvalMany(string selector, Func<object> pageFunction, object[] args)
138139
{
139140
throw new NotImplementedException();
@@ -315,6 +316,20 @@ function hasVisibleBoundingBox() {
315316
return handle.AsElement();
316317
}
317318

319+
internal Task<string[]> SelectAsync(string selector, params string[] values)
320+
=> QuerySelectorAsync(selector).EvaluateFunctionAsync<string[]>(@"(element, values) => {
321+
if (element.nodeName.toLowerCase() !== 'select')
322+
throw new Error('Element is not a <select> element.');
323+
324+
const options = Array.from(element.options);
325+
element.value = undefined;
326+
for (const option of options)
327+
option.selected = values.includes(option.value);
328+
element.dispatchEvent(new Event('input', { 'bubbles': true }));
329+
element.dispatchEvent(new Event('change', { 'bubbles': true }));
330+
return options.filter(option => option.selected).map(option => option.value);
331+
}", new[] { values });
332+
318333
#endregion
319334

320335
#region Private Methods

lib/PuppeteerSharp/Page.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,18 @@ await Task.WhenAll(
775775
return navigationTask.Result;
776776
}
777777

778+
/// <summary>
779+
/// Triggers a change and input event once all the provided options have been selected.
780+
/// If there's no <select> element matching selector, the method throws an error.
781+
/// </summary>
782+
/// <exception cref="SelectorException">If there's no element matching <paramref name="selector"/></exception>
783+
/// <returns>Returns an array of option values that have been successfully selected.</returns>
784+
/// <param name="selector">A selector to query page for</param>
785+
/// <param name="values">Values of options to select. If the <select> has the multiple attribute,
786+
/// all values are considered, otherwise only the first one is taken into account.</param>
787+
public Task<string[]> SelectAsync(string selector, params string[] values)
788+
=> MainFrame.SelectAsync(selector, values);
789+
778790
/// <summary>
779791
/// Sends a <c>keydown</c>, <c>keypress</c>/<c>input</c>, and <c>keyup</c> event for each character in the text.
780792
/// </summary>

0 commit comments

Comments
 (0)