@wdio/sync
Version:
A WebdriverIO plugin. Helper module to run WebdriverIO commands synchronously
155 lines (154 loc) • 5.61 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = __importDefault(require("@wdio/logger"));
const executeHooksWithArgs_1 = __importDefault(require("./executeHooksWithArgs"));
const utils_1 = require("./utils");
const fibers_1 = require("./fibers");
const log = (0, logger_1.default)('@wdio/sync');
let inCommandHook = false;
const timers = [];
const elements = new Set();
/**
* resets `_NOT_FIBER` if Timer has timed out
*/
process.on('WDIO_TIMER', (payload) => {
if (payload.start) {
return timers.push(payload.id);
}
if (timers.includes(payload.id)) {
while (timers.pop() !== payload.id)
;
}
if (payload.timeout) {
elements.forEach(/* istanbul ignore next */ /* istanbul ignore next */ element => { delete element._NOT_FIBER; });
}
if (timers.length === 0) {
elements.clear();
}
});
/**
* wraps a function into a Fiber ready context to enable sync execution and hooks
* @param {Function} fn function to be executed
* @param {String} commandName name of that function
* @param {Function[]} beforeCommand method to be executed before calling the actual function
* @param {Function[]} afterCommand method to be executed after calling the actual function
* @return {Function} actual wrapped function
*/
function wrapCommand(commandName, fn) {
return function wrapCommandFn(...args) {
/**
* print error if a user is using a fiberized command outside of the Fibers context
*/
if (!global._HAS_FIBER_CONTEXT && global.WDIO_WORKER) {
log.warn(`Can't return command result of ${commandName} synchronously because command ` +
'was executed outside of an it block, hook or step definition!');
}
/**
* store element if Timer is running to reset `_NOT_FIBER` if timeout has occurred
*/
if (timers.length > 0) {
elements.add(this);
}
/**
* Avoid running some functions in Future that are not in Fiber.
*/
if (this._NOT_FIBER === true) {
this._NOT_FIBER = isNotInFiber(this, fn.name);
return fn.apply(this, args);
}
/**
* all named nested functions run in parent Fiber context
*/
this._NOT_FIBER = fn.name !== '';
const future = new fibers_1.Future();
const result = runCommandWithHooks.apply(this, [commandName, fn, ...args]);
result.then(future.return.bind(future), future.throw.bind(future));
try {
const futureResult = future.wait();
inFiber(this);
return futureResult;
}
catch (err) {
/**
* in case some 3rd party lib rejects without bundling into an error
*/
if (typeof err === 'string') {
throw new Error(err);
}
/**
* in case we run commands where no fiber function was used
* e.g. when we call deleteSession
*/
if (err.message.includes('Can\'t wait without a fiber')) {
return result;
}
inFiber(this);
throw err;
}
};
}
exports.default = wrapCommand;
/**
* helper method that runs the command with before/afterCommand hook
*/
async function runCommandWithHooks(commandName, fn, ...args) {
// save error for getting full stack in case of failure
// should be before any async calls
const stackError = new Error();
await runCommandHook.call(this, 'beforeCommand', this.options.beforeCommand, [commandName, args]);
let commandResult;
let commandError;
try {
commandResult = await fn.apply(this, args);
}
catch (err) {
commandError = (0, utils_1.sanitizeErrorMessage)(err, stackError);
}
await runCommandHook.call(this, 'afterCommand', this.options.afterCommand, [commandName, args, commandResult, commandError]);
if (commandError) {
throw commandError;
}
return commandResult;
}
async function runCommandHook(hookName, hookFn, args) {
if (!inCommandHook) {
inCommandHook = true;
await (0, executeHooksWithArgs_1.default)(hookName, hookFn, args);
inCommandHook = false;
}
}
/**
* isNotInFiber
* if element or its parent has element id then we are in parent's Fiber
* @param {object} context browser or element
* @param {string} fnName function name
*/
function isNotInFiber(context, fnName) {
return fnName !== '' && !!(context.elementId || (context.parent && context.parent.elementId));
}
/**
* set `_NOT_FIBER` to `false` for element and its parents
* @param {object} context browser or element
*/
function inFiber(context) {
const multiRemoteContext = context;
if (multiRemoteContext.constructor.name === 'MultiRemoteDriver') {
return multiRemoteContext.instances.forEach(instance => {
multiRemoteContext[instance]._NOT_FIBER = false;
let parent = multiRemoteContext[instance].parent;
while (parent && parent._NOT_FIBER) {
parent._NOT_FIBER = false;
parent = parent.parent;
}
});
}
context._NOT_FIBER = false;
let parent = context.parent;
while (parent && parent._NOT_FIBER) {
parent._NOT_FIBER = false;
parent = parent.parent;
}
}
;