const FindHelpersMixin = require('./FindHelpersMixin')
const utils = require('./utils')
const KEY = require('./KEY')
/**
* The base class for Elements
*
* Elements are never instanced directly: they are returned by
* {@link Driver#findElement} and {@link Driver#findElements}.
*
* Element objects are then used to either get specific information about them
* using for example `element.getText()`, or to perform actions on them such as
* `element.click()` or `element.findElements` (which will return more elements)
*
* @mixes FindHelpersMixin
* @augments FindHelpersMixin
* @inheritdoc
*/
var Element = class {
/**
* Constructor. You never have to run this yourself, since both {@link Driver#findElement}
* and {@link Driver#findElements} will run `new Element()` for you.
*
* @param {Driver} driver The driver that originally created this element
* @param {object} elObject An element object as it was returned by the webdriver.
*
*/
constructor (driver, elObject) {
var value
// Assssign the driver
this.driver = driver
// elObject will contain `value` if it's a straight answer from the webdriver
// Otherwise, it's assumed to be passed `res.value`
if (utils.isObject(elObject) && elObject.value) value = elObject.value
else value = elObject
// Sets the ID. Having `element-XXX` and `ELEMENT` as keys to the object means
// that it can be used by switchToFrame()
var idx = 'element-6066-11e4-a52e-4f735466cecf'
this.id = this.ELEMENT = this[idx] = value[idx]
// No ID could be find
if (!this.id) throw new Error('Could not get element ID from element object')
}
/** Alias to {@link Driver#waitFor Driver's waitFor function}
* @async
*/
waitFor (timeout = 0, pollInterval = 0) {
timeout = timeout || this.driver._defaultPollTimeout
pollInterval = pollInterval || this.driver._pollInterval
return this.driver.waitFor.call(this, timeout, pollInterval)
}
/** Constant returning special KEY characters (enter, etc.)
* Constant are from the global variable {@link KEY}
* @static
*
* @example
* var el = await driver.findElementCss('#input')
* await e.sendKeys("This is a search" + Element.Key.ENTER)
*/
static get Key () { return KEY }
/**
* Check that the element is selected
*
* @return {Promise<boolean>} true of false
* @example
* var el = await driver.findElementCss('#main')
* var isSelected = await el.isSelected()
*
*/
async isSelected () {
return !!(await this._execute('get', `/element/${this.id}/selected`))
}
/**
* Get attribute called `name` from element
* @async
* @param {string} name The name of the attribute to be fetched
*
* @return {Promise<string>} The attribute's value
* @example
* var el = await driver.findElementCss('a' })
* var href = el.getAttribute('href')
*
*/
getAttribute (name) {
return this._execute('get', `/element/${this.id}/attribute/${name}`)
}
/**
* Get property called `name` from element
* @async
*
* @param {string} name The name of the property to be fetched
*
* @return {Promise<string>} The property's value
* @example
* var el = await driver.findElementCss('a' })
* var href = el.getProperty('href')
*
*/
getProperty (name) {
return this._execute('get', `/element/${this.id}/property/${name}`)
}
/**
* Get css value from element
* @async
*
* @param {string} name The name of the CSS value to be fetched
*
* @return {Promise<string>} The CSS's value
* @example
* var el = await driver.findElementCss('a' })
* var height = el.getCssValue('height')
*
*/
getCssValue (name) {
return this._execute('get', `/element/${this.id}/css/${name}`)
}
/**
* Get text value from element
* @async
*
* @return {Promise<string>} The text
* @example
* var el = await driver.findElementCss('a' })
* var text = el.getText()
*/
getText () {
return this._execute('get', `/element/${this.id}/text`)
}
/**
* Get tag name from element
* @async
*
* @return {Promise<string>} The tag's name
* @example
* var el = await driver.findElementCss('.link' })
* var tagName = el.getTagName()
*/
getTagName () {
return this._execute('get', `/element/${this.id}/name`)
}
/**
* Get rect from element
* @async
*
* @return {Promise<string>} The rect info
* @example
* var el = await driver.findElementCss('a' })
* var rect = el.getRect()
*/
getRect () {
return this._execute('get', `/element/${this.id}/rect`)
}
/**
* Check that the element is enabled
*
* @return {Promise<boolean>} true of false
* @example
* var el = await driver.findElementCss('#main')
var isSelected = await el.isSelected()
*
*/
async isEnabled () {
return !!(await this._execute('get', `/element/${this.id}/enabled`))
}
/**
* Click on an element
*
* @return {Promise<Element>} The element itself
* @example
* var el = await driver.findElementCss('#button')
* await el.click()
*
*/
async click () {
await this._execute('post', `/element/${this.id}/click`)
return this
}
/**
* Clear an element
*
* @return {Promise<Element>} The element itself
* @example
* var el = await driver.findElementCss('#input')
* await el.clear()
*
*/
async clear () {
await this._execute('post', `/element/${this.id}/clear`)
return this
}
/**
* Send keys to an element
*
* @return {Promise<Element>} The element itself. Concatenate with `Element.Key` to send
* special characters.
* @example
* var el = await driver.findElementCss('#input')
* await e.sendKeys("This is a search" + Element.Key.ENTER)
*
*/
async sendKeys (text) {
// W3c: Adding 'value' to parameters, so that Chrome works too
var value = text.split('')
await this._execute('post', `/element/${this.id}/value`, { text, value })
return this
}
/**
* Take screenshot of the element
* @param {boolean} scroll If true (by default), it will scroll to the element
* @return {Promise<Buffer>} The screenshot data in a Buffer object
* @example
* var el = await driver.findElementCss('#input')
* var screenshot = await el.takeScreenshot()
*
*/
async takeScreenshot (scroll = true) {
var data = await this._execute('get', `/element/${this.id}/screenshot`, { scroll })
return Buffer.from(data, 'base64')
}
/**
* @private
*/
async _execute (method, command, params) {
return this.driver._execute(method, command, params)
}
/**
* Find an element within this element
*
* Note that you are encouraged to use one of the helper functions:
* `findElementCss()`, `findElementLinkText()`, `findElementPartialLinkText()`,
* `findElementTagName()`, `findElementXpath()`
*
* @param {string} using It can be `Driver.Using.CSS`, `Driver.Using.LINK_TEXT`,
* `Driver.Using.PARTIAL_LINK_TEXT`, `Driver.Using.TAG_NAME`,
* `Driver.Using.XPATH`
* @param {string} value The parameter to the `using` method
*
* @return {Promise<Element>} An object representing the element.
*
* @example
* // Find the element using the driver's `findElement()` call
* var ul = await driver.findElement({ Driver.Using.CSS, value: 'ul' )
* // Find the first LI element within the found UL
* var items = await ul.findElement({ Driver.Using.CSS, value: 'li')
*
*/
async findElement (using, value) {
var el = await this._execute('post', `/element/${this.id}/element`, {using, value})
return new Element(this.driver, el)
}
/**
* Find several elements within this element
*
* Note that you are encouraged to use one of the helper functions:
* `findElementCss()`, `findElementLinkText()`, `findElementPartialLinkText()`,
* `findElementTagName()`, `findElementXpath()`
*
* @param {string} using It can be `Driver.Using.CSS`, `Driver.Using.LINK_TEXT`,
* `Driver.Using.PARTIAL_LINK_TEXT`, `Driver.Using.TAG_NAME`,
* `Driver.Using.XPATH`
* @param {string} value The parameter to the `using` method
*
* @return {Promise<Array<Element>>} An array of elements
*
* @example
* // Find the element using the driver's `findElement()` call
* var ul = await driver.findElement({ Driver.Using.CSS, value: 'ul' )
* // Find ALL LI sub-elements within the found UL element
* var items = await ul.findElements({ Driver.Using.CSS, value: 'li')
*
*/
async findElements (using, value) {
var els = await this._execute('post', `/element/${this.id}/elements`, {using, value})
if (!Array.isArray(els)) throw new Error('Result from findElements must be an array')
return els.map((v) => new Element(this.driver, v))
}
}
exports = module.exports = Element = FindHelpersMixin(Element)