Skip to content

Commit 4edbd3d

Browse files
author
DavertMik
committed
improved locators for selectOption
1 parent 493df00 commit 4edbd3d

File tree

7 files changed

+68
-14
lines changed

7 files changed

+68
-14
lines changed

lib/helper/Playwright.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2492,24 +2492,22 @@ class Playwright extends Helper {
24922492
const context = await this.context
24932493
const matchedLocator = new Locator(select)
24942494

2495-
// Strict locator
24962495
if (!matchedLocator.isFuzzy()) {
24972496
this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
24982497
const els = await this._locate(matchedLocator)
24992498
assertElementExists(els, select, 'Selectable element')
25002499
return proceedSelect.call(this, context, els[0], option)
25012500
}
25022501

2503-
// Fuzzy: try combobox
25042502
this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
2505-
let els = await findByRole(context, { role: 'combobox', name: matchedLocator.value })
2506-
if (els?.length) return proceedSelect.call(this, context, els[0], option)
2503+
const literal = xpathLocator.literal(matchedLocator.value)
25072504

2508-
// Fuzzy: try listbox
2509-
els = await findByRole(context, { role: 'listbox', name: matchedLocator.value })
2510-
if (els?.length) return proceedSelect.call(this, context, els[0], option)
2505+
let els = await this._locate({ xpath: Locator.select.narrow(literal) })
2506+
if (els.length) return proceedSelect.call(this, context, els[0], option)
2507+
2508+
els = await this._locate({ xpath: Locator.select.wide(literal) })
2509+
if (els.length) return proceedSelect.call(this, context, els[0], option)
25112510

2512-
// Fuzzy: try native select
25132511
els = await findFields.call(this, select)
25142512
assertElementExists(els, select, 'Selectable element')
25152513
return proceedSelect.call(this, context, els[0], option)
@@ -2559,6 +2557,10 @@ class Playwright extends Helper {
25592557
*
25602558
*/
25612559
async see(text, context = null) {
2560+
// If only one argument passed and it's an object without custom toString(), treat as locator
2561+
if (!context && text && typeof text === 'object' && !Array.isArray(text) && text.toString === Object.prototype.toString) {
2562+
return this.seeElement(text)
2563+
}
25622564
return proceedSee.call(this, 'assert', text, context)
25632565
}
25642566

@@ -4630,7 +4632,7 @@ async function proceedSelect(context, el, option) {
46304632
const role = await el.getAttribute('role')
46314633
const options = Array.isArray(option) ? option : [option]
46324634

4633-
if (role === 'combobox') {
4635+
if (role === 'combobox' || role === 'button') {
46344636
this.debugSection('SelectOption', 'Expanding combobox')
46354637
await highlightActiveElement.call(this, el)
46364638
const [ariaOwns, ariaControls] = await Promise.all([el.getAttribute('aria-owns'), el.getAttribute('aria-controls')])

lib/helper/Puppeteer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,6 +1690,10 @@ class Puppeteer extends Helper {
16901690
* {{ react }}
16911691
*/
16921692
async see(text, context = null) {
1693+
// If only one argument passed and it's an object without custom toString(), treat as locator
1694+
if (!context && text && typeof text === 'object' && !Array.isArray(text) && text.toString === Object.prototype.toString) {
1695+
return this.seeElement(text)
1696+
}
16931697
return proceedSee.call(this, 'assert', text, context)
16941698
}
16951699

lib/helper/WebDriver.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1563,6 +1563,10 @@ class WebDriver extends Helper {
15631563
* {{ react }}
15641564
*/
15651565
async see(text, context = null) {
1566+
// If only one argument passed and it's an object without custom toString(), treat as locator
1567+
if (!context && text && typeof text === 'object' && !Array.isArray(text) && text.toString === Object.prototype.toString) {
1568+
return this.seeElement(text)
1569+
}
15661570
return proceedSee.call(this, 'assert', text, context)
15671571
}
15681572

@@ -2399,6 +2403,10 @@ class WebDriver extends Helper {
23992403
})
24002404
}
24012405

2406+
async _waitForAction() {
2407+
return this.wait(0.1)
2408+
}
2409+
24022410
/**
24032411
* {{> waitForEnabled }}
24042412
*/
@@ -3030,7 +3038,10 @@ async function proceedSelect(el, option) {
30303038
const listboxId = ariaOwns || ariaControls
30313039
let listboxEls = listboxId ? await this.browser.$$(`#${listboxId}`) : []
30323040
if (!listboxEls.length) {
3033-
listboxEls = await this.browser.$$('[role="listbox"]')
3041+
listboxEls = await this.browser.findElementsFromElement(elementId, 'xpath', 'following-sibling::*[@role="listbox"]')
3042+
}
3043+
if (!listboxEls.length) {
3044+
listboxEls = await this.browser.findElementsFromElement(elementId, 'xpath', 'ancestor::*[@role="listbox"]')
30343045
}
30353046
if (!listboxEls.length) throw new Error('Cannot find listbox for combobox')
30363047
const listbox = listboxEls[0]

lib/locator.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,35 @@ Locator.checkable = {
583583
}
584584

585585
Locator.select = {
586+
/**
587+
* Narrow strategy for finding select elements.
588+
* Tries exact role match first (combobox/listbox by name), then select by name.
589+
* @param {string} literal
590+
* @returns {string}
591+
*/
592+
narrow: literal =>
593+
xpathLocator.combine([
594+
`.//*[@role = 'combobox'][normalize-space(@name) = ${literal}]`,
595+
`.//*[@role = 'listbox'][normalize-space(@name) = ${literal}]`,
596+
`.//select[@name = ${literal}]`,
597+
]),
598+
599+
/**
600+
* Wide strategy for finding select elements.
601+
* Includes select, button, combobox, listbox with various accessibility attributes.
602+
* @param {string} literal
603+
* @returns {string}
604+
*/
605+
wide: literal =>
606+
xpathLocator.combine([
607+
`.//select[((./@name = ${literal}) or ./@id = //label[@for][normalize-space(string(.)) = ${literal}]/@for or ./@placeholder = ${literal})]`,
608+
`.//*[@role='button' or @role='combobox' or @role='listbox'][normalize-space(string(.)) = ${literal}]`,
609+
`.//*[@role='button' or @role='combobox' or @role='listbox'][@aria-label = ${literal}]`,
610+
`.//*[@role='button' or @role='combobox' or @role='listbox'][@title = ${literal}]`,
611+
`.//*[@role='button' or @role='combobox' or @role='listbox'][@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id ]`,
612+
`.//label[normalize-space(string(.)) = ${literal}]//*[self::button or @role='button' or @role='combobox' or @role='listbox']`,
613+
]),
614+
586615
/**
587616
* @param {string} opt
588617
* @returns {string}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codeceptjs",
3-
"version": "4.0.1-beta.23",
3+
"version": "4.0.1-beta.24",
44
"type": "module",
55
"description": "Supercharged End 2 End Testing Framework for NodeJS",
66
"keywords": [

test/helper/Playwright_test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,9 +495,9 @@ describe('Playwright', function () {
495495
})
496496

497497
describe('#selectOption', () => {
498-
it('should select option by label and partial option text', async () => {
498+
it('should select option by label and option text', async () => {
499499
await I.amOnPage('/form/select')
500-
await I.selectOption('Select your age', '21-')
500+
await I.selectOption('Select your age', '21-60')
501501
await I.click('Submit')
502502
assert.equal(formContents('age'), 'adult')
503503
})
@@ -511,7 +511,7 @@ describe('Playwright', function () {
511511

512512
it('should select option using JSON string css locator', async () => {
513513
await I.amOnPage('/form/select')
514-
await I.selectOption('{"css": "#age"}', '21-')
514+
await I.selectOption('{"css": "#age"}', '21-60')
515515
await I.click('Submit')
516516
assert.equal(formContents('age'), 'adult')
517517
})

test/helper/webapi.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ export function tests() {
139139
await I.amOnPage('/')
140140
await I.see('With special space chars')
141141
})
142+
143+
it('should treat locator object without custom toString as seeElement', async () => {
144+
await I.amOnPage('/form/field')
145+
// Passing a locator object without custom toString should call seeElement
146+
await I.see({ css: 'input[name=name]' })
147+
await I.see({ xpath: '//input[@id="name"]' })
148+
await I.see({ name: 'name' })
149+
})
142150
})
143151

144152
describe('see element : #seeElement, #seeElementInDOM, #dontSeeElement', () => {

0 commit comments

Comments
 (0)