Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[js] Ensure 'selectVisibleByText' method is same as other languages #13899

Merged
merged 1 commit into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading