@@ -153,6 +153,10 @@ class Select {
153153 throw new Error ( `Select only works on <select> elements` )
154154 }
155155 } )
156+
157+ this . element . getAttribute ( 'multiple' ) . then ( ( multiple ) => {
158+ this . multiple = multiple !== null && multiple !== 'false'
159+ } )
156160 }
157161
158162 /**
@@ -254,30 +258,46 @@ class Select {
254258 async selectByVisibleText ( text ) {
255259 text = typeof text === 'number' ? text . toString ( ) : text
256260
257- const normalized = text
258- . trim ( ) // strip leading and trailing white-space characters
259- . replace ( / \s + / , ' ' ) // replace sequences of whitespace characters by a single space
261+ const xpath = './/option[normalize-space(.) = ' + escapeQuotes ( text ) + ']'
260262
261- /**
262- * find option element using xpath
263- */
264- const formatted = / " / . test ( normalized )
265- ? 'concat("' + normalized . split ( '"' ) . join ( '", \'"\', "' ) + '")'
266- : `"${ normalized } "`
267- const dotFormat = `[. = ${ formatted } ]`
268- const spaceFormat = `[normalize-space(text()) = ${ formatted } ]`
263+ const options = await this . element . findElements ( By . xpath ( xpath ) )
269264
270- const selections = [
271- `./ option${ dotFormat } ` ,
272- `./option ${ spaceFormat } ` ,
273- `./optgroup/option ${ dotFormat } ` ,
274- `./optgroup/option ${ spaceFormat } ` ,
275- ]
265+ for ( let option of options ) {
266+ await this . setSelected ( option )
267+ if ( ! ( await this . isMultiple ( ) ) ) {
268+ return
269+ }
270+ }
276271
277- const optionElement = await this . element . findElement ( {
278- xpath : selections . join ( '|' ) ,
279- } )
280- await this . setSelected ( optionElement )
272+ let matched = Array . isArray ( options ) && options . length > 0
273+
274+ if ( ! matched && text . includes ( ' ' ) ) {
275+ const subStringWithoutSpace = getLongestSubstringWithoutSpace ( text )
276+ let candidates
277+ if ( '' === subStringWithoutSpace ) {
278+ candidates = await this . element . findElements ( By . tagName ( 'option' ) )
279+ } else {
280+ const xpath = './/option[contains(., ' + escapeQuotes ( subStringWithoutSpace ) + ')]'
281+ candidates = await this . element . findElements ( By . xpath ( xpath ) )
282+ }
283+
284+ const trimmed = text . trim ( )
285+
286+ for ( let option of candidates ) {
287+ const optionText = await option . getText ( )
288+ if ( trimmed === optionText . trim ( ) ) {
289+ await this . setSelected ( option )
290+ if ( ! ( await this . isMultiple ( ) ) ) {
291+ return
292+ }
293+ matched = true
294+ }
295+ }
296+ }
297+
298+ if ( ! matched ) {
299+ throw new Error ( `Cannot locate option with text: ${ text } ` )
300+ }
281301 }
282302
283303 /**
@@ -293,7 +313,7 @@ class Select {
293313 * @returns {Promise<boolean> }
294314 */
295315 async isMultiple ( ) {
296- return ( await this . element . getAttribute ( ' multiple' ) ) !== null
316+ return this . multiple
297317 }
298318
299319 /**
@@ -457,4 +477,42 @@ class Select {
457477 }
458478}
459479
460- module . exports = { Select }
480+ function escapeQuotes ( toEscape ) {
481+ if ( toEscape . includes ( `"` ) && toEscape . includes ( `'` ) ) {
482+ const quoteIsLast = toEscape . lastIndexOf ( `"` ) === toEscape . length - 1
483+ const substrings = toEscape . split ( `"` )
484+
485+ // Remove the last element if it's an empty string
486+ if ( substrings [ substrings . length - 1 ] === '' ) {
487+ substrings . pop ( )
488+ }
489+
490+ let result = 'concat('
491+
492+ for ( let i = 0 ; i < substrings . length ; i ++ ) {
493+ result += `"${ substrings [ i ] } "`
494+ result += i === substrings . length - 1 ? ( quoteIsLast ? `, '"')` : `)` ) : `, '"', `
495+ }
496+ return result
497+ }
498+
499+ if ( toEscape . includes ( '"' ) ) {
500+ return `'${ toEscape } '`
501+ }
502+
503+ // Otherwise return the quoted string
504+ return `"${ toEscape } "`
505+ }
506+
507+ function getLongestSubstringWithoutSpace ( text ) {
508+ let words = text . split ( ' ' )
509+ let longestString = ''
510+ for ( let word of words ) {
511+ if ( word . length > longestString . length ) {
512+ longestString = word
513+ }
514+ }
515+ return longestString
516+ }
517+
518+ module . exports = { Select, escapeQuotes }
0 commit comments