Skip to content

Commit

Permalink
mousedown and mouseup action
Browse files Browse the repository at this point in the history
wait for visible, pattern, page actions
back action #52
provide collection action - for cases when rules are not defined before actions are performed #64
postActions support
concurrent cases actions support - when we don't know what to do exactly
performance improved for wait action
actions results rule type - for cases when we don't need parse
possibility to use blank scopes for rules, for virtual rules which can get results from actions for example

Refs #57

🐗
  • Loading branch information
jifeon committed Dec 9, 2015
1 parent 2090f74 commit 8a9fb06
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 33 deletions.
196 changes: 186 additions & 10 deletions lib/Actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ function Actions (options) {
Actions.prototype = {
TYPES: {
CLICK: 'click',
MOUSE_DOWN: 'mousedown',
MOUSE_UP: 'mouseup',
WAIT: 'wait',
WAIT_FOR_VISIBLE: 'waitForVisible',
WAIT_FOR_PATTERN: 'waitForPattern',
WAIT_FOR_PAGE: 'waitForPage',
TYPE: 'type',
CONDITION: 'conditionalActions',
EXIST: 'exist'
EXIST: 'exist',
BACK: 'back',
PROVIDE_COLLECTION: 'provideCollection'
},

/**
Expand All @@ -32,6 +39,22 @@ Actions.prototype = {
return this.performActions(actions, parentSelector);
},

/**
* Perform parsing rule
* @param {Rule} rule
* @param {string} parentSelector
* @returns {Promise}
*/
performPostActionsForRule: function (rule, parentSelector) {
var actions = rule.postActions;

if (!actions) {
return vow.resolve();
}

return this.performActions(actions, parentSelector);
},

/**
* Perform array of actions
* @param {Array} actions
Expand Down Expand Up @@ -76,34 +99,68 @@ Actions.prototype = {
debug('Perform action %o for generated selector %s', action, selector);

var waitingForPage;
if (action.waitForPage) {
if (action.waitForPage || action.type === this.TYPES.BACK) {
waitingForPage = this.waitForPage(action.waitForPageTimeout);
} else {
waitingForPage = vow.resolve();
}

var casesPromise;
if (action.cases) {
casesPromise = this._performCases(action.cases, parentSelector);
}

var actionPromise;
switch (action.type) {
case this.TYPES.CLICK:
actionPromise = this.click(selector);
break;

case this.TYPES.MOUSE_DOWN:
actionPromise = this.mousedown(selector);
break;

case this.TYPES.MOUSE_UP:
actionPromise = this.mouseup(selector);
break;

case this.TYPES.WAIT:
actionPromise = this.waitElement(selector, action.timeout);
break;

case this.TYPES.WAIT_FOR_VISIBLE:
actionPromise = this.waitElementIsVisible(selector, action.timeout);
break;

case this.TYPES.WAIT_FOR_PATTERN:
actionPromise = this.waitForPattern(selector, action.pattern, action.timeout);
break;

case this.TYPES.WAIT_FOR_PAGE:
actionPromise = this.waitForPage(action.timeout);
break;

case this.TYPES.TYPE:
actionPromise = this.type(selector, action.text);
break;

case this.TYPES.CONDITION:
actionPromise = this.performConditionalActions(selector, action.conditions, action.actions);
actionPromise = this.performConditionalActions(selector, action.conditions, action.actions, action.elseActions);
break;

case this.TYPES.EXIST:
actionPromise = this.exist(selector);
break;

case this.TYPES.BACK:
actionPromise = this.back();
break;

case this.TYPES.PROVIDE_COLLECTION:
debug('Providing collection %o', action.collection);
actionPromise = vow.resolve(action.collection);
break;

default:
var customAction = this._customActions[action.type];
if (!customAction) {
Expand All @@ -115,7 +172,46 @@ Actions.prototype = {
}

return vow.all([actionPromise, waitingForPage]).spread(function (result) {
return result;
return casesPromise || result;
});
},

_performCases: function (cases, parentSelector) {
debug('handle several cases in parallel %o', cases);

var wonCase = null;
var promises = cases.map(function (actions, caseNumber) {
var beginningPromise = this._performAction(actions[0], parentSelector);
return actions
.slice(1)
.reduce(function (promise, action, i, array) {
return promise.then(function () {
if (wonCase !== null && array !== cases[wonCase]) {
return vow.reject('Failed actions chain');
}

if (action.trueCase) {
wonCase = caseNumber;
debug('Won case with actions %o', cases[wonCase]);
}

return this._performAction(action, parentSelector);
}, this);
}.bind(this), beginningPromise)
.then(function (results) {
if (wonCase === null) {
wonCase = caseNumber;
debug('Won case with actions %o', cases[wonCase]);
}
return results;
}, function (reason) {
debug('Chain %o was reject with reason %s', actions, reason);
throw reason;
});
}, this);

return vow.any(promises).then(function () {
return promises[wonCase];
});
},

Expand Down Expand Up @@ -148,10 +244,47 @@ Actions.prototype = {
}, [selector], timeout, interval);
},

/**
* Wait for an element is on the page and visible
* @param {string} selector
* @param {number} [timeout]
* @param {number} [interval]
* @returns {Promise}
*/
waitElementIsVisible: function (selector, timeout, interval) {
debug('._waitElementIsVisible() ' + selector);
return this.wait(/* @covignore */ function (selector) {
var nodes = Array.prototype.slice.call(Sizzle(selector), 0);
return nodes.some(function (node) {
return node.offsetWidth !== 0 && node.offsetHeight !== 0;
});
}, function (visible) {
return visible;
}, [selector], timeout, interval);
},

/**
* Wait for an element'c content matches pattern
* @param {string} selector
* @param {string} pattern
* @param {number} [timeout]
* @param {number} [interval]
* @returns {Promise}
*/
waitForPattern: function (selector, pattern, timeout, interval) {
debug('._waitForPattern() %s on selector %s', pattern, selector);
return this.wait(/* @covignore */ function (selector) {
var nodes = Sizzle(selector);
return nodes.length && nodes[0].textContent || '';
}, function (text) {
return text.match(pattern) !== null;
}, [selector], timeout, interval);
},

/**
* Wait until function evalFunction expected in checkerFunction result
* @param {Function} evalFunction
* @param {Function} checkerFunction
* @param {Function} [checkerFunction]
* @param {Array} [args]
* @param {number} [timeout]
* @param {number} [interval]
Expand All @@ -161,11 +294,25 @@ Actions.prototype = {
var deferred = vow.defer();
args = args || [];
timeout = timeout || 5000;
interval = interval || 0;
interval = interval || 10;

checkerFunction = checkerFunction || function (result) {
return !!result
};

var errback = function (msg) {
clearTimeout(timeoutId);
clearInterval(intervalId);
deferred.reject(new Error('Error during _wait with args ' + args.toString() + ': ' + msg));
};

var timeoutId = setTimeout(function () {
this._env.removeErrback(errback);
clearInterval(intervalId);
deferred.reject(new Error('Timeout for _wait with arguments: ' + args.toString()));
}, timeout);
}.bind(this), timeout);

this._env.addErrback(errback);

var evalArgs = args.slice(0);
evalArgs.push(evalFunction);
Expand All @@ -176,9 +323,10 @@ Actions.prototype = {
if (checkerFunction.apply(null, arguments)) {
clearTimeout(timeoutId);
clearInterval(intervalId);
this._env.removeErrback(errback);
deferred.resolve();
}
});
}, this);
}.bind(this), interval);

return deferred.promise();
Expand Down Expand Up @@ -215,6 +363,26 @@ Actions.prototype = {
});
},

/**
* Perform mousedown on the element matched by selector
* @param {string} selector
* @returns {Promise}
*/
mousedown: function (selector) {
debug('mousedown on %s', selector);
return this._env.mousedown(selector);
},

/**
* Perform mouseup on the element matched by selector
* @param {string} selector
* @returns {Promise}
*/
mouseup: function (selector) {
debug('mouseup on %s', selector);
return this._env.mouseup(selector);
},

/**
* Type text to the element
* @param {string} selector
Expand Down Expand Up @@ -249,15 +417,16 @@ Actions.prototype = {
* @param {string} selector
* @param {Array} conditions
* @param {Array} actions
* @param {Array} [elseActions]
* @returns {Promise}
*/
performConditionalActions: function (selector, conditions, actions) {
performConditionalActions: function (selector, conditions, actions, elseActions) {
return this
.performActions(conditions, selector)
.then(function (result) {
if (!result) {
debug('Conditional actions failed with result %s, skip %o', result, actions);
return;
return elseActions ? this.performActions(elseActions, selector) : false;
}

debug('Conditional actions return %s, go with real some', result);
Expand All @@ -274,6 +443,13 @@ Actions.prototype = {
return this._env.evaluateJs(selector, /* @covignore */ function (selector) {
return Sizzle(selector).length > 0;
});
},

/**
* Navigates to previous page
*/
back: function () {
return this._env.back();
}
};

Expand Down
5 changes: 5 additions & 0 deletions lib/BrowserEnvironment.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ BrowserEnvironment.prototype = _.create(Environment.prototype, /**@lends Browser

var result = evalFunc.apply(null, args);
return vow.resolve(result);
},

// todo: write tests
back: function () {
window.history.back();
}
});

Expand Down
24 changes: 24 additions & 0 deletions lib/Environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ var vow = require('vow'),

function Environment(options) {
debug('Initializing...');

this._errbacks = [];
}

Environment.prototype = {
Expand Down Expand Up @@ -50,6 +52,28 @@ Environment.prototype = {
*/
waitForPage: function (timeout) {
throw new Error('You must redefine waitForPage method in child environment');
},

back: function () {
throw new Error('You must redefine back method in child environment');
},

mousedown: function () {
throw new Error('You must redefine back method in child environment');
},

mouseup: function () {
throw new Error('You must redefine back method in child environment');
},

addErrback: function (errback) {
this._errbacks.push(errback);
},

removeErrback: function (errback) {
this._errbacks = this._errbacks.filter(function (e) {
return e !== errback;
});
}
};

Expand Down
Loading

0 comments on commit 8a9fb06

Please sign in to comment.