forked from enviPath/enviPy
794 lines
26 KiB
JavaScript
794 lines
26 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
|
|
var _getIterator2 = require('babel-runtime/core-js/get-iterator');
|
|
|
|
var _getIterator3 = _interopRequireDefault(_getIterator2);
|
|
|
|
var _keys = require('babel-runtime/core-js/object/keys');
|
|
|
|
var _keys2 = _interopRequireDefault(_keys);
|
|
|
|
var _assign = require('babel-runtime/core-js/object/assign');
|
|
|
|
var _assign2 = _interopRequireDefault(_assign);
|
|
|
|
var _promise = require('babel-runtime/core-js/promise');
|
|
|
|
var _promise2 = _interopRequireDefault(_promise);
|
|
|
|
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
|
|
|
|
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
|
|
|
|
var _create = require('babel-runtime/core-js/object/create');
|
|
|
|
var _create2 = _interopRequireDefault(_create);
|
|
|
|
var _q = require('q');
|
|
|
|
var _q2 = _interopRequireDefault(_q);
|
|
|
|
var _fs = require('fs');
|
|
|
|
var _fs2 = _interopRequireDefault(_fs);
|
|
|
|
var _path = require('path');
|
|
|
|
var _path2 = _interopRequireDefault(_path);
|
|
|
|
var _deepmerge = require('deepmerge');
|
|
|
|
var _deepmerge2 = _interopRequireDefault(_deepmerge);
|
|
|
|
var _mkdirp = require('mkdirp');
|
|
|
|
var _mkdirp2 = _interopRequireDefault(_mkdirp);
|
|
|
|
var _events = require('events');
|
|
|
|
var _events2 = _interopRequireDefault(_events);
|
|
|
|
var _safeBuffer = require('safe-buffer');
|
|
|
|
var _RequestHandler = require('./utils/RequestHandler');
|
|
|
|
var _RequestHandler2 = _interopRequireDefault(_RequestHandler);
|
|
|
|
var _ErrorHandler = require('./utils/ErrorHandler');
|
|
|
|
var _Logger = require('./utils/Logger');
|
|
|
|
var _Logger2 = _interopRequireDefault(_Logger);
|
|
|
|
var _safeExecute = require('./helpers/safeExecute');
|
|
|
|
var _safeExecute2 = _interopRequireDefault(_safeExecute);
|
|
|
|
var _sanitize = require('./helpers/sanitize');
|
|
|
|
var _sanitize2 = _interopRequireDefault(_sanitize);
|
|
|
|
var _mobileDetector2 = require('./helpers/mobileDetector');
|
|
|
|
var _mobileDetector3 = _interopRequireDefault(_mobileDetector2);
|
|
|
|
var _detectSeleniumBackend = require('./helpers/detectSeleniumBackend');
|
|
|
|
var _detectSeleniumBackend2 = _interopRequireDefault(_detectSeleniumBackend);
|
|
|
|
var _errorHandler = require('./helpers/errorHandler');
|
|
|
|
var _errorHandler2 = _interopRequireDefault(_errorHandler);
|
|
|
|
var _hasElementResultHelper = require('./helpers/hasElementResultHelper');
|
|
|
|
var _hasElementResultHelper2 = _interopRequireDefault(_hasElementResultHelper);
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
var INTERNAL_EVENTS = ['init', 'command', 'error', 'result', 'end', 'screenshot'];
|
|
var PROMISE_FUNCTIONS = ['then', 'catch', 'finally'];
|
|
|
|
var EventEmitter = _events2.default.EventEmitter;
|
|
|
|
/**
|
|
* WebdriverIO v4
|
|
*/
|
|
var WebdriverIO = function WebdriverIO(args, modifier) {
|
|
var prototype = (0, _create2.default)(Object.prototype);
|
|
var rawCommands = {};
|
|
var eventHandler = new EventEmitter();
|
|
var fulFilledPromise = (0, _q2.default)();
|
|
var stacktrace = [];
|
|
var commandList = [];
|
|
|
|
var EVENTHANDLER_FUNCTIONS = (0, _getPrototypeOf2.default)(eventHandler);
|
|
|
|
/**
|
|
* merge default options with given user options
|
|
*/
|
|
var options = (0, _deepmerge2.default)({
|
|
protocol: 'http',
|
|
waitforTimeout: 1000,
|
|
waitforInterval: 500,
|
|
coloredLogs: true,
|
|
logLevel: 'silent',
|
|
baseUrl: null,
|
|
onError: [],
|
|
connectionRetryTimeout: 90000,
|
|
connectionRetryCount: 3
|
|
}, typeof args !== 'string' ? args : {});
|
|
|
|
/**
|
|
* define Selenium backend given on user options
|
|
*/
|
|
options = (0, _deepmerge2.default)((0, _detectSeleniumBackend2.default)(args), options);
|
|
|
|
/**
|
|
* only set globals we wouldn't get otherwise
|
|
*/
|
|
if (!process.env.WEBDRIVERIO_COLORED_LOGS) {
|
|
process.env.WEBDRIVERIO_COLORED_LOGS = options.coloredLogs;
|
|
}
|
|
|
|
var logger = new _Logger2.default(options, eventHandler);
|
|
var requestHandler = new _RequestHandler2.default(options, eventHandler, logger);
|
|
|
|
/**
|
|
* assign instance to existing session
|
|
*/
|
|
if (typeof args === 'string') {
|
|
requestHandler.sessionID = args;
|
|
}
|
|
|
|
/**
|
|
* sanitize error handler
|
|
*/
|
|
if (!Array.isArray(options.onError)) {
|
|
options.onError = [options.onError];
|
|
}
|
|
options.onError = options.onError.filter(function (fn) {
|
|
return typeof fn === 'function';
|
|
});
|
|
|
|
var desiredCapabilities = (0, _deepmerge2.default)({
|
|
javascriptEnabled: true,
|
|
locationContextEnabled: true,
|
|
handlesAlerts: true,
|
|
rotatable: true
|
|
}, options.desiredCapabilities || {});
|
|
|
|
var _mobileDetector = (0, _mobileDetector3.default)(desiredCapabilities),
|
|
isMobile = _mobileDetector.isMobile,
|
|
isIOS = _mobileDetector.isIOS,
|
|
isAndroid = _mobileDetector.isAndroid;
|
|
|
|
/**
|
|
* if no caps are specified fall back to firefox
|
|
*/
|
|
|
|
|
|
if (!desiredCapabilities.browserName && !isMobile) {
|
|
desiredCapabilities.browserName = 'firefox';
|
|
}
|
|
|
|
if (!isMobile && typeof desiredCapabilities.loggingPrefs === 'undefined') {
|
|
desiredCapabilities.loggingPrefs = {
|
|
browser: 'ALL',
|
|
driver: 'ALL'
|
|
};
|
|
}
|
|
|
|
var resolve = function resolve(result, onFulfilled, onRejected, context) {
|
|
if (typeof result === 'function') {
|
|
this.isExecuted = true;
|
|
result = result.call(this);
|
|
}
|
|
|
|
/**
|
|
* run error handler if command fails
|
|
*/
|
|
if (result instanceof Error) {
|
|
var _result = result;
|
|
|
|
this.defer.resolve(_promise2.default.all(_errorHandler2.default.map(function (fn) {
|
|
return fn.call(context, result);
|
|
})).then(function (res) {
|
|
var handlerResponses = res.filter(function (r) {
|
|
return typeof r !== 'undefined';
|
|
});
|
|
|
|
/**
|
|
* if no handler was triggered trough actual error
|
|
*/
|
|
if (handlerResponses.length === 0) {
|
|
return callErrorHandlerAndReject.call(context, _result, onRejected);
|
|
}
|
|
|
|
return onFulfilled.call(context, handlerResponses[0]);
|
|
}, function (e) {
|
|
return callErrorHandlerAndReject.call(context, e, onRejected);
|
|
}));
|
|
} else {
|
|
this.defer.resolve(result);
|
|
}
|
|
|
|
return this.promise;
|
|
};
|
|
|
|
/**
|
|
* middleware to call on error handler in wdio mode
|
|
*/
|
|
var callErrorHandlerAndReject = function callErrorHandlerAndReject(err, onRejected) {
|
|
var _this = this;
|
|
|
|
/**
|
|
* only call error handler if there is any and if error has bubbled up
|
|
*/
|
|
if (!this || this.depth !== 0 || options.onError.length === 0) {
|
|
return reject.call(this, err, onRejected);
|
|
}
|
|
|
|
return new _promise2.default(function (resolve, reject) {
|
|
return _promise2.default.all(options.onError.map(function (fn) {
|
|
if (!global.wdioSync) {
|
|
return fn.call(_this, err);
|
|
}
|
|
|
|
return new _promise2.default(function (resolve) {
|
|
return global.wdioSync(fn, resolve).call(_this, err);
|
|
});
|
|
})).then(resolve, reject);
|
|
}).then(function () {
|
|
return reject.call(_this, err, onRejected);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* By using finally in our next method we omit the duty to throw an exception at some
|
|
* point. To avoid propagating rejected promises until everything crashes silently we
|
|
* check if the last and current promise got rejected. If so we can throw the error.
|
|
*/
|
|
var reject = function reject(err, onRejected) {
|
|
if (!options.isWDIO && !options.screenshotOnReject && typeof onRejected === 'function') {
|
|
delete err.screenshot;
|
|
return onRejected(err);
|
|
}
|
|
|
|
var onRejectedSafe = function onRejectedSafe(err) {
|
|
if (typeof onRejected === 'function') {
|
|
onRejected(err);
|
|
}
|
|
};
|
|
|
|
if (!this && !options.screenshotOnReject) {
|
|
onRejectedSafe(err);
|
|
throw err;
|
|
}
|
|
|
|
if (this && this.depth !== 0) {
|
|
onRejectedSafe(err);
|
|
return this.promise;
|
|
}
|
|
|
|
var shouldTakeScreenshot = options.screenshotOnReject || typeof options.screenshotPath === 'string';
|
|
if (!shouldTakeScreenshot || err.shotTaken || insideCommand('screenshot', this)) {
|
|
return fail(err, onRejected);
|
|
}
|
|
|
|
err.shotTaken = true;
|
|
return takeScreenshot(err).catch(function (e) {
|
|
return logger.log('\tFailed to take screenshot on reject:', e);
|
|
}).then(function () {
|
|
return fail(err, onRejected);
|
|
});
|
|
};
|
|
|
|
function insideCommand(command, unit) {
|
|
var commands = unit && unit.commandList;
|
|
return commands && commands[commands.length - 1].name === command;
|
|
}
|
|
|
|
function takeScreenshot(err) {
|
|
var client = unit();
|
|
var failDate = new Date();
|
|
|
|
if (typeof options.screenshotOnReject === 'object') {
|
|
client.requestHandler = createRequestHandlerDecorator(options.screenshotOnReject);
|
|
}
|
|
|
|
// don't take a new screenshot if we already got one from Selenium
|
|
var getScreenshot = typeof err.screenshot === 'string' ? function () {
|
|
return err.screenshot;
|
|
} : function () {
|
|
return rawCommands.screenshot.call(client).then(function (res) {
|
|
return res.value;
|
|
});
|
|
};
|
|
|
|
return _q2.default.fcall(getScreenshot).then(function (screenshot) {
|
|
if (options.screenshotOnReject) {
|
|
err.screenshot = screenshot;
|
|
}
|
|
|
|
if (typeof options.screenshotPath === 'string') {
|
|
var filename = saveScreenshotSync(screenshot, failDate);
|
|
client.emit('screenshot', { data: screenshot, filename: filename });
|
|
}
|
|
});
|
|
}
|
|
|
|
function createRequestHandlerDecorator(opts) {
|
|
return (0, _create2.default)(requestHandler, {
|
|
defaultOptions: {
|
|
value: (0, _assign2.default)({}, requestHandler.defaultOptions, opts)
|
|
}
|
|
});
|
|
}
|
|
|
|
function saveScreenshotSync(screenshot, date) {
|
|
var screenshotPath = _path2.default.isAbsolute(options.screenshotPath) ? options.screenshotPath : _path2.default.join(process.cwd(), options.screenshotPath);
|
|
|
|
/**
|
|
* create directory if not existing
|
|
*/
|
|
try {
|
|
_fs2.default.statSync(screenshotPath);
|
|
} catch (e) {
|
|
_mkdirp2.default.sync(screenshotPath);
|
|
}
|
|
|
|
var capId = _sanitize2.default.caps(desiredCapabilities);
|
|
var timestamp = date.toJSON().replace(/:/g, '-');
|
|
var filename = 'ERROR_' + capId + '_' + timestamp + '.png';
|
|
var filePath = _path2.default.join(screenshotPath, filename);
|
|
|
|
_fs2.default.writeFileSync(filePath, new _safeBuffer.Buffer(screenshot, 'base64'));
|
|
logger.log('\tSaved screenshot: ' + filename);
|
|
|
|
return filename;
|
|
}
|
|
|
|
function fail(e, onRejected) {
|
|
if (!e.stack) {
|
|
e = new Error(e);
|
|
}
|
|
|
|
var message = e.seleniumStack && e.seleniumStack.message || e.message;
|
|
|
|
var stack = stacktrace.slice().map(function (trace) {
|
|
return ' at ' + trace;
|
|
});
|
|
e.stack = e.name + ': ' + message + '\n' + stack.reverse().join('\n');
|
|
|
|
/**
|
|
* the waitUntil command can execute a lot of functions until it resolves
|
|
* to keep the stacktrace sane we just shrink down the stacktrack and
|
|
* only keep waitUntil in it
|
|
*/
|
|
if (e.name === 'WaitUntilTimeoutError') {
|
|
stack = e.stack.split('\n');
|
|
stack.splice(1, stack.length - 2);
|
|
e.stack = stack.join('\n');
|
|
}
|
|
|
|
/**
|
|
* ToDo useful feature for standalone mode:
|
|
* option that if true causes script to throw exception if command fails:
|
|
*
|
|
* process.nextTick(() => {
|
|
* throw e
|
|
* })
|
|
*/
|
|
|
|
if (typeof onRejected !== 'function') {
|
|
throw e;
|
|
}
|
|
|
|
return onRejected(e);
|
|
}
|
|
|
|
/**
|
|
* WebdriverIO Monad
|
|
*/
|
|
function unit(lastPromise) {
|
|
var client = (0, _create2.default)(prototype);
|
|
var defer = _q2.default.defer();
|
|
var promise = defer.promise;
|
|
|
|
client.defer = defer;
|
|
client.promise = promise;
|
|
client.lastPromise = lastPromise || fulFilledPromise;
|
|
|
|
client.desiredCapabilities = desiredCapabilities;
|
|
client.requestHandler = requestHandler;
|
|
client.logger = logger;
|
|
client.options = options;
|
|
client.commandList = commandList;
|
|
|
|
client.isMobile = isMobile;
|
|
client.isIOS = isIOS;
|
|
client.isAndroid = isAndroid;
|
|
|
|
/**
|
|
* actual bind function
|
|
*/
|
|
client.next = function (func, args, name) {
|
|
var _this2 = this;
|
|
|
|
/**
|
|
* use finally to propagate rejected promises up the chain
|
|
*/
|
|
return this.lastPromise.then(function (val) {
|
|
/**
|
|
* store command into command list so `getHistory` can return it
|
|
*/
|
|
var timestamp = Date.now();
|
|
commandList.push({ name: name, args: args, timestamp: timestamp });
|
|
|
|
/**
|
|
* allow user to leave out selector argument if they have already queried an element before
|
|
*/
|
|
var lastResult = val || _this2.lastResult;
|
|
if ((0, _hasElementResultHelper2.default)(lastResult) && args.length < func.length && func.toString().indexOf('function ' + name + '(selector') === 0) {
|
|
var isWaitFor = name.match(/^waitFor/);
|
|
var isWaitForWithSelector = isWaitFor && typeof args[0] === 'string';
|
|
|
|
/**
|
|
* Throw error if someone tries to call an action on a chained element call,
|
|
* otherwise the context would be ignored and a different element could
|
|
* selected.
|
|
* Ignore this behavior for all waitFor calls as we want to refetch elements
|
|
* in case they haven't been found. Also not throw an error for isExisting
|
|
* and isVisible since they use that information to return their result.
|
|
*/
|
|
if (!isWaitFor && !name.match(/^is(Existing|Visible)/) && lastResult.state === 'failure') {
|
|
var message = lastResult.message;
|
|
|
|
/**
|
|
* add selector parameter if no existing
|
|
*/
|
|
if (message.match(/using the given search parameters\.$/)) {
|
|
message = message.slice(0, -1) + ' ("' + lastResult.selector + '").';
|
|
}
|
|
|
|
var error = new Error(message);
|
|
return resolve.call(_this2, error, null, null, { depth: 0 });
|
|
}
|
|
|
|
/**
|
|
* propagate last element result if last result has a selector and we've
|
|
* called a waitFor command without selector
|
|
*
|
|
* e.g. when calling `waitForExist` via $('#notExisting').waitForExist()
|
|
*/
|
|
if (lastResult.selector && isWaitFor && !isWaitForWithSelector) {
|
|
_this2.lastResult = null;
|
|
args.unshift(lastResult.selector);
|
|
|
|
/**
|
|
* All non waitFor commands get null as selector which will be replaced by
|
|
* the actual web element id in the element(s) protocol commands
|
|
*
|
|
* e.g. when calling `getText` via $('#existing').getText()
|
|
*/
|
|
} else if (!isWaitForWithSelector) {
|
|
args.unshift(null);
|
|
}
|
|
}
|
|
|
|
return resolve.call(_this2, (0, _safeExecute2.default)(func, args));
|
|
}, function (e) {
|
|
/**
|
|
* this will get reached only in standalone mode if the command
|
|
* fails and doesn't get followed by a then or catch method
|
|
*/
|
|
return resolve.call(_this2, e, null, null, { depth: 0 });
|
|
});
|
|
};
|
|
|
|
client.finally = function (fn) {
|
|
var _this3 = this;
|
|
|
|
var client = unit(this.promise.finally(function () {
|
|
return resolve.call(client, (0, _safeExecute2.default)(fn, []).bind(_this3));
|
|
}));
|
|
return client;
|
|
};
|
|
|
|
client.call = function (fn) {
|
|
var _this4 = this;
|
|
|
|
var client = unit(this.promise.done(function () {
|
|
return resolve.call(client, (0, _safeExecute2.default)(fn, []).bind(_this4));
|
|
}));
|
|
return client;
|
|
};
|
|
|
|
client.then = function (onFulfilled, onRejected) {
|
|
var _this5 = this;
|
|
|
|
if (typeof onFulfilled !== 'function' && typeof onRejected !== 'function') {
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* execute then function in context of the new instance
|
|
* but resolve result with this
|
|
*/
|
|
var client = unit(this.promise.then(function () {
|
|
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
|
|
/**
|
|
* store result in commandList
|
|
*/
|
|
if (commandList.length) {
|
|
commandList[commandList.length - 1].result = args[0];
|
|
}
|
|
|
|
/**
|
|
* resolve command
|
|
*/
|
|
return resolve.call(client, (0, _safeExecute2.default)(onFulfilled, args).bind(_this5));
|
|
}, function (e) {
|
|
var result = (0, _safeExecute2.default)(onRejected, [e]).bind(_this5);
|
|
|
|
/**
|
|
* handle error once command was bubbled up the command chain
|
|
*/
|
|
if (_this5.depth === 0) {
|
|
result = e;
|
|
}
|
|
|
|
return resolve.call(client, result, onFulfilled, onRejected, _this5);
|
|
}));
|
|
|
|
return client;
|
|
};
|
|
|
|
client.catch = function (onRejected) {
|
|
return this.then(undefined, onRejected);
|
|
};
|
|
|
|
client.inspect = function () {
|
|
return this.promise.inspect();
|
|
};
|
|
|
|
/**
|
|
* internal helper method to handle command results
|
|
*
|
|
* @param {Promise[]} promises list of promises
|
|
* @param {Boolean} option if true extract value property from selenium result
|
|
*/
|
|
client.unify = function (promises) {
|
|
var option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
promises = Array.isArray(promises) ? promises : [promises];
|
|
|
|
return _promise2.default.all(promises)
|
|
/**
|
|
* extract value property from result if desired
|
|
*/
|
|
.then(function (result) {
|
|
if (!option.extractValue || !Array.isArray(result)) {
|
|
return result;
|
|
}
|
|
|
|
return result.map(function (res) {
|
|
return res.value;
|
|
});
|
|
|
|
/**
|
|
* sanitize result for better assertion
|
|
*/
|
|
}).then(function (result) {
|
|
if (Array.isArray(result) && result.length === 1) {
|
|
result = result[0];
|
|
}
|
|
|
|
if (option.lowercase && typeof result === 'string') {
|
|
result = result.toLowerCase();
|
|
}
|
|
|
|
return result;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* addCommand handler for async or standalone mode
|
|
* @param {String} fnName function name
|
|
* @param {Function} fn function
|
|
* @param {Boolean} forceOverwrite if true it can overwrite existing commands
|
|
*/
|
|
client.addCommand = function (fnName, fn, forceOverwrite) {
|
|
if (typeof fn === 'string') {
|
|
var namespace = arguments[0];
|
|
fnName = arguments[1];
|
|
fn = arguments[2];
|
|
forceOverwrite = arguments[3];
|
|
|
|
switch (typeof client[namespace]) {
|
|
case 'function':
|
|
throw new _ErrorHandler.RuntimeError('Command namespace "' + namespace + '" is used internally, and can\'t be overwritten!');
|
|
case 'object':
|
|
if (client[namespace][fnName] && !forceOverwrite) {
|
|
throw new _ErrorHandler.RuntimeError('Command "' + fnName + '" is already defined!');
|
|
}
|
|
break;
|
|
}
|
|
return unit.lift(namespace, fnName, fn);
|
|
}
|
|
|
|
if (client[fnName] && !forceOverwrite) {
|
|
throw new _ErrorHandler.RuntimeError('Command "' + fnName + '" is already defined!');
|
|
}
|
|
return unit.lift(fnName, fn);
|
|
};
|
|
|
|
client.getPrototype = function () {
|
|
return prototype;
|
|
};
|
|
|
|
client.transferPromiseness = function (target, promise) {
|
|
/**
|
|
* transfer WebdriverIO commands
|
|
*/
|
|
var clientFunctions = (0, _keys2.default)(prototype);
|
|
var functionsToTranfer = clientFunctions.concat(PROMISE_FUNCTIONS);
|
|
|
|
var _iteratorNormalCompletion = true;
|
|
var _didIteratorError = false;
|
|
var _iteratorError = undefined;
|
|
|
|
try {
|
|
for (var _iterator = (0, _getIterator3.default)(functionsToTranfer), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
|
var fnName = _step.value;
|
|
|
|
if (typeof promise[fnName] === 'function') {
|
|
target[fnName] = promise[fnName].bind(promise);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
_didIteratorError = true;
|
|
_iteratorError = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion && _iterator.return) {
|
|
_iterator.return();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError) {
|
|
throw _iteratorError;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (typeof modifier === 'function') {
|
|
client = modifier(client, options);
|
|
}
|
|
|
|
return client;
|
|
}
|
|
|
|
/**
|
|
* enhance base monad prototype with methods
|
|
*/
|
|
unit.lift = function (name, func) {
|
|
var commandGroup = prototype;
|
|
|
|
if (typeof func === 'string') {
|
|
var namespace = arguments[0];
|
|
name = arguments[1];
|
|
func = arguments[2];
|
|
|
|
if (!prototype[namespace]) {
|
|
prototype[namespace] = {};
|
|
}
|
|
|
|
commandGroup = prototype[namespace];
|
|
}
|
|
|
|
rawCommands[name] = func;
|
|
|
|
commandGroup[name] = function () {
|
|
var nextPromise = this.promise;
|
|
|
|
/**
|
|
* commands executed inside commands don't have to wait
|
|
* on any promise
|
|
*/
|
|
if (this.isExecuted) {
|
|
nextPromise = this.lastPromise;
|
|
}
|
|
|
|
var client = unit(nextPromise);
|
|
|
|
/**
|
|
* catch stack to find information about where the command that causes
|
|
* the error was used (stack line 2) and only save it when it was not
|
|
* within WebdriverIO context
|
|
*/
|
|
var stack = new Error().stack;
|
|
var lineInTest = stack.split('\n').slice(2, 3).join('\n');
|
|
var fileAndPosition = lineInTest.slice(lineInTest.indexOf('(') + 1, lineInTest.indexOf(')'));
|
|
var atCommand = lineInTest.trim().slice(3).split(' ')[0];
|
|
|
|
atCommand = atCommand.slice(atCommand.lastIndexOf('.') + 1);
|
|
|
|
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
|
args[_key2] = arguments[_key2];
|
|
}
|
|
|
|
var trace = name + '(' + _sanitize2.default.args(args) + ') - ' + fileAndPosition.slice(fileAndPosition.lastIndexOf('/') + 1);
|
|
if ((0, _keys2.default)(prototype).indexOf(atCommand) === -1 && atCommand !== 'exports') {
|
|
stacktrace = [trace];
|
|
} else {
|
|
/**
|
|
* save trace for nested commands
|
|
*/
|
|
stacktrace.push(trace);
|
|
}
|
|
|
|
/**
|
|
* determine execution depth:
|
|
* This little tweak helps us to determine whether the command was executed
|
|
* by the test script or by another command. With that we can make sure
|
|
* that errors are getting thrown once they bubbled up the command chain.
|
|
*/
|
|
client.depth = stack.split('\n').filter(function (line) {
|
|
return !!line.match(/\/lib\/(commands|protocol)\/(\w+)\.js/);
|
|
}).length;
|
|
|
|
/**
|
|
* queue command
|
|
*/
|
|
client.name = name;
|
|
client.lastResult = this.lastResult;
|
|
client.next(func, args, name);
|
|
return client;
|
|
};
|
|
|
|
return unit;
|
|
};
|
|
|
|
/**
|
|
* register event emitter
|
|
*/
|
|
|
|
var _loop = function _loop(eventCommand) {
|
|
prototype[eventCommand] = function () {
|
|
for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
|
|
args[_key3] = arguments[_key3];
|
|
}
|
|
|
|
/**
|
|
* custom commands needs to get emitted and registered in order
|
|
* to prevent race conditions
|
|
*/
|
|
if (INTERNAL_EVENTS.indexOf(args[0]) === -1) {
|
|
return this.finally(function () {
|
|
return eventHandler[eventCommand].apply(eventHandler, args);
|
|
});
|
|
}
|
|
|
|
eventHandler[eventCommand].apply(eventHandler, args);
|
|
return this;
|
|
};
|
|
};
|
|
|
|
for (var eventCommand in EVENTHANDLER_FUNCTIONS) {
|
|
_loop(eventCommand);
|
|
}
|
|
|
|
return unit;
|
|
};
|
|
|
|
exports.default = WebdriverIO;
|
|
module.exports = exports['default']; |