taiko
Version:
Taiko is a Node.js library for automating Chromium based browsers
225 lines (206 loc) • 8.3 kB
JavaScript
const {
firstElement,
desc,
prepareParameters,
elementTypeToSelectorName,
} = require("./helper");
const { descEvent } = require("../eventBus");
const { getIfExists } = require("../elementSearch");
const runtimeHandler = require("../handlers/runtimeHandler");
/**
* Wrapper object of all found elements. This list mimics the behaviour of {@link Element}
* by exposing similar methods. The call of these methods gets delegated to first element.
* By default, the `ElementWrapper` acts as a proxy to the first matching element and hence
* it forwards function calls that belong to {@link Element}
*/
class ElementWrapper {
constructor(elementType, query, attrValuePairs, _options, ...args) {
if (attrValuePairs instanceof ElementWrapper) {
const selectorName = elementTypeToSelectorName(elementType);
throw new TypeError(
`You are passing a \`ElementWrapper\` to a \`${selectorName}\` selector. Refer https://docs.taiko.dev/api/${selectorName.toLowerCase()}/ for the correct parameters`,
);
}
const { selector, options } = prepareParameters(
attrValuePairs,
_options,
...args,
);
this.selector = selector;
this._options = options;
this._description = desc(selector, query, elementType, options);
}
/**
* @deprecated Deprecated from version `1.0.3`. DOM element getter. Implicitly wait for the element to appears with timeout of 10 seconds.
* @param {number} retryInterval Retry Interval in milliseconds (defaults to global settings).
* @param {number} retryTimeout Retry Timeout in milliseconds (defaults to global settings).
* @returns {Element[]} All elements mathing the selector.
*/
async get(retryInterval, retryTimeout) {
console.warn("DEPRECATED use .elements()");
return this.elements(retryInterval, retryTimeout);
}
/**
* @property
* @description Describes the operation performed. The description is the same that is printed when performing the operation in REPL.
* @returns {string} Description of the current command that fetched this element(wrapper).
* @example
* link('google').description // prints "'Link with text google'"
*/
get description() {
return this._description;
}
/**
* @description Checks existence for element. `exists()` waits for `retryTimeout` before deciding that the page is loaded.
* (NOTE: `exists()` returns boolean from version `0.4.0`)
* @since 0.4.0
* @param {number} retryInterval Retry Interval in milliseconds (defaults to global settings).
* @param {number} retryTimeout Retry Timeout in milliseconds (defaults to global settings).
* @returns {boolean} true if exists, else false.
* @example
* // To 'short-circuit' non existence. However this should be done only if there is no network calls/reloads.
* element.exists(0,0)
* @example
* link('google').exists()
* @example
* link('google').exists(1000)
*/
async exists(retryInterval, retryTimeout) {
try {
await firstElement.apply(this, [retryInterval, retryTimeout]);
} catch (e) {
if (e.message === `${this._description} not found`) {
descEvent.emit("success", "Does not exist");
return false;
}
throw e;
}
descEvent.emit("success", "Exists");
return true;
}
/**
* @description Gets the [`innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText) of the element
* @returns {string} [`innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText) of the element
*/
async text() {
const elem = await firstElement.apply(this);
return await elem.text();
}
/**
* @description Checks if element is visually visible. `isVisible()` is false when the element is overshadowed by another element,
* or if the element is outside the viewport.
* @param {number} retryInterval Retry Interval in milliseconds (defaults to global settings).
* @param {number} retryTimeout Retry Timeout in milliseconds (defaults to global settings).
* @returns {boolean} true if visible, else false.
*/
async isVisible(retryInterval, retryTimeout) {
const elem = await firstElement.apply(this, [retryInterval, retryTimeout]);
async function isVisible() {
const visibilityRatio = await new Promise((resolve) => {
const elem = this;
const observer = new IntersectionObserver((entries) => {
resolve(entries[0].intersectionRatio);
observer.disconnect();
});
observer.observe(elem);
});
return visibilityRatio === 1;
}
const objectId = elem.get();
const { result } = await runtimeHandler.runtimeCallFunctionOn(
isVisible,
null,
{
objectId: objectId,
awaitPromise: true,
},
);
if (result.value) {
descEvent.emit("success", "Element is Visible");
return true;
}
descEvent.emit("success", "Element is not Visible");
return false;
}
/**
* @description Checks if element is disabled
* @param {number} retryInterval Retry Interval in milliseconds (defaults to global settings).
* @param {number} retryTimeout Retry Timeout in milliseconds (defaults to global settings).
* @returns {boolean} true if disabled, else false.
*/
async isDisabled(retryInterval, retryTimeout) {
const elem = await firstElement.apply(this, [retryInterval, retryTimeout]);
return await elem.isDisabled();
}
/**
* @description Checks if element is [draggable](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable).
* @param {number} retryInterval Retry Interval in milliseconds (defaults to global settings).
* @param {number} retryTimeout Retry Timeout in milliseconds (defaults to global settings).
* @returns {boolean} true if disabled, else false.
*/
async isDraggable(retryInterval, retryTimeout) {
const elem = await firstElement.apply(this, [retryInterval, retryTimeout]);
return await elem.isDraggable();
}
/**
* @description Read attribute value of the element found.
* @param {string} name
* @returns {string} value of attribute
* @example
* link('google').attribute('alt')
*/
async attribute(name) {
const elem = await firstElement.apply(this);
return await elem.getAttribute(name);
}
/**
* @description DOM element getter. Implicitly wait for the element to appears with timeout of 10 seconds.
* @param {number} retryInterval Retry Interval in milliseconds (defaults to global settings).
* @param {number} retryTimeout Retry Timeout in milliseconds (defaults to global settings).
* @returns {Element[]} Array of all elements matching the selector.
* @example
* // To loop over all the elements
* let elements = await $('a').elements();
* for (element of elements) {
* console.log(await element.text());
* }
* @example
* textBox('username').value()
* (await textBox('username').elements())[0].value() # same as above
* @example
* $('.class').text()
* (await $('.class').elements())[0].text() # same as above
* @example
* let element = await $('a').element(0);
* console.log(await element.text());
*/
async elements(retryInterval, retryTimeout) {
return await getIfExists(this._get, this._description)(
null,
retryInterval,
retryTimeout,
);
}
/**
* @description DOM element getter. Implicitly wait for the element to appears with timeout of 10 seconds.
* @alias elements()[0]
* @param {number} index Zero-based index of element to return
* @param {number} retryInterval Retry Interval in milliseconds (defaults to global settings).
* @param {number} retryTimeout Retry Timeout in milliseconds (defaults to global settings).
* @returns {Element} First element that matches the selector.
*/
async element(index, retryInterval, retryTimeout) {
const results = await getIfExists(this._get, this._description)(
null,
retryInterval,
retryTimeout,
);
if (index > results.length - 1) {
throw new Error(
`Element index is out of range. There are only ${results.length} element(s)`,
);
}
return results[index];
}
}
module.exports = ElementWrapper;