mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-10-18 23:07:13 -05:00
fix(humanize): select_option returns when the pick navigates the page (#597)
This commit is contained in:
7 files changed
+62
-3
No files matched your search
@@ -9,6 +9,7 @@ Changes are tagged: **[wrapper]** for Python/JS wrapper, **[binary]** for Chromi
|
||||
## [Unreleased]
|
||||
|
||||
- **[wrapper]** Async launches no longer freeze the caller's event loop. `launch_async()`, `launch_context_async()` and `launch_persistent_context_async()` ran the binary/license check and the GeoIP and WebRTC exit-IP lookups directly on the event loop, so every other coroutine (a server's health endpoint, WebSockets, other browsers) stalled for up to 20s per launch behind a slow proxy. They now run in a worker thread. .NET: the license check and Pro version lookup inside async launches no longer hold the caller's thread (e.g. a UI thread). Python and .NET; JavaScript was not affected.
|
||||
- **[wrapper]** Fix humanized `select_option()` raising `element is not attached to the DOM` when the pick navigates the page (e.g. a dropdown that submits its form on change). The option was selected and the page navigated, but the check after the pick ran on the new page. It now returns the chosen values, as Playwright does. A pick that lands on the wrong option without navigating still raises. Python, JavaScript Playwright, and .NET (thanks [@msamoylov](https://github.com/msamoylov), #597)
|
||||
|
||||
## [0.6.2] — 2026-10-12
|
||||
|
||||
|
||||
@@ -923,7 +923,12 @@ class Human:
|
||||
cfg, deadline)
|
||||
else:
|
||||
await self._select_listbox(target, r, p, opts, api, cfg, deadline)
|
||||
state = await self.worlds.call(r.frame, "selectState", r.id)
|
||||
try:
|
||||
state = await self.worlds.call(r.frame, "selectState", r.id)
|
||||
except StaleElement:
|
||||
# The pick navigated the page (a form submitted on change): nothing
|
||||
# left to read back, so report what was chosen, as Playwright does.
|
||||
return p["values"]
|
||||
if sorted(state["selected"]) != sorted(p["targets"]):
|
||||
raise Error(f"{api}: Error: selection ended as {state['values']} instead of {p['values']}")
|
||||
return state["values"]
|
||||
|
||||
@@ -274,7 +274,17 @@ internal sealed partial class HumanEngine
|
||||
await SelectDropdownAsync(t, r, targets.Count > 0 ? targets[0] : null, plan, o, api, d).ConfigureAwait(false);
|
||||
else
|
||||
await SelectListboxAsync(t, r, targets, o, api, cfg, d).ConfigureAwait(false);
|
||||
var state = (await World.CallAsync(r.Frame, "selectState", r.Id).ConfigureAwait(false))!.Value;
|
||||
JsonElement state;
|
||||
try
|
||||
{
|
||||
state = (await World.CallAsync(r.Frame, "selectState", r.Id).ConfigureAwait(false))!.Value;
|
||||
}
|
||||
catch (StaleElementException)
|
||||
{
|
||||
// The pick navigated the page (a form submitted on change): nothing
|
||||
// left to read back, so report what was chosen, as Playwright does.
|
||||
return values;
|
||||
}
|
||||
var selected = state.GetProperty("selected").EnumerateArray().Select(x => x.GetInt32()).OrderBy(x => x).ToList();
|
||||
var got = state.GetProperty("values").EnumerateArray().Select(x => x.GetString()!).ToList();
|
||||
if (!selected.SequenceEqual(targets.OrderBy(x => x)))
|
||||
|
||||
@@ -385,6 +385,19 @@ public class HumanEngineBrowserTests : IClassFixture<EngineFixture>, IAsyncLifet
|
||||
Assert.Equal(new[] { "c" }, await p.SelectOptionAsync("#emoji", "c"));
|
||||
}
|
||||
|
||||
[BrowserFact]
|
||||
public async Task SelectOption_that_navigates_the_page_still_returns()
|
||||
{
|
||||
// #597: a dropdown that submits its form on change.
|
||||
var p = await Open();
|
||||
await p.EvaluateAsync(@"() => { const f = document.createElement('form'); f.method = 'get';
|
||||
const s = document.createElement('select'); s.id = 'nav'; s.name = 'city';
|
||||
for (const [l, v] of [['Amsterdam', 'a'], ['Berlin', 'b']]) s.add(new Option(l, v));
|
||||
s.onchange = () => f.submit(); f.append(s); document.body.prepend(f); }");
|
||||
Assert.Equal(new[] { "b" }, await p.SelectOptionAsync("#nav", "b"));
|
||||
await p.WaitForURLAsync(new System.Text.RegularExpressions.Regex("city=b"));
|
||||
}
|
||||
|
||||
// --- frames / handles --------------------------------------------------
|
||||
|
||||
[BrowserFact]
|
||||
|
||||
@@ -872,7 +872,15 @@ export class Human {
|
||||
});
|
||||
if (!p.listbox) await this.selectDropdown(target, r, p.targets.length ? p.targets[0] : null, p, opts, api, deadline);
|
||||
else await this.selectListbox(target, r, p, opts, api, cfg, deadline);
|
||||
const state = await this.worlds.call(r.frame, 'selectState', r.id);
|
||||
let state;
|
||||
try {
|
||||
state = await this.worlds.call(r.frame, 'selectState', r.id);
|
||||
} catch (e) {
|
||||
// The pick navigated the page (a form submitted on change): nothing
|
||||
// left to read back, so report what was chosen, as Playwright does.
|
||||
if (e instanceof StaleElement) return p.values;
|
||||
throw e;
|
||||
}
|
||||
const sorted = (a: number[]) => JSON.stringify([...a].sort((x, y) => x - y));
|
||||
if (sorted(state.selected) !== sorted(p.targets)) {
|
||||
throw err(`${api}: Error: selection ended as ${JSON.stringify(state.values)} instead of ${JSON.stringify(p.values)}`);
|
||||
|
||||
@@ -470,6 +470,18 @@ describe.skipIf(!process.env.CLOAKBROWSER_BINARY_PATH)('humanize engine (real br
|
||||
}
|
||||
});
|
||||
|
||||
it('selectOption that navigates the page still returns (#597)', async () => {
|
||||
const p = await open();
|
||||
await p.evaluate(() => {
|
||||
const f = document.createElement('form'); f.method = 'get';
|
||||
const s = document.createElement('select'); s.id = 'nav'; s.name = 'city';
|
||||
for (const [l, v] of [['Amsterdam', 'a'], ['Berlin', 'b']]) s.add(new Option(l, v));
|
||||
s.onchange = () => f.submit(); f.append(s); document.body.prepend(f);
|
||||
});
|
||||
expect(await p.selectOption('#nav', 'b')).toEqual(['b']);
|
||||
await p.waitForURL('**city=b');
|
||||
});
|
||||
|
||||
// --- frames and handles ---------------------------------------------------
|
||||
|
||||
it('frame actions', async () => {
|
||||
|
||||
@@ -724,6 +724,16 @@ def test_select_option_on_emoji_labels(page):
|
||||
assert page.select_option("#emoji", "c") == ["c"]
|
||||
|
||||
|
||||
def test_select_option_that_navigates_the_page(page):
|
||||
"""#597: a dropdown that submits its form on change; the pick still returns."""
|
||||
page.evaluate("""() => { const f = document.createElement('form'); f.method = 'get';
|
||||
const s = document.createElement('select'); s.id = 'nav'; s.name = 'city';
|
||||
for (const [l, v] of [['Amsterdam', 'a'], ['Berlin', 'b']]) s.add(new Option(l, v));
|
||||
s.onchange = () => f.submit(); f.append(s); document.body.prepend(f); }""")
|
||||
assert page.select_option("#nav", "b") == ["b"]
|
||||
page.wait_for_url("**city=b")
|
||||
|
||||
|
||||
# --- frames and handles ------------------------------------------------------
|
||||
|
||||
def test_frame_actions(page):
|
||||
|
||||
Reference in new issue
Block a user