@@ -1302,33 +1302,29 @@ class WebDriver extends Helper {
13021302 * {{> selectOption }}
13031303 */
13041304 async selectOption ( select , option ) {
1305- const res = await findFields . call ( this , select )
1306- assertElementExists ( res , select , 'Selectable field' )
1307- const elem = usingFirstElement ( res )
1308- highlightActiveElement . call ( this , elem )
1305+ const matchedLocator = new Locator ( select )
13091306
1310- if ( ! Array . isArray ( option ) ) {
1311- option = [ option ]
1307+ // Strict locator
1308+ if ( ! matchedLocator . isFuzzy ( ) ) {
1309+ this . debugSection ( 'SelectOption' , `Strict: ${ JSON . stringify ( select ) } ` )
1310+ const els = await this . _locate ( select )
1311+ assertElementExists ( els , select , 'Selectable element' )
1312+ return proceedSelectOption . call ( this , usingFirstElement ( els ) , option )
13121313 }
13131314
1314- // select options by visible text
1315- let els = await forEachAsync ( option , async opt => this . browser . findElementsFromElement ( getElementId ( elem ) , 'xpath' , Locator . select . byVisibleText ( xpathLocator . literal ( opt ) ) ) )
1315+ // Fuzzy: try combobox
1316+ this . debugSection ( 'SelectOption' , `Fuzzy: "${ matchedLocator . value } "` )
1317+ let els = await this . _locateByRole ( { role : 'combobox' , name : matchedLocator . value } )
1318+ if ( els ?. length ) return proceedSelectOption . call ( this , usingFirstElement ( els ) , option )
13161319
1317- const clickOptionFn = async el => {
1318- if ( el [ 0 ] ) el = el [ 0 ]
1319- const elementId = getElementId ( el )
1320- if ( elementId ) return this . browser . elementClick ( elementId )
1321- }
1320+ // Fuzzy: try listbox
1321+ els = await this . _locateByRole ( { role : 'listbox' , name : matchedLocator . value } )
1322+ if ( els ?. length ) return proceedSelectOption . call ( this , usingFirstElement ( els ) , option )
13221323
1323- if ( Array . isArray ( els ) && els . length ) {
1324- return forEachAsync ( els , clickOptionFn )
1325- }
1326- // select options by value
1327- els = await forEachAsync ( option , async opt => this . browser . findElementsFromElement ( getElementId ( elem ) , 'xpath' , Locator . select . byValue ( xpathLocator . literal ( opt ) ) ) )
1328- if ( els . length === 0 ) {
1329- throw new ElementNotFound ( select , `Option "${ option } " in` , 'was not found neither by a visible text nor by a value' )
1330- }
1331- return forEachAsync ( els , clickOptionFn )
1324+ // Fuzzy: try native select
1325+ const res = await findFields . call ( this , select )
1326+ assertElementExists ( res , select , 'Selectable field' )
1327+ return proceedSelectOption . call ( this , usingFirstElement ( res ) , option )
13321328 }
13331329
13341330 /**
@@ -3376,4 +3372,89 @@ function logEvents(event) {
33763372 browserLogs . push ( event . text ) // add log message to the array
33773373}
33783374
3375+ async function proceedSelectOption ( elem , option ) {
3376+ const elementId = getElementId ( elem )
3377+ const role = await this . browser . getElementAttribute ( elementId , 'role' ) . catch ( ( ) => null )
3378+ const options = Array . isArray ( option ) ? option : [ option ]
3379+
3380+ if ( role === 'combobox' ) {
3381+ this . debugSection ( 'SelectOption' , 'Expanding combobox' )
3382+ highlightActiveElement . call ( this , elem )
3383+ const ariaOwns = await this . browser . getElementAttribute ( elementId , 'aria-owns' ) . catch ( ( ) => null )
3384+ const ariaControls = await this . browser . getElementAttribute ( elementId , 'aria-controls' ) . catch ( ( ) => null )
3385+ await this . browser . elementClick ( elementId )
3386+ await this . _waitForAction ( )
3387+
3388+ const listboxId = ariaOwns || ariaControls
3389+ let listbox = null
3390+ if ( listboxId ) {
3391+ const listboxEls = await this . browser . $$ ( `#${ listboxId } ` )
3392+ if ( listboxEls ?. length ) listbox = listboxEls [ 0 ]
3393+ }
3394+ if ( ! listbox ) {
3395+ const listboxEls = await this . _locateByRole ( { role : 'listbox' } )
3396+ if ( listboxEls ?. length ) listbox = listboxEls [ 0 ]
3397+ }
3398+
3399+ if ( listbox ) {
3400+ const listboxElementId = getElementId ( listbox )
3401+ for ( const opt of options ) {
3402+ const optEls = await this . _locateByRole ( { role : 'option' , text : opt } )
3403+ 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 ) )
3408+ }
3409+ }
3410+ }
3411+ return this . _waitForAction ( )
3412+ }
3413+
3414+ if ( role === 'listbox' ) {
3415+ for ( const opt of options ) {
3416+ const optEls = await this . browser . findElementsFromElement ( elementId , 'css' , `[role="option"]` )
3417+ if ( optEls ?. length ) {
3418+ for ( const optEl of optEls ) {
3419+ const optElId = getElementId ( optEl )
3420+ const text = await this . browser . getElementText ( optElId ) . catch ( ( ) => '' )
3421+ if ( text && text . includes ( opt ) ) {
3422+ this . debugSection ( 'SelectOption' , `Clicking: "${ opt } "` )
3423+ highlightActiveElement . call ( this , optEl )
3424+ await this . browser . elementClick ( optElId )
3425+ break
3426+ }
3427+ }
3428+ }
3429+ }
3430+ return this . _waitForAction ( )
3431+ }
3432+
3433+ // Native <select> element
3434+ highlightActiveElement . call ( this , elem )
3435+
3436+ if ( ! Array . isArray ( option ) ) {
3437+ option = [ option ]
3438+ }
3439+
3440+ const clickOptionFn = async el => {
3441+ if ( el [ 0 ] ) el = el [ 0 ]
3442+ const elId = getElementId ( el )
3443+ if ( elId ) return this . browser . elementClick ( elId )
3444+ }
3445+
3446+ // select options by visible text
3447+ let els = await forEachAsync ( option , async opt => this . browser . findElementsFromElement ( elementId , 'xpath' , Locator . select . byVisibleText ( xpathLocator . literal ( opt ) ) ) )
3448+
3449+ if ( Array . isArray ( els ) && els . length ) {
3450+ return forEachAsync ( els , clickOptionFn )
3451+ }
3452+ // select options by value
3453+ els = await forEachAsync ( option , async opt => this . browser . findElementsFromElement ( elementId , 'xpath' , Locator . select . byValue ( xpathLocator . literal ( opt ) ) ) )
3454+ if ( els . length === 0 ) {
3455+ throw new ElementNotFound ( elem , `Option "${ option } " in` , 'was not found neither by a visible text nor by a value' )
3456+ }
3457+ return forEachAsync ( els , clickOptionFn )
3458+ }
3459+
33793460export { WebDriver as default }
0 commit comments