Skip to content

Commit b9a7366

Browse files
DavertMikDavertMikclaude
authored
fix(CDPBrowser): support ARIA radiogroups in selectOption, fix waitInUrl message (#5711)
* fix(CDPBrowser): support ARIA radiogroups in selectOption, fix waitInUrl message The Obscura CI job had 13 failing tests. 12 of them were not Obscura-specific: they fail identically against Chrome (test/helper/CDPBrowser_chrome_test.js) and only surfaced here because Obscura is the sole CDPBrowser variant with a CI job. selectOption had no radiogroup branch (11 failures). Locator.field.labelContains matches .//*[@aria-label = ...], so the [role="radiogroup"] element was found — the client's select action then looked only for [role="option"] children and returned false. It now clicks the [role="radio"] whose accessible name matches, exact match first (the fixture's "Compact" / "Compact mode" siblings require it), and lets the widget check it and uncheck the rest. Passing several options returns a sentinel instead of throwing in-page, so selectOption raises the "radio group holds one value" error from Node. waitInUrl reported the resolved absolute URL instead of the part it was given (1 failure), so the message read "expected url to include http://host/info2". 75f1221 restored the substring matching but left the message resolving. The 13th is an Obscura/React interop gap with Base UI radios and is skipped with the reason; Radix and plain radio groups pass on Obscura. Obscura is bumped to v0.2.2 (version + tarball sha256, install URL, regenerated docs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bdxGACghDuRVjn3hzmdy4 * fix(Obscura): pass --allow-file-access when launching obscura serve 0.2.2 gates DOM.setFileInputFiles behind --allow-file-access, so attachFile failed with "DOM.setFileInputFiles is disabled" on a self-launched server. The flag exists in 0.2.0 too, so passing it works against both. Also drops "file uploads" from the documented limitations: they work, and the two skipped #attachFile tests are skipped for an unrelated <select multiple> bug, not for lack of upload support. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017bdxGACghDuRVjn3hzmdy4 --------- Co-authored-by: DavertMik <davert@testomat.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 894b664 commit b9a7366

6 files changed

Lines changed: 42 additions & 22 deletions

File tree

.github/workflows/obscura.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ permissions:
1818
env:
1919
CI: true
2020
FORCE_COLOR: 1
21-
OBSCURA_VERSION: v0.2.0
22-
OBSCURA_SHA256: d601f4f542319c3b9fa8dca9f5ccfc134a2ca001648da528db5f03c9e6c2599b
21+
OBSCURA_VERSION: v0.2.2
22+
OBSCURA_SHA256: 9e5d9d081909ea983bc8c94999bb3d411fd6b74a9788504295b7e25f84310505
2323

2424
jobs:
2525
build:

docs/helpers/Obscura.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ process lifecycle, the same way Playwright manages its own browser process.
2828
never spawns or kills anything, no matter what `binaryPath`/`port` are set to.
2929
* **SELF-LAUNCH**`endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
3030
the config, then the `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The helper
31-
spawns `obscura serve --port <port> --allow-private-network` (`port` from the config, or a
32-
free port picked automatically), waits for it to answer, connects, and kills it in
33-
`_finishTest`.
31+
spawns `obscura serve --port <port> --allow-private-network --allow-file-access` (`port` from
32+
the config, or a free port picked automatically), waits for it to answer, connects, and kills
33+
it in `_finishTest`.
3434
* **COURTESY-ATTACH**`endpoint` is unset and no binary can be resolved, but something already
3535
answers `http://127.0.0.1:9222/json/version` (e.g. `obscura serve` started by hand, or by CI
3636
before this process ever ran). The helper attaches to it and never kills it — it isn't the
@@ -43,12 +43,13 @@ Download a release binary and put it on your `PATH` (or point `binaryPath`/`OBSC
4343
it directly) and the helper launches and tears it down for you automatically:
4444

4545
```sh
46-
curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.0/obscura-x86_64-linux.tar.gz | tar xz
46+
curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.2/obscura-x86_64-linux.tar.gz | tar xz
4747
```
4848

49-
`--allow-private-network` is always passed by this helper (it's required to reach apps running
50-
on `localhost`/private IPs, e.g. a dev server on `127.0.0.1:8000` — Obscura blocks
51-
private-network requests by default).
49+
`--allow-private-network` and `--allow-file-access` are always passed by this helper: the first
50+
is required to reach apps running on `localhost`/private IPs, e.g. a dev server on
51+
`127.0.0.1:8000`, the second to let `attachFile` upload local files. Obscura blocks both by
52+
default.
5253

5354
## Config presets
5455

@@ -67,7 +68,7 @@ Set them explicitly in your own config to skip probing or to force a mode.
6768
## Limitations
6869

6970
* `input` is always `synthetic`, even on rendering builds — see `input` above.
70-
* No frames, popups, or file uploads.
71+
* No frames or popups.
7172
* On `-no-render` builds and v0.1.x: no screenshots, no visibility assertions
7273
(`seeElement`/`dontSeeElement` always throw) — only DOM presence
7374
(`seeElementInDOM`/`dontSeeElementInDOM`) is meaningful without a layout engine.

lib/helper/CDPBrowser.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,6 +1631,9 @@ class CDPBrowser extends Helper {
16311631
const value = Array.isArray(option) ? option.map(String) : String(option)
16321632
const res = await this._run(this._candidates(select, 'field'), 'select', { value }, context)
16331633
if (!res.found) throw new ElementNotFound(select, 'Selectable field')
1634+
if (res.result === '__RADIOGROUP_MULTI__') {
1635+
throw new Error(`selectOption: a radio group holds one value, but ${value.length} options were passed: ${value.join(', ')}`)
1636+
}
16341637
if (res.result === false) throw new Error(`Option "${Array.isArray(option) ? option.join(',') : option}" not found in ${new Locator(select).toString()}`)
16351638
}
16361639

@@ -1810,7 +1813,6 @@ class CDPBrowser extends Helper {
18101813
*/
18111814
async waitInUrl(urlPart, sec = null) {
18121815
const timeout = sec || this.options.waitForTimeout
1813-
const expectedUrl = resolveUrl(urlPart, this.options.url)
18141816
let lastUrl = ''
18151817
try {
18161818
return await this._poll(
@@ -1822,7 +1824,7 @@ class CDPBrowser extends Helper {
18221824
'placeholder',
18231825
)
18241826
} catch (e) {
1825-
throw new Error(`expected url to include ${expectedUrl}, but found ${lastUrl}`)
1827+
throw new Error(`expected url to include ${urlPart}, but found ${lastUrl}`)
18261828
}
18271829
}
18281830

lib/helper/Obscura.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ const config = {}
4545
* never spawns or kills anything, no matter what `binaryPath`/`port` are set to.
4646
* - **SELF-LAUNCH** — `endpoint` is unset and a binary can be resolved, in order: `binaryPath` in
4747
* the config, then the `OBSCURA_PATH` environment variable, then `obscura` on `PATH`. The helper
48-
* spawns `obscura serve --port <port> --allow-private-network` (`port` from the config, or a
49-
* free port picked automatically), waits for it to answer, connects, and kills it in
50-
* `_finishTest`.
48+
* spawns `obscura serve --port <port> --allow-private-network --allow-file-access` (`port` from
49+
* the config, or a free port picked automatically), waits for it to answer, connects, and kills
50+
* it in `_finishTest`.
5151
* - **COURTESY-ATTACH** — `endpoint` is unset and no binary can be resolved, but something already
5252
* answers `http://127.0.0.1:9222/json/version` (e.g. `obscura serve` started by hand, or by CI
5353
* before this process ever ran). The helper attaches to it and never kills it — it isn't the
@@ -60,12 +60,13 @@ const config = {}
6060
* it directly) and the helper launches and tears it down for you automatically:
6161
*
6262
* ```sh
63-
* curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.0/obscura-x86_64-linux.tar.gz | tar xz
63+
* curl -sL https://github.com/h4ckf0r0day/obscura/releases/download/v0.2.2/obscura-x86_64-linux.tar.gz | tar xz
6464
* ```
6565
*
66-
* `--allow-private-network` is always passed by this helper (it's required to reach apps running
67-
* on `localhost`/private IPs, e.g. a dev server on `127.0.0.1:8000` — Obscura blocks
68-
* private-network requests by default).
66+
* `--allow-private-network` and `--allow-file-access` are always passed by this helper: the first
67+
* is required to reach apps running on `localhost`/private IPs, e.g. a dev server on
68+
* `127.0.0.1:8000`, the second to let `attachFile` upload local files. Obscura blocks both by
69+
* default.
6970
*
7071
* ## Config presets
7172
*
@@ -84,7 +85,7 @@ const config = {}
8485
* ## Limitations
8586
*
8687
* - `input` is always `synthetic`, even on rendering builds — see `input` above.
87-
* - No frames, popups, or file uploads.
88+
* - No frames or popups.
8889
* - On `-no-render` builds and v0.1.x: no screenshots, no visibility assertions
8990
* (`seeElement`/`dontSeeElement` always throw) — only DOM presence
9091
* (`seeElementInDOM`/`dontSeeElementInDOM`) is meaningful without a layout engine.
@@ -179,7 +180,7 @@ class Obscura extends CDPBrowser {
179180
const port = this.options.port || (await this._findFreePort())
180181
this.options.port = port
181182
this.serverError = null
182-
this.serverProcess = spawn(binaryPath, ['serve', '--port', String(port), '--allow-private-network'], { stdio: 'ignore' })
183+
this.serverProcess = spawn(binaryPath, ['serve', '--port', String(port), '--allow-private-network', '--allow-file-access'], { stdio: 'ignore' })
183184
this.serverProcess.on('error', err => {
184185
this.serverError = err
185186
})

lib/helper/clientscripts/cdpBrowserClient.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,17 @@ export default function installCodeceptClient(xpathNeedsPolyfill) {
387387
return true
388388
}
389389

390+
if (resolveRole(el) === 'radiogroup') {
391+
if (values.length > 1) return '__RADIOGROUP_MULTI__'
392+
const radios = Array.from(el.querySelectorAll('[role="radio"]'))
393+
const [wanted] = values
394+
const named = (radio, matchFn) => roleTextCandidates(radio).some(matchFn)
395+
const radio = radios.find(r => named(r, t => t === wanted)) || radios.find(r => named(r, t => t.indexOf(wanted) !== -1))
396+
if (!radio) return false
397+
radio.click()
398+
return true
399+
}
400+
390401
// ARIA combobox/listbox widgets: click the trigger (if any) to reveal the
391402
// listbox, then click each matching [role="option"].
392403
let container = el

test/helper/webapi.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,8 @@ export function tests() {
588588
await I.seeCheckboxIsChecked('Airplane mode')
589589
})
590590

591-
it('checks a radio by its label', async () => {
591+
it('checks a radio by its label', async function () {
592+
if (page === 'baseui' && isHelper('Obscura')) this.skip()
592593
await open(page)
593594
await I.dontSeeCheckboxIsChecked('Comfortable')
594595

@@ -753,6 +754,10 @@ export function tests() {
753754

754755
for (const page of Object.keys(pages)) {
755756
describe(page, () => {
757+
beforeEach(function () {
758+
if (page === 'baseui' && isHelper('Obscura')) this.skip()
759+
})
760+
756761
it('checks the radio matching the option and unchecks its siblings', async () => {
757762
await open(page)
758763
await I.selectOption('Density', 'Compact')

0 commit comments

Comments
 (0)