Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Browserbase selectOption Bug — advancedStealth breaks option.selected cross-world propagation

Summary

Playwright's selectOption does not work when connected to a Browserbase session with advancedStealth: true. The selected option's value is empty and selectedIndex is -1 from the page's JavaScript perspective, even though Playwright reports success.

Root cause

Playwright's selectOption runs in Chrome's utility world (an isolated JavaScript execution context). It sets option.selected = true on the target <option> element, then dispatches change and input events.

With Browserbase's advancedStealth: true, the option.selected = true property mutation made in the utility world does not propagate to the main world. When the page's event handlers fire and read select.value, they see "".

Notably:

  • select.value = undefined (HTMLSelectElement setter) DOES cross worlds — it successfully clears the previous selection
  • option.selected = true (HTMLOptionElement setter) does NOT cross worlds

Without advancedStealth (or on a local Playwright browser), both property mutations propagate correctly.

Reproduction

Test page (index.html)

A minimal page with a <select> and two event listeners (direct + delegated on document).

Steps

# 1. Host the test page
python3 -m http.server 8765

# 2. Run with local Playwright (PASS)
npm install
PAGE_URL=http://localhost:8765/ node repro.js --local

# 3. Run with Browserbase (stealth FAILS, no-stealth PASSES)
BROWSERBASE_API_KEY=... BROWSERBASE_PROJECT_ID=... PAGE_URL=http://localhost:8765/ node repro.js

Expected output

=== Local Playwright (no stealth) ===
Direct handler: {"value":"A","selectedIndex":1,"optionSelected":true,"isTrusted":false}
Delegated handler: {"value":"A","selectedIndex":1,"optionSelected":true,"isTrusted":false}
DOM state: {"value":"A","selectedIndex":1,"options":["0:false=","1:true=A","2:false=B"]}
PASS ✓

=== Browserbase (advancedStealth: false) ===
...
PASS ✓

=== Browserbase (advancedStealth: true) ===
Direct handler: {"value":"","selectedIndex":-1,"optionSelected":false,"isTrusted":false}
Delegated handler: {"value":"","selectedIndex":-1,"optionSelected":false,"isTrusted":false}
DOM state: {"value":"","selectedIndex":-1,"options":["0:false=","1:false=A","2:false=B"]}
FAIL ✗ — value is empty, option.selected did not propagate

Impact

Any page with a JavaScript change event handler that reads select.value synchronously will see an empty value when selectOption is used through Browserbase with advanced stealth. This breaks:

  • jQuery delegated event handlers ($(document).on('change', 'select', handler))
  • Any framework that manages form state via change events (e.g., government portal frameworks like FWDC)
  • Direct event listeners that read e.target.value

Workaround

Use page.evaluate to set the value in the main world instead of Playwright's selectOption:

await page.evaluate(([selector, value]) => {
  const el = document.querySelector(selector);
  const norm = s => s.replace(/\s+/g, ' ').trim().toLowerCase();
  for (const opt of el.options) {
    if (norm(opt.text) === norm(value) || norm(opt.value) === norm(value)) {
      el.value = opt.value;
      el.dispatchEvent(new Event('change', { bubbles: true }));
      return;
    }
  }
}, ['#myselect', 'Alpha']);

This works because page.evaluate runs in the main world, where property mutations are immediately visible to all event handlers.

About

Reproduction: Browserbase advancedStealth breaks Playwright selectOption

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages