Skip to content

Commit

Permalink
[js] Ensure 'selectVisibleByText' method is same as other languages
Browse files Browse the repository at this point in the history
  • Loading branch information
pujagani committed May 2, 2024
1 parent 753dec8 commit 79b6650
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 23 deletions.
14 changes: 14 additions & 0 deletions common/src/web/select_space.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Multiple Selection test page</title>
</head>
<body>
<select id="selectWithoutMultiple">
<option value="one">one</option>
<option value="two">&nbsp;&nbsp;two</option>
<option value="three">&nbsp;&nbsp;&nbsp;three</option>
<option value="four">&nbsp;&nbsp;&nbsp;&nbsp;four</option>
<option value="five">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;five</option>
</body>
</html>
104 changes: 81 additions & 23 deletions javascript/node/selenium-webdriver/lib/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ class Select {
throw new Error(`Select only works on <select> elements`)
}
})

this.element.getAttribute('multiple').then((multiple) => {
this.multiple = multiple !== null && multiple !== 'false'
})
}

/**
Expand Down Expand Up @@ -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}`)
}
}

/**
Expand All @@ -293,7 +313,7 @@ class Select {
* @returns {Promise<boolean>}
*/
async isMultiple() {
return (await this.element.getAttribute('multiple')) !== null
return this.multiple
}

/**
Expand Down Expand Up @@ -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 }
1 change: 1 addition & 0 deletions javascript/node/selenium-webdriver/lib/test/fileserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
37 changes: 37 additions & 0 deletions javascript/node/selenium-webdriver/test/select_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 79b6650

Please sign in to comment.