Skip to content

Commit ee68e9b

Browse files
authored
Bidi implement Select Async (#3005)
1 parent d6f24b9 commit ee68e9b

File tree

6 files changed

+57
-38
lines changed

6 files changed

+57
-38
lines changed

lib/CLAUDE.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -392,15 +392,20 @@ Test directory structure demonstrates comprehensive coverage:
392392
When running tests, always build first and then use the `--no-build` flag to avoid rebuilding during test execution. This provides faster and more reliable test runs:
393393
Always be explicit with the browser and protocol you want to test using ENV variables BROWSER=FIREFOX|CHROME and PROTOCOL=bidi|cdp
394394

395+
**IMPORTANT: Chrome should ALWAYS use CDP protocol. Firefox can use either CDP or BiDi.**
396+
395397
```bash
396-
# Build the test project first
398+
# Build the test project first with Firefox and BiDi
397399
BROWSER=FIREFOX PROTOCOL=bidi dotnet build PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj
398400

399401
# Then run tests with --no-build flag
400-
BROWSER=CHROME PROTOCOL=bidi dotnet test PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj --filter "FullyQualifiedName~TestName" --no-build -- NUnit.TestOutputXml=TestResults
402+
BROWSER=FIREFOX PROTOCOL=bidi dotnet test PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj --filter "FullyQualifiedName~TestName" --no-build -- NUnit.TestOutputXml=TestResults
403+
404+
# Chrome should ALWAYS use CDP protocol
405+
BROWSER=CHROME PROTOCOL=cdp dotnet build PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj && BROWSER=CHROME PROTOCOL=cdp dotnet test PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj --filter "FullyQualifiedName~TestName" --no-build -- NUnit.TestOutputXml=TestResults
401406

402-
# Can also chain them together
403-
BROWSER=CHROME PROTOCOL=cdp dotnet build PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj && dotnet test PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj --filter "FullyQualifiedName~TestName" --no-build -- NUnit.TestOutputXml=TestResults
407+
# Firefox should ALWAYS use bidi protocol
408+
BROWSER=FIREFOX PROTOCOL=bidi dotnet build PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj && BROWSER=FIREFOX PROTOCOL=cdp dotnet test PuppeteerSharp.Tests/PuppeteerSharp.Tests.csproj --filter "FullyQualifiedName~TestName" --no-build -- NUnit.TestOutputXml=TestResults
404409
```
405410

406411
You can switch between CDP and Bidi by changing the PuppeteerTestAttribute.IsCdp property.

lib/PuppeteerSharp.Nunit/TestExpectations/TestExpectations.local.json

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,21 +1035,6 @@
10351035
"FAIL"
10361036
]
10371037
},
1038-
{
1039-
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
1040-
"testIdPattern": "[page.spec] *Page.select*",
1041-
"platforms": [
1042-
"darwin",
1043-
"linux",
1044-
"win32"
1045-
],
1046-
"parameters": [
1047-
"webDriverBiDi"
1048-
],
1049-
"expectations": [
1050-
"FAIL"
1051-
]
1052-
},
10531038
{
10541039
"comment": "This is part of organizing the webdriver bidi implementation, We will remove it one by one",
10551040
"testIdPattern": "[page.spec] *Page.setBypassCSP*",

lib/PuppeteerSharp.TestServer/wwwroot/input/select.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</head>
66
<body>
77
<select>
8+
<option value="">Empty</option>
89
<option value="black">Black</option>
910
<option value="blue">Blue</option>
1011
<option value="brown">Brown</option>

lib/PuppeteerSharp.Tests/PageTests/SelectTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ public async Task ShouldDeselectAllOptionsWhenPassedNoValuesForASelectWithoutMul
117117
await Page.GoToAsync(TestConstants.ServerUrl + "/input/select.html");
118118
await Page.SelectAsync("select", "blue", "black", "magenta");
119119
await Page.SelectAsync("select");
120-
Assert.That(await Page.QuerySelectorAsync("select").EvaluateFunctionAsync<bool>(
121-
"select => Array.from(select.options).every(option => !option.selected)"), Is.True);
120+
Assert.That(await Page.QuerySelectorAsync("select").EvaluateFunctionAsync<string>(
121+
"select => Array.from(select.options).filter(option => option.selected)[0].value"), Is.EqualTo(""));
122122
}
123123
}
124124
}

lib/PuppeteerSharp/Bidi/BidiRealm.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
using System;
2424
using System.Collections;
2525
using System.Collections.Generic;
26-
using System.Data;
2726
using System.Globalization;
2827
using System.Linq;
2928
using System.Numerics;
@@ -214,7 +213,7 @@ private async Task<EvaluateResultSuccess> EvaluateAsync(bool returnByValue, bool
214213
if (result.ResultType == EvaluateResultType.Exception)
215214
{
216215
// TODO: Improve text details
217-
throw new EvaluateException(((EvaluateResultException)result).ExceptionDetails.Text);
216+
throw new EvaluationFailedException(((EvaluateResultException)result).ExceptionDetails.Text);
218217
}
219218

220219
return result as EvaluateResultSuccess;

lib/PuppeteerSharp/ElementHandle.cs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -472,24 +472,53 @@ public Task<bool> IsIntersectingViewportAsync(decimal threshold)
472472

473473
/// <inheritdoc/>
474474
public Task<string[]> SelectAsync(params string[] values)
475-
=> BindIsolatedHandleAsync<string[], ElementHandle>(handle => handle.EvaluateFunctionAsync<string[]>(
476-
@"(element, values) =>
477-
{
478-
if (element.nodeName.toLowerCase() !== 'select')
479-
throw new Error('Element is not a <select> element.');
480-
481-
const options = Array.from(element.options);
482-
element.value = undefined;
483-
for (const option of options) {
484-
option.selected = values.includes(option.value);
485-
if (option.selected && !element.multiple)
475+
{
476+
if (values == null)
477+
{
478+
throw new ArgumentNullException(nameof(values));
479+
}
480+
481+
foreach (var value in values)
482+
{
483+
if (value == null)
484+
{
485+
throw new ArgumentException($"Values must be strings. Found value \"null\" of type \"null\"");
486+
}
487+
}
488+
489+
return BindIsolatedHandleAsync<string[], ElementHandle>(handle => handle.EvaluateFunctionAsync<string[]>(
490+
@"(element, vals) => {
491+
const values = new Set(vals);
492+
if (!(element instanceof HTMLSelectElement)) {
493+
throw new Error('Element is not a <select> element.');
494+
}
495+
496+
const selectedValues = new Set();
497+
if (!element.multiple) {
498+
for (const option of element.options) {
499+
option.selected = false;
500+
}
501+
for (const option of element.options) {
502+
if (values.has(option.value)) {
503+
option.selected = true;
504+
selectedValues.add(option.value);
486505
break;
506+
}
487507
}
488-
element.dispatchEvent(new Event('input', { 'bubbles': true }));
489-
element.dispatchEvent(new Event('change', { 'bubbles': true }));
490-
return options.filter(option => option.selected).map(option => option.value);
491-
}",
508+
} else {
509+
for (const option of element.options) {
510+
option.selected = values.has(option.value);
511+
if (option.selected) {
512+
selectedValues.add(option.value);
513+
}
514+
}
515+
}
516+
element.dispatchEvent(new Event('input', { bubbles: true }));
517+
element.dispatchEvent(new Event('change', { bubbles: true }));
518+
return [...selectedValues.values()];
519+
}",
492520
new object[] { values }));
521+
}
493522

494523
/// <inheritdoc/>
495524
public Task<DragData> DragAsync(decimal x, decimal y)

0 commit comments

Comments
 (0)