diff --git a/common/src/web/select_space.html b/common/src/web/select_space.html new file mode 100644 index 0000000000000..9e237e5eca9ce --- /dev/null +++ b/common/src/web/select_space.html @@ -0,0 +1,14 @@ + + + +Multiple Selection test page + + + elements`) } }) + + this.element.getAttribute('multiple').then((multiple) => { + this.multiple = multiple !== null && multiple !== 'false' + }) } /** @@ -254,30 +258,46 @@ class Select { async selectByVisibleText(text) { text = typeof text === 'number' ? text.toString() : text - const normalized = text - .trim() // strip leading and trailing white-space characters - .replace(/\s+/, ' ') // replace sequences of whitespace characters by a single space + const xpath = './/option[normalize-space(.) = ' + escapeQuotes(text) + ']' - /** - * find option element using xpath - */ - const formatted = /"/.test(normalized) - ? 'concat("' + normalized.split('"').join('", \'"\', "') + '")' - : `"${normalized}"` - const dotFormat = `[. = ${formatted}]` - const spaceFormat = `[normalize-space(text()) = ${formatted}]` + const options = await this.element.findElements(By.xpath(xpath)) - const selections = [ - `./option${dotFormat}`, - `./option${spaceFormat}`, - `./optgroup/option${dotFormat}`, - `./optgroup/option${spaceFormat}`, - ] + for (let option of options) { + await this.setSelected(option) + if (!(await this.isMultiple())) { + return + } + } - const optionElement = await this.element.findElement({ - xpath: selections.join('|'), - }) - await this.setSelected(optionElement) + let matched = Array.isArray(options) && options.length > 0 + + if (!matched && text.includes(' ')) { + const subStringWithoutSpace = getLongestSubstringWithoutSpace(text) + let candidates + if ('' === subStringWithoutSpace) { + candidates = await this.element.findElements(By.tagName('option')) + } else { + const xpath = './/option[contains(., ' + escapeQuotes(subStringWithoutSpace) + ')]' + candidates = await this.element.findElements(By.xpath(xpath)) + } + + const trimmed = text.trim() + + for (let option of candidates) { + const optionText = await option.getText() + if (trimmed === optionText.trim()) { + await this.setSelected(option) + if (!(await this.isMultiple())) { + return + } + matched = true + } + } + } + + if (!matched) { + throw new Error(`Cannot locate option with text: ${text}`) + } } /** @@ -293,7 +313,7 @@ class Select { * @returns {Promise} */ async isMultiple() { - return (await this.element.getAttribute('multiple')) !== null + return this.multiple } /** @@ -457,4 +477,42 @@ class Select { } } -module.exports = { Select } +function escapeQuotes(toEscape) { + if (toEscape.includes(`"`) && toEscape.includes(`'`)) { + const quoteIsLast = toEscape.lastIndexOf(`"`) === toEscape.length - 1 + const substrings = toEscape.split(`"`) + + // Remove the last element if it's an empty string + if (substrings[substrings.length - 1] === '') { + substrings.pop() + } + + let result = 'concat(' + + for (let i = 0; i < substrings.length; i++) { + result += `"${substrings[i]}"` + result += i === substrings.length - 1 ? (quoteIsLast ? `, '"')` : `)`) : `, '"', ` + } + return result + } + + if (toEscape.includes('"')) { + return `'${toEscape}'` + } + + // Otherwise return the quoted string + return `"${toEscape}"` +} + +function getLongestSubstringWithoutSpace(text) { + let words = text.split(' ') + let longestString = '' + for (let word of words) { + if (word.length > longestString.length) { + longestString = word + } + } + return longestString +} + +module.exports = { Select, escapeQuotes } diff --git a/javascript/node/selenium-webdriver/lib/test/fileserver.js b/javascript/node/selenium-webdriver/lib/test/fileserver.js index aa270d54f91d8..337c1b9205137 100644 --- a/javascript/node/selenium-webdriver/lib/test/fileserver.js +++ b/javascript/node/selenium-webdriver/lib/test/fileserver.js @@ -94,6 +94,7 @@ const Pages = (function () { addPage('scrollingPage', 'scrollingPage.html') addPage('selectableItemsPage', 'selectableItems.html') addPage('selectPage', 'selectPage.html') + addPage('selectSpacePage', 'select_space.html') addPage('simpleTestPage', 'simpleTest.html') addPage('simpleXmlDocument', 'simple.xml') addPage('sleepingPage', 'sleep') diff --git a/javascript/node/selenium-webdriver/test/select_test.js b/javascript/node/selenium-webdriver/test/select_test.js index b6dc61377f067..b4681ee7ed715 100644 --- a/javascript/node/selenium-webdriver/test/select_test.js +++ b/javascript/node/selenium-webdriver/test/select_test.js @@ -20,6 +20,7 @@ const assert = require('node:assert') const { Select, By } = require('..') const { Pages, suite } = require('../lib/test') +const { escapeQuotes } = require('../lib/select') let singleSelectValues1 = { name: 'selectomatic', @@ -87,6 +88,42 @@ suite( } }) + it('Should be able to select by visible text with spaces', async function () { + await driver.get(Pages.selectSpacePage) + + const elem = await driver.findElement(By.id('selectWithoutMultiple')) + const select = new Select(elem) + await select.selectByVisibleText(' five') + let selectedElement = await select.getFirstSelectedOption() + selectedElement.getText().then((text) => { + assert.strictEqual(text, ' five') + }) + }) + + it('Should convert an unquoted string into one with quotes', async function () { + assert.strictEqual(escapeQuotes('abc'), '"abc"') + assert.strictEqual(escapeQuotes('abc aqewqqw'), '"abc aqewqqw"') + assert.strictEqual(escapeQuotes(''), '""') + assert.strictEqual(escapeQuotes(' '), '" "') + assert.strictEqual(escapeQuotes(' abc '), '" abc "') + }) + + it('Should add double quotes to a string that contains a single quote', async function () { + assert.strictEqual(escapeQuotes("f'oo"), `"f'oo"`) + }) + + it('Should add single quotes to a string that contains a double quotes', async function () { + assert.strictEqual(escapeQuotes('f"oo'), `'f"oo'`) + }) + + it('Should provide concatenated strings when string to escape contains both single and double quotes', async function () { + assert.strictEqual(escapeQuotes(`f"o'o`), `concat("f", '"', "o'o")`) + }) + + it('Should provide concatenated strings when string ends with quote', async function () { + assert.strictEqual(escapeQuotes(`'"`), `concat("'", '"')`) + }) + it('Should select by multiple index', async function () { await driver.get(Pages.formPage)