forked from annv/MinigameTest
467 lines
13 KiB
JavaScript
467 lines
13 KiB
JavaScript
|
|
// Licensed to the Software Freedom Conservancy (SFC) under one
|
||
|
|
// or more contributor license agreements. See the NOTICE file
|
||
|
|
// distributed with this work for additional information
|
||
|
|
// regarding copyright ownership. The SFC licenses this file
|
||
|
|
// to you under the Apache License, Version 2.0 (the
|
||
|
|
// "License"); you may not use this file except in compliance
|
||
|
|
// with the License. You may obtain a copy of the License at
|
||
|
|
//
|
||
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
//
|
||
|
|
// Unless required by applicable law or agreed to in writing,
|
||
|
|
// software distributed under the License is distributed on an
|
||
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
|
// KIND, either express or implied. See the License for the
|
||
|
|
// specific language governing permissions and limitations
|
||
|
|
// under the License.
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Licensed to the Software Freedom Conservancy (SFC) under one
|
||
|
|
* or more contributor license agreements. See the NOTICE file
|
||
|
|
* distributed with this work for additional information
|
||
|
|
* regarding copyright ownership. The SFC licenses this file
|
||
|
|
* to you under the Apache License, Version 2.0 (the
|
||
|
|
* "License"); you may not use this file except in compliance
|
||
|
|
* with the License. You may obtain a copy of the License at
|
||
|
|
*
|
||
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
*
|
||
|
|
* Unless required by applicable law or agreed to in writing,
|
||
|
|
* software distributed under the License is distributed on an
|
||
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
|
* KIND, either express or implied. See the License for the
|
||
|
|
* specific language governing permissions and limitations
|
||
|
|
* under the License.
|
||
|
|
*/
|
||
|
|
|
||
|
|
'use strict'
|
||
|
|
|
||
|
|
const { By, escapeCss } = require('./by')
|
||
|
|
const error = require('./error')
|
||
|
|
|
||
|
|
/**
|
||
|
|
* ISelect interface makes a protocol for all kind of select elements (standard html and custom
|
||
|
|
* model)
|
||
|
|
*
|
||
|
|
* @interface
|
||
|
|
*/
|
||
|
|
// eslint-disable-next-line no-unused-vars
|
||
|
|
class ISelect {
|
||
|
|
/**
|
||
|
|
* @return {!Promise<boolean>} Whether this select element supports selecting multiple options at the same time? This
|
||
|
|
* is done by checking the value of the "multiple" attribute.
|
||
|
|
*/
|
||
|
|
isMultiple() {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return {!Promise<!Array<!WebElement>>} All options belonging to this select tag
|
||
|
|
*/
|
||
|
|
getOptions() {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return {!Promise<!Array<!WebElement>>} All selected options belonging to this select tag
|
||
|
|
*/
|
||
|
|
getAllSelectedOptions() {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return {!Promise<!WebElement>} The first selected option in this select tag (or the currently selected option in a
|
||
|
|
* normal select)
|
||
|
|
*/
|
||
|
|
getFirstSelectedOption() {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Select all options that display text matching the argument. That is, when given "Bar" this
|
||
|
|
* would select an option like:
|
||
|
|
*
|
||
|
|
* <option value="foo">Bar</option>
|
||
|
|
*
|
||
|
|
* @param {string} text The visible text to match against
|
||
|
|
* @return {Promise<void>}
|
||
|
|
*/
|
||
|
|
selectByVisibleText(text) {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Select all options that have a value matching the argument. That is, when given "foo" this
|
||
|
|
* would select an option like:
|
||
|
|
*
|
||
|
|
* <option value="foo">Bar</option>
|
||
|
|
*
|
||
|
|
* @param {string} value The value to match against
|
||
|
|
* @return {Promise<void>}
|
||
|
|
*/
|
||
|
|
selectByValue(value) {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Select the option at the given index. This is done by examining the "index" attribute of an
|
||
|
|
* element, and not merely by counting.
|
||
|
|
*
|
||
|
|
* @param {Number} index The option at this index will be selected
|
||
|
|
* @return {Promise<void>}
|
||
|
|
*/
|
||
|
|
selectByIndex(index) {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear all selected entries. This is only valid when the SELECT supports multiple selections.
|
||
|
|
*
|
||
|
|
* @return {Promise<void>}
|
||
|
|
*/
|
||
|
|
deselectAll() {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Deselect all options that display text matching the argument. That is, when given "Bar" this
|
||
|
|
* would deselect an option like:
|
||
|
|
*
|
||
|
|
* <option value="foo">Bar</option>
|
||
|
|
*
|
||
|
|
* @param {string} text The visible text to match against
|
||
|
|
* @return {Promise<void>}
|
||
|
|
*/
|
||
|
|
deselectByVisibleText(text) {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Deselect all options that have a value matching the argument. That is, when given "foo" this
|
||
|
|
* would deselect an option like:
|
||
|
|
*
|
||
|
|
* @param {string} value The value to match against
|
||
|
|
* @return {Promise<void>}
|
||
|
|
*/
|
||
|
|
deselectByValue(value) {} // eslint-disable-line
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Deselect the option at the given index. This is done by examining the "index" attribute of an
|
||
|
|
* element, and not merely by counting.
|
||
|
|
*
|
||
|
|
* @param {Number} index The option at this index will be deselected
|
||
|
|
* @return {Promise<void>}
|
||
|
|
*/
|
||
|
|
deselectByIndex(index) {} // eslint-disable-line
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @implements ISelect
|
||
|
|
*/
|
||
|
|
class Select {
|
||
|
|
/**
|
||
|
|
* Create an Select Element
|
||
|
|
* @param {WebElement} element Select WebElement.
|
||
|
|
*/
|
||
|
|
constructor(element) {
|
||
|
|
this.element = element
|
||
|
|
|
||
|
|
this.element.getAttribute('tagName').then(function (tagName) {
|
||
|
|
if (tagName.toLowerCase() !== 'select') {
|
||
|
|
throw new Error(`Select only works on <select> elements`)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* Select option with specified index.
|
||
|
|
*
|
||
|
|
* <example>
|
||
|
|
<select id="selectbox">
|
||
|
|
<option value="1">Option 1</option>
|
||
|
|
<option value="2">Option 2</option>
|
||
|
|
<option value="3">Option 3</option>
|
||
|
|
</select>
|
||
|
|
const selectBox = await driver.findElement(By.id("selectbox"));
|
||
|
|
await selectObject.selectByIndex(1);
|
||
|
|
* </example>
|
||
|
|
*
|
||
|
|
* @param index
|
||
|
|
*/
|
||
|
|
async selectByIndex(index) {
|
||
|
|
if (index < 0) {
|
||
|
|
throw new Error('Index needs to be 0 or any other positive number')
|
||
|
|
}
|
||
|
|
|
||
|
|
let options = await this.element.findElements(By.tagName('option'))
|
||
|
|
|
||
|
|
if (options.length === 0) {
|
||
|
|
throw new Error("Select element doesn't contain any option element")
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.length - 1 < index) {
|
||
|
|
throw new Error(
|
||
|
|
`Option with index "${index}" not found. Select element only contains ${
|
||
|
|
options.length - 1
|
||
|
|
} option elements`
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
for (let option of options) {
|
||
|
|
if ((await option.getAttribute('index')) === index.toString()) {
|
||
|
|
await this.setSelected(option)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* Select option by specific value.
|
||
|
|
*
|
||
|
|
* <example>
|
||
|
|
<select id="selectbox">
|
||
|
|
<option value="1">Option 1</option>
|
||
|
|
<option value="2">Option 2</option>
|
||
|
|
<option value="3">Option 3</option>
|
||
|
|
</select>
|
||
|
|
const selectBox = await driver.findElement(By.id("selectbox"));
|
||
|
|
await selectObject.selectByVisibleText("Option 2");
|
||
|
|
* </example>
|
||
|
|
*
|
||
|
|
*
|
||
|
|
* @param {string} value value of option element to be selected
|
||
|
|
*/
|
||
|
|
async selectByValue(value) {
|
||
|
|
let matched = false
|
||
|
|
let isMulti = await this.isMultiple()
|
||
|
|
|
||
|
|
let options = await this.element.findElements({
|
||
|
|
css: 'option[value =' + escapeCss(value) + ']',
|
||
|
|
})
|
||
|
|
|
||
|
|
for (let option of options) {
|
||
|
|
await this.setSelected(option)
|
||
|
|
|
||
|
|
if (!isMulti) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
matched = true
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!matched) {
|
||
|
|
throw new Error(`Cannot locate option with value: ${value}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* Select option with displayed text matching the argument.
|
||
|
|
*
|
||
|
|
* <example>
|
||
|
|
<select id="selectbox">
|
||
|
|
<option value="1">Option 1</option>
|
||
|
|
<option value="2">Option 2</option>
|
||
|
|
<option value="3">Option 3</option>
|
||
|
|
</select>
|
||
|
|
const selectBox = await driver.findElement(By.id("selectbox"));
|
||
|
|
await selectObject.selectByVisibleText("Option 2");
|
||
|
|
* </example>
|
||
|
|
*
|
||
|
|
* @param {String|Number} text text of option element to get selected
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
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
|
||
|
|
|
||
|
|
/**
|
||
|
|
* find option element using xpath
|
||
|
|
*/
|
||
|
|
const formatted = /"/.test(normalized)
|
||
|
|
? 'concat("' + normalized.split('"').join('", \'"\', "') + '")'
|
||
|
|
: `"${normalized}"`
|
||
|
|
const dotFormat = `[. = ${formatted}]`
|
||
|
|
const spaceFormat = `[normalize-space(text()) = ${formatted}]`
|
||
|
|
|
||
|
|
const selections = [
|
||
|
|
`./option${dotFormat}`,
|
||
|
|
`./option${spaceFormat}`,
|
||
|
|
`./optgroup/option${dotFormat}`,
|
||
|
|
`./optgroup/option${spaceFormat}`,
|
||
|
|
]
|
||
|
|
|
||
|
|
const optionElement = await this.element.findElement({
|
||
|
|
xpath: selections.join('|'),
|
||
|
|
})
|
||
|
|
await this.setSelected(optionElement)
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a list of all options belonging to this select tag
|
||
|
|
* @returns {!Promise<!Array<!WebElement>>}
|
||
|
|
*/
|
||
|
|
async getOptions() {
|
||
|
|
return await this.element.findElements({ tagName: 'option' })
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a boolean value if the select tag is multiple
|
||
|
|
* @returns {Promise<boolean>}
|
||
|
|
*/
|
||
|
|
async isMultiple() {
|
||
|
|
return (await this.element.getAttribute('multiple')) !== null
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a list of all selected options belonging to this select tag
|
||
|
|
*
|
||
|
|
* @returns {Promise<void>}
|
||
|
|
*/
|
||
|
|
async getAllSelectedOptions() {
|
||
|
|
const opts = await this.getOptions()
|
||
|
|
const results = []
|
||
|
|
for (let options of opts) {
|
||
|
|
if (await options.isSelected()) {
|
||
|
|
results.push(options)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return results
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns first Selected Option
|
||
|
|
* @returns {Promise<Element>}
|
||
|
|
*/
|
||
|
|
async getFirstSelectedOption() {
|
||
|
|
return (await this.getAllSelectedOptions())[0]
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Deselects all selected options
|
||
|
|
* @returns {Promise<void>}
|
||
|
|
*/
|
||
|
|
async deselectAll() {
|
||
|
|
if (!this.isMultiple()) {
|
||
|
|
throw new Error('You may only deselect all options of a multi-select')
|
||
|
|
}
|
||
|
|
|
||
|
|
const options = await this.getOptions()
|
||
|
|
|
||
|
|
for (let option of options) {
|
||
|
|
if (await option.isSelected()) {
|
||
|
|
await option.click()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param {string|Number}text text of option to deselect
|
||
|
|
* @returns {Promise<void>}
|
||
|
|
*/
|
||
|
|
async deselectByVisibleText(text) {
|
||
|
|
if (!(await this.isMultiple())) {
|
||
|
|
throw new Error('You may only deselect options of a multi-select')
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* convert value into string
|
||
|
|
*/
|
||
|
|
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
|
||
|
|
|
||
|
|
/**
|
||
|
|
* find option element using xpath
|
||
|
|
*/
|
||
|
|
const formatted = /"/.test(normalized)
|
||
|
|
? 'concat("' + normalized.split('"').join('", \'"\', "') + '")'
|
||
|
|
: `"${normalized}"`
|
||
|
|
const dotFormat = `[. = ${formatted}]`
|
||
|
|
const spaceFormat = `[normalize-space(text()) = ${formatted}]`
|
||
|
|
|
||
|
|
const selections = [
|
||
|
|
`./option${dotFormat}`,
|
||
|
|
`./option${spaceFormat}`,
|
||
|
|
`./optgroup/option${dotFormat}`,
|
||
|
|
`./optgroup/option${spaceFormat}`,
|
||
|
|
]
|
||
|
|
|
||
|
|
const optionElement = await this.element.findElement({
|
||
|
|
xpath: selections.join('|'),
|
||
|
|
})
|
||
|
|
if (await optionElement.isSelected()) {
|
||
|
|
await optionElement.click()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param {Number} index index of option element to deselect
|
||
|
|
* Deselect the option at the given index.
|
||
|
|
* This is done by examining the "index"
|
||
|
|
* attribute of an element, and not merely by counting.
|
||
|
|
* @returns {Promise<void>}
|
||
|
|
*/
|
||
|
|
async deselectByIndex(index) {
|
||
|
|
if (!(await this.isMultiple())) {
|
||
|
|
throw new Error('You may only deselect options of a multi-select')
|
||
|
|
}
|
||
|
|
|
||
|
|
if (index < 0) {
|
||
|
|
throw new Error('Index needs to be 0 or any other positive number')
|
||
|
|
}
|
||
|
|
|
||
|
|
let options = await this.element.findElements(By.tagName('option'))
|
||
|
|
|
||
|
|
if (options.length === 0) {
|
||
|
|
throw new Error("Select element doesn't contain any option element")
|
||
|
|
}
|
||
|
|
|
||
|
|
if (options.length - 1 < index) {
|
||
|
|
throw new Error(
|
||
|
|
`Option with index "${index}" not found. Select element only contains ${
|
||
|
|
options.length - 1
|
||
|
|
} option elements`
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
for (let option of options) {
|
||
|
|
if ((await option.getAttribute('index')) === index.toString()) {
|
||
|
|
if (await option.isSelected()) {
|
||
|
|
await option.click()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
*
|
||
|
|
* @param {String} value value of an option to deselect
|
||
|
|
* @returns {Promise<void>}
|
||
|
|
*/
|
||
|
|
async deselectByValue(value) {
|
||
|
|
if (!(await this.isMultiple())) {
|
||
|
|
throw new Error('You may only deselect options of a multi-select')
|
||
|
|
}
|
||
|
|
|
||
|
|
let matched = false
|
||
|
|
|
||
|
|
let options = await this.element.findElements({
|
||
|
|
css: 'option[value =' + escapeCss(value) + ']',
|
||
|
|
})
|
||
|
|
|
||
|
|
for (let option of options) {
|
||
|
|
if (await option.isSelected()) {
|
||
|
|
await option.click()
|
||
|
|
}
|
||
|
|
matched = true
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!matched) {
|
||
|
|
throw new Error(`Cannot locate option with value: ${value}`)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async setSelected(option) {
|
||
|
|
if (!(await option.isSelected())) {
|
||
|
|
if (!(await option.isEnabled())) {
|
||
|
|
throw new error.UnsupportedOperationError(
|
||
|
|
`You may not select a disabled option`
|
||
|
|
)
|
||
|
|
}
|
||
|
|
await option.click()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = { Select }
|