nightwatch
Version:
Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.
378 lines (308 loc) • 9.85 kB
JavaScript
const {until, WebElement} = require('selenium-webdriver');
const Utils = require('../../utils');
const {Logger, isUndefined, isObject, isString, isFunction} = Utils;
const isDefined = function(val) {
return !isUndefined(val);
};
const Element = require('../../element');
const {Locator} = Element;
class ElementGlobal {
static get availableElementCommands() {
return [
'getId',
['findElement', 'element', 'find', 'get'],
['findElements', 'findAll'],
'click',
'sendKeys',
['getTagName', 'tagName'],
['getCssValue', 'css'],
['getAttribute', 'attr', 'attribute'],
['getProperty', 'property', 'prop'],
['getText', 'text'],
['getAriaRole', 'arialRole'],
['getAccessibleName', 'accessibleName'],
['getRect', 'rect'],
'isEnabled',
'isSelected',
'submit',
'clear',
'isDisplayed',
['takeScreenshot', 'screenshot']
];
}
static element({locator, testSuite, client, options}) {
const instance = new ElementGlobal({testSuite, client, options});
instance.setLocator(locator);
return instance.exported();
}
get api() {
return this.nightwatchInstance.api;
}
get reporter() {
return this.nightwatchInstance.reporter;
}
get commandQueue() {
return this.nightwatchInstance.queue;
}
get transport() {
return this.nightwatchInstance.transport;
}
get settings() {
return this.client ? this.client.settings : this.testSuite.settings;
}
constructor({testSuite, client, options = {}}) {
this.testSuite = testSuite;
this.client = client;
this.isComponent = options.isComponent || false;
this.componentType = this.isComponent && options.type;
this.suppressNotFoundErrors = false;
this.abortOnFailure = this.settings.globals.abortOnElementLocateError;
this.timeout = this.settings.globals.waitForConditionTimeout;
this.retryInterval = this.settings.globals.waitForConditionPollInterval;
this.init();
}
init() {
this.nightwatchInstance = this.testSuite && this.testSuite.client ? this.testSuite.client : this.client;
}
async findElement() {
if (this.element) {
return;
}
let {locator} = this;
if ((locator instanceof Element) && locator.resolvedElement) {
this.element = this.createWebElement(locator.resolvedElement);
return;
}
this.element = await this.transport.driver.wait(until.elementLocated(locator), this.timeout, null, this.retryInterval);
}
static isElementObject(element) {
if (!isObject(element)) {
return false;
}
if (!isString(element.selector)) {
return false;
}
const {
abortOnFailure, retryInterval, timeout, suppressNotFoundErrors, index
} = element;
return isDefined(abortOnFailure) || isDefined(retryInterval) || isDefined(timeout) || isDefined(suppressNotFoundErrors) || isDefined(index);
}
setPropertiesFromElement(element) {
const {
abortOnFailure, retryInterval, timeout, suppressNotFoundErrors, index
} = element;
if (isDefined(index)) {
this.index = index;
}
if (isDefined(abortOnFailure)) {
this.abortOnFailure = abortOnFailure;
}
if (isDefined(suppressNotFoundErrors)) {
this.suppressNotFoundErrors = suppressNotFoundErrors;
}
if (isDefined(retryInterval)) {
this.retryInterval = retryInterval;
}
if (isDefined(timeout)) {
this.timeout = timeout;
}
}
createWebElement(id) {
return new WebElement(this.transport.driver, id);
}
setLocator(locator) {
if (WebElement.isId(locator)) {
this.element = this.createWebElement(WebElement.extractId(locator));
return;
}
if (locator instanceof WebElement) {
this.element = locator;
return;
}
if (ElementGlobal.isElementObject(locator)) {
locator = Element.createFromSelector(locator);
}
if (locator instanceof Element) {
this.locator = locator;
} else {
this.locator = Locator.create(locator);
}
}
exported() {
const exportedElement = Element.createFromSelector(this.locator || this.element);
this.loadCommandsOntoObject(exportedElement);
return exportedElement;
}
computeArguments(args, commandName) {
if (args.length === 0) {
return args;
}
if (['findElements', 'findElement'].includes(commandName)) {
if (isString(args[0])) {
args[0] = {
value: args[0],
using: 'css selector'
};
} else if (isObject(args[0]) && args[0].selector) {
if (args[0] instanceof Element || ElementGlobal.isElementObject(args[0])) {
this.setPropertiesFromElement(args[0]);
}
args[0] = Locator.create(args[0]);
}
}
return args;
}
getComponentProperty(propName) {
return this.client.transportActions.executeScript(function(property) {
// eslint-disable-next-line
if (!window['@component_element']) {
throw new Error('Component was not rendered.');
}
// eslint-disable-next-line
return window['@component_element'].componentVM[property];
}, [propName]).then(result => {
if (result.error instanceof Error) {
throw result.error;
}
return result.value;
});
}
createCommand(commandName, commandToExecute, nightwatchName) {
return function executeFn(...args) {
const deferred = Utils.createPromise();
const stackTrace = Utils.getOriginalStackTrace(executeFn);
this.init();
const {api, commandQueue, nightwatchInstance} = this;
const commandFn = async () => {
const isElement = await this.setElement(stackTrace);
if (!isElement) {
return null;
}
if (commandName === 'findElement' && args.length === 0) {
return this.element;
}
args = this.computeArguments(args, commandName);
let value;
let error;
try {
if (['getProperty', 'property', 'prop'].includes(nightwatchName) && this.isComponent && this.componentType) {
value = await this.getComponentProperty(args[0]);
} else {
value = await this.element[commandName].apply(this.element, args);
}
} catch (err) {
error = err;
}
if (['find', 'get', 'element'].includes(nightwatchName) && (value instanceof WebElement)) {
value = ElementGlobal.element({locator: value, client: nightwatchInstance});
} else if (nightwatchName === 'findAll') {
value = value.map(element => {
if (element instanceof WebElement) {
return ElementGlobal.element({locator: element, client: nightwatchInstance});
}
return element;
});
}
const lastArg = args[args.length-1];
if (isFunction(lastArg)) {
if (error) {
return lastArg.call(this.api, {
value,
error,
status: 0
});
}
const callbackResult = lastArg.call(this.api, {
value,
status: 0
});
if (isDefined(callbackResult)) {
return callbackResult;
}
}
if (['find', 'get', 'element'].includes(nightwatchName) && error) {
return null;
}
if (error) {
throw error;
}
return value;
};
const isES6Async = true;
const node = commandQueue.add({
commandName: `element().${nightwatchName}`,
commandFn: commandToExecute ? commandToExecute({stackTrace}) : commandFn,
context: api,
args,
stackTrace,
namespace: null,
alwaysResolvePromise: true,
rejectPromise: true,
deferred,
isES6Async
});
Object.assign(node.deferred.promise, api);
if (commandName === 'findElement') {
node.deferred.promise['@nightwatch_element'] = true;
node.deferred.promise['@nightwatch_args'] = args;
if (this.isComponent) {
node.deferred.promise['@nightwatch_component'] = true;
}
} else if (commandName === 'findElements') {
node.deferred.promise['@nightwatch_multiple_elements'] = true;
}
return node.deferred.promise;
}.bind(this);
}
async setElement(stackTrace) {
try {
await this.findElement();
return true;
} catch (err) {
if (this.suppressNotFoundErrors) {
return null;
}
err.stack = stackTrace;
Logger.error(err);
if (this.abortOnFailure) {
this.reporter.registerTestError(err);
}
return null;
}
}
loadCommandsOntoObject(exportedElement) {
ElementGlobal.availableElementCommands.forEach(commandName => {
let names = commandName;
if (!Array.isArray(names)) {
names = [names];
}
const seleniumName = names[0];
names.forEach(commandName => {
Object.defineProperty(exportedElement, commandName, {
value: this.createCommand.call(this, seleniumName, null, commandName),
writable: false
});
});
});
Object.defineProperty(exportedElement, 'getWebElement', {
value: this.createCommand.call(this, 'getWebElement', ({stackTrace}) => {
return async () => {
const isElement = await this.setElement(stackTrace);
if (!isElement) {
return null;
}
return this.element;
};
}),
writable: false
});
if (this.isComponent) {
Object.defineProperty(exportedElement, 'isComponent', {
value: this.isComponent,
writable: false
});
}
return this;
}
}
module.exports = ElementGlobal;