nightwatch
Version:
Easy to use Node.js based End-to-End testing solution for browser based apps and websites, using the W3C WebDriver API.
177 lines (141 loc) • 4.22 kB
JavaScript
const EventEmitter = require('events');
const Utils = require('../utils');
class TreeNode {
get command() {
return this.__command;
}
get context() {
return this.__context;
}
get args() {
return this.__args;
}
get instance() {
return this.__instance;
}
get fullName() {
if (!this.namespace || !Utils.isString(this.namespace)) {
return this.name;
}
return `${this.namespace}.${this.name}`;
}
/**
*
* @param {function} commandFn
* @param {object} context
* @param {Array} args
* @param {object} options
*/
setCommand(commandFn, context, args, options = {}) {
this.__command = commandFn;
this.__context = context;
this.__args = args;
this.options = options;
}
/**
* @param {{name, namespace, stackTrace, parent, deferred}} opts
*/
constructor({name, parent, namespace, stackTrace, deferred, isES6Async}) {
this.__command = null;
this.__context = null;
this.__args = null;
this.__instance = null;
// if we have an ES6 async/await testcase, there will be a deferred object containing the promise
this.deferred = deferred;
this.hasPromise = deferred !== undefined && isES6Async;
this.name = name || '__root__';
this.namespace = namespace;
this.stackTrace = stackTrace;
this.parent = parent;
this.childNodes = [];
this.started = false;
this.done = false;
this.startTime = null;
}
resolve(result) {
if (!this.hasPromise || !this.deferred) {
return;
}
this.deferred.resolve(result);
}
reject(reason) {
if (!this.hasPromise || !this.deferred) {
return;
}
this.deferred.reject(reason);
}
run() {
return this.runCommand()
.catch(err => {
return err;
})
.then(result => {
this.done = true;
this.elapsedTime = new Date().getTime() - this.startTime;
return result;
});
}
execute() {
const commandFn = this.command;
if (!commandFn) {
return Promise.resolve();
}
const {args, stackTrace, options} = this;
commandFn.stackTrace = stackTrace;
this.__instance = commandFn.call(this.context, {args, stackTrace, options});
return (this.instance instanceof Promise) ? this.instance : this.handleCommandResult();
}
runCommand() {
this.started = true;
this.startTime = new Date().getTime();
this.result = null;
try {
this.result = this.execute().catch(err => {
err.message = `Error while running "${this.name}" command: ${err.message}`;
err.abortOnFailure = err.abortOnFailure || err.abortOnFailure === undefined;
if (err.abortOnFailure) {
throw err;
}
return err;
});
} catch (err) {
let originalError = `${err.name}: ${err.message}\n` + Utils.filterStack(err);
if (this.stackTrace) {
err.stack = this.stackTrace;
}
err.message = `Error while running "${this.name}" command: "${originalError}"`;
this.result = Promise.reject(err);
}
return this.result;
}
handleCommandResult() {
if (!this.deferred) {
this.deferred = Utils.createPromise();
}
let commandResult;
if (this.instance instanceof EventEmitter) {
commandResult = this.instance;
if (this.instance.needsPromise) {
this.needsPromise = true;
}
} else if (this.context instanceof EventEmitter) { // Chai assertions
commandResult = this.context;
// this is for when the command is not emitting an event itself, but has child nodes that may do,
// so when all the child nodes will finish, the parent will also finish
this.needsPromise = true;
}
if (!commandResult) {
throw new Error('Commands must either return an EventEmitter which emits a "complete" event or a Promise.');
}
commandResult
.once('complete', result => {
this.deferred.resolve(result);
})
.once('error', (err, abortOnFailure) => {
err.abortOnFailure = abortOnFailure || abortOnFailure === undefined;
this.deferred.reject(err);
});
return this.deferred.promise;
}
}
module.exports = TreeNode;