Skip to content

Commit 7aa361f

Browse files
author
DavertMik
committed
fix: resolve aria-labelledby for combobox/listbox in WebDriver selectOption
1 parent 46ad07e commit 7aa361f

File tree

1 file changed

+48
-14
lines changed

1 file changed

+48
-14
lines changed

lib/helper/WebDriver.js

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,11 +1314,11 @@ class WebDriver extends Helper {
13141314

13151315
// Fuzzy: try combobox
13161316
this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
1317-
let els = await this._locateByRole({ role: 'combobox', name: matchedLocator.value })
1317+
let els = await this._locateByRole({ role: 'combobox', text: matchedLocator.value })
13181318
if (els?.length) return proceedSelectOption.call(this, usingFirstElement(els), option)
13191319

13201320
// Fuzzy: try listbox
1321-
els = await this._locateByRole({ role: 'listbox', name: matchedLocator.value })
1321+
els = await this._locateByRole({ role: 'listbox', text: matchedLocator.value })
13221322
if (els?.length) return proceedSelectOption.call(this, usingFirstElement(els), option)
13231323

13241324
// Fuzzy: try native select
@@ -3124,7 +3124,23 @@ async function getElementTextAttributes(element) {
31243124
const ariaLabel = await this.browser.getElementAttribute(elementId, 'aria-label').catch(() => '')
31253125
const placeholder = await this.browser.getElementAttribute(elementId, 'placeholder').catch(() => '')
31263126
const innerText = await this.browser.getElementText(elementId).catch(() => '')
3127-
return [ariaLabel, placeholder, innerText]
3127+
3128+
// Handle aria-labelledby
3129+
const labelledBy = await this.browser.getElementAttribute(elementId, 'aria-labelledby').catch(() => '')
3130+
let labelText = ''
3131+
if (labelledBy) {
3132+
try {
3133+
const labelId = labelledBy.split(' ')[0]
3134+
const labelEls = await this.browser.$$(`#${labelId}`)
3135+
if (labelEls?.length) {
3136+
labelText = await this.browser.getElementText(getElementId(labelEls[0])).catch(() => '')
3137+
}
3138+
} catch (e) {
3139+
// Ignore errors when resolving aria-labelledby
3140+
}
3141+
}
3142+
3143+
return [ariaLabel, placeholder, innerText, labelText]
31283144
}
31293145

31303146
async function isElementChecked(browser, elementId) {
@@ -3382,38 +3398,56 @@ async function proceedSelectOption(elem, option) {
33823398
highlightActiveElement.call(this, elem)
33833399
const ariaOwns = await this.browser.getElementAttribute(elementId, 'aria-owns').catch(() => null)
33843400
const ariaControls = await this.browser.getElementAttribute(elementId, 'aria-controls').catch(() => null)
3401+
const ariaLabelledBy = await this.browser.getElementAttribute(elementId, 'aria-labelledby').catch(() => null)
33853402
await this.browser.elementClick(elementId)
3386-
await this._waitForAction()
33873403

33883404
const listboxId = ariaOwns || ariaControls
33893405
let listbox = null
33903406
if (listboxId) {
33913407
const listboxEls = await this.browser.$$(`#${listboxId}`)
33923408
if (listboxEls?.length) listbox = listboxEls[0]
33933409
}
3394-
if (!listbox) {
3395-
const listboxEls = await this._locateByRole({ role: 'listbox' })
3410+
if (!listbox && ariaLabelledBy) {
3411+
// Find listbox with the same aria-labelledby
3412+
const listboxEls = await this.browser.$$(`[role="listbox"][aria-labelledby="${ariaLabelledBy}"]`)
33963413
if (listboxEls?.length) listbox = listboxEls[0]
33973414
}
3415+
if (!listbox) {
3416+
// Fallback: find any listbox with the same label
3417+
const allListboxes = await this.browser.$$(`[role="listbox"]`)
3418+
for (const lb of allListboxes) {
3419+
const lbLabelledBy = await this.browser.getElementAttribute(getElementId(lb), 'aria-labelledby').catch(() => '')
3420+
if (lbLabelledBy === ariaLabelledBy) {
3421+
listbox = lb
3422+
break
3423+
}
3424+
}
3425+
}
33983426

33993427
if (listbox) {
34003428
const listboxElementId = getElementId(listbox)
34013429
for (const opt of options) {
3402-
const optEls = await this._locateByRole({ role: 'option', text: opt })
3430+
const optEls = await this.browser.findElementsFromElement(listboxElementId, 'xpath', `.//*[@role="option"]`)
34033431
if (optEls?.length) {
3404-
const optEl = optEls[0]
3405-
this.debugSection('SelectOption', `Clicking: "${opt}"`)
3406-
highlightActiveElement.call(this, optEl)
3407-
await this.browser.elementClick(getElementId(optEl))
3432+
for (const optEl of optEls) {
3433+
const optElId = getElementId(optEl)
3434+
const text = await this.browser.getElementText(optElId).catch(() => '')
3435+
if (text && text.includes(opt)) {
3436+
this.debugSection('SelectOption', `Clicking: "${opt}"`)
3437+
highlightActiveElement.call(this, optEl)
3438+
await this.browser.elementClick(optElId)
3439+
break
3440+
}
3441+
}
34083442
}
34093443
}
34103444
}
3411-
return this._waitForAction()
3445+
return
34123446
}
34133447

34143448
if (role === 'listbox') {
34153449
for (const opt of options) {
3416-
const optEls = await this.browser.findElementsFromElement(elementId, 'css', `[role="option"]`)
3450+
const optEls = await this.browser.findElementsFromElement(elementId, 'xpath', `.//*[@role="option"]`)
34173451
if (optEls?.length) {
34183452
for (const optEl of optEls) {
34193453
const optElId = getElementId(optEl)
@@ -3427,7 +3461,7 @@ async function proceedSelectOption(elem, option) {
34273461
}
34283462
}
34293463
}
3430-
return this._waitForAction()
3464+
return
34313465
}
34323466

34333467
// Native <select> element

0 commit comments

Comments
 (0)