_waitForElement.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. var util = require('util');
  2. var events = require('events');
  3. var Logger = require('../../util/logger.js');
  4. var Utils = require('../../util/utils.js');
  5. /*!
  6. * Base class for waitForElement commands. It provides a command
  7. * method and element* methods to be overwritten by subclasses
  8. *
  9. * @constructor
  10. */
  11. function WaitForElement() {
  12. events.EventEmitter.call(this);
  13. this.startTimer = null;
  14. this.cb = null;
  15. this.ms = null;
  16. this.element = null;
  17. this.abortOnFailure = typeof this.client.api.globals.abortOnAssertionFailure == 'undefined' || this.client.api.globals.abortOnAssertionFailure;
  18. this.selector = null;
  19. this.locateStrategy = this.client.locateStrategy || 'css selector';
  20. this.rescheduleInterval = this.client.api.globals.waitForConditionPollInterval || this.client.options.waitForConditionPollInterval || 500; //ms
  21. this.throwOnMultipleElementsReturned = this.client.api.globals.throwOnMultipleElementsReturned || this.client.options.throwOnMultipleElementsReturned || false;
  22. this.protocol = require('../protocol.js')(this.client);
  23. }
  24. util.inherits(WaitForElement, events.EventEmitter);
  25. /*!
  26. * The public command function which will be called by the test runner. Arguments can be passed in a variety of ways.
  27. *
  28. * The custom message always is last and the callback is always before the message or last if a message is not passed.
  29. *
  30. * The second argument is always the time in milliseconds. The third argument can be either of:
  31. * - abortOnFailure: this can overwrite the default behaviour of aborting the test if the condition is not met within the specified time
  32. * - rescheduleInterval: this can overwrite the default polling interval (currently 500ms)
  33. * The above can be supplied also together, in which case the rescheduleInterval is specified before the abortOnFailure.
  34. *
  35. * Some of the multiple usage possibilities:
  36. * ---------------------------------------------------------------------------
  37. * - with no arguments; in this case a global default timeout is expected
  38. * waitForElement('body');
  39. *
  40. * - with a global default timeout and a callback
  41. * waitForElement('body', function() {});
  42. *
  43. * - with a global default timeout, a callback and a custom message
  44. * waitForElement('body', function() {}, 'test message');
  45. *
  46. * - with only the timeout
  47. * waitForElement('body', 500);
  48. *
  49. * - with a timeout and a custom message
  50. * waitForElement('body', 500, 'test message);
  51. *
  52. * - with a timeout and a callback
  53. * waitForElement('body', 500, function() { .. });
  54. *
  55. * - with a timeout and a custom abortOnFailure
  56. * waitForElement('body', 500, true);
  57. *
  58. * - with a timeout, a custom abortOnFailure and a custom message
  59. * waitForElement('body', 500, true, 'test message');
  60. *
  61. * - with a timeout, a custom abortOnFailure and a callback
  62. * waitForElement('body', 500, true, function() { .. });
  63. *
  64. * - with a timeout, a custom abortOnFailure, a callback and a custom message
  65. * waitForElement('body', 500, true, function() { .. }, 'test message');
  66. *
  67. * - with a timeout, a custom reschedule interval and a callback
  68. * waitForElement('body', 500, 100, function() { .. });
  69. *
  70. * - with a timeout, a custom rescheduleInterval and a custom abortOnFailure
  71. * waitForElement('body', 500, 100, false);
  72. *
  73. *
  74. * @param {string} selector
  75. * @param {number|function|string} milliseconds
  76. * @param {function|boolean|string|number} callbackOrAbort
  77. * @returns {WaitForElement}
  78. */
  79. WaitForElement.prototype.command = function commandFn(selector, milliseconds, callbackOrAbort) {
  80. this.startTimer = new Date().getTime();
  81. this.ms = this.setMilliseconds(milliseconds);
  82. this._stackTrace = commandFn.stackTrace;
  83. if (typeof arguments[1] === 'function') {
  84. ////////////////////////////////////////////////
  85. // The command was called with an implied global timeout:
  86. //
  87. // waitForElement('body', function() {});
  88. // waitForElement('body', function() {}, 'custom message');
  89. ////////////////////////////////////////////////
  90. this.cb = arguments[1];
  91. } else if (typeof arguments[2] === 'boolean') {
  92. ////////////////////////////////////////////////
  93. // The command was called with a custom abortOnFailure:
  94. //
  95. // waitForElement('body', 500, false);
  96. ////////////////////////////////////////////////
  97. this.abortOnFailure = arguments[2];
  98. // The optional callback is the 4th argument now
  99. this.cb = arguments[3] || function() {};
  100. } else if (typeof arguments[2] === 'number') {
  101. ////////////////////////////////////////////////
  102. // The command was called with a custom rescheduleInterval:
  103. //
  104. // waitForElement('body', 500, 100);
  105. ////////////////////////////////////////////////
  106. this.rescheduleInterval = arguments[2];
  107. if (typeof arguments[3] === 'boolean') {
  108. ////////////////////////////////////////////////
  109. // The command was called with a custom rescheduleInterval and custom abortOnFailure:
  110. //
  111. // waitForElement('body', 500, 100, false);
  112. ////////////////////////////////////////////////
  113. this.abortOnFailure = arguments[3];
  114. // The optional callback is the 5th argument now
  115. this.cb = arguments[4] || function() {};
  116. } else {
  117. // The optional callback is the 4th argument now
  118. this.cb = arguments[3] || function() {};
  119. }
  120. } else {
  121. // The optional callback is the 3th argument now
  122. this.cb = (typeof callbackOrAbort === 'function' && callbackOrAbort) || function() {};
  123. }
  124. // support for a custom message
  125. this.message = null;
  126. if (arguments.length > 1) {
  127. var lastArgument = arguments[arguments.length - 1];
  128. if (typeof lastArgument === 'string') {
  129. this.message = lastArgument;
  130. }
  131. }
  132. this.selector = selector;
  133. this.checkElement();
  134. return this;
  135. };
  136. /*!
  137. * @override
  138. */
  139. WaitForElement.prototype.elementFound = function(result, now) {};
  140. /*!
  141. * @override
  142. */
  143. WaitForElement.prototype.elementNotFound = function(result, now) {};
  144. /*!
  145. * @override
  146. */
  147. WaitForElement.prototype.elementVisible = function(result, now) {};
  148. /*!
  149. * @override
  150. */
  151. WaitForElement.prototype.elementNotVisible = function(result, now) {};
  152. /*!
  153. * Reschedule the checkElement
  154. */
  155. WaitForElement.prototype.reschedule = function(method) {
  156. var self = this;
  157. method = method || 'checkElement';
  158. setTimeout(function() {
  159. self[method]();
  160. }, this.rescheduleInterval);
  161. };
  162. WaitForElement.prototype.complete = function() {
  163. var args = Array.prototype.slice.call(arguments, 0);
  164. args.push(this);
  165. this.cb.apply(this.client.api, args);
  166. this.emit('complete');
  167. return this;
  168. };
  169. WaitForElement.prototype.pass = function(result, defaultMsg, timeMs) {
  170. this.message = this.formatMessage(defaultMsg, timeMs);
  171. this.client.assertion(true, null, null, this.message, this.abortOnFailure);
  172. return this.complete(result);
  173. };
  174. WaitForElement.prototype.fail = function(result, actual, expected, defaultMsg) {
  175. this.message = this.formatMessage(defaultMsg);
  176. this.client.assertion(false, actual, expected, this.message, this.abortOnFailure, this._stackTrace);
  177. return this.complete(result);
  178. };
  179. /*!
  180. * Will start checking if the element exists and if not re-schedule the check
  181. * until the timeout expires or the condition has been met
  182. */
  183. WaitForElement.prototype.checkElement = function() {
  184. var self = this;
  185. this.getProtocolCommand(function(result) {
  186. var now = new Date().getTime();
  187. if (result.value && result.value.length > 0) {
  188. if (result.value.length > 1) {
  189. var message = 'WaitForElement found ' + result.value.length + ' elements for selector "' + self.selector + '".';
  190. if (self.throwOnMultipleElementsReturned) {
  191. throw new Error(message);
  192. } else if (self.client.options.output) {
  193. console.log(Logger.colors.green(' Warn: ' + message + ' Only the first one will be checked.'));
  194. }
  195. }
  196. self.element = result.value[0].ELEMENT;
  197. return self.elementFound(result, now);
  198. }
  199. return self.elementNotFound(result, now);
  200. });
  201. };
  202. WaitForElement.prototype.getProtocolCommand = function(callback) {
  203. return this.protocol.elements(this.locateStrategy, this.selector, callback);
  204. };
  205. /*!
  206. * Will start checking if the element is visible and if not re-schedule the check
  207. * until the timeout expires or the condition has been met
  208. */
  209. WaitForElement.prototype.isVisible = function() {
  210. var self = this;
  211. this.protocol.elementIdDisplayed(this.element, function(result) {
  212. var now = new Date().getTime();
  213. if (result.status === 0 && result.value === true) {
  214. // element was visible
  215. return self.elementVisible(result, now);
  216. }
  217. if (result.status === -1 && result.errorStatus === 10) {
  218. return self.checkElement();
  219. }
  220. return self.elementNotVisible(result, now);
  221. });
  222. };
  223. /**
  224. * @param {string} defaultMsg
  225. * @param {number} [timeMs]
  226. * @returns {string}
  227. */
  228. WaitForElement.prototype.formatMessage = function (defaultMsg, timeMs) {
  229. return Utils.format(this.message || defaultMsg, this.selector, timeMs || this.ms);
  230. };
  231. /**
  232. * Set the time in milliseconds to wait for the condition, accepting a given value or a globally defined default
  233. *
  234. * @param {number} [timeoutMs]
  235. * @throws Will throw an error if the global default is undefined or a non-number
  236. * @returns {number}
  237. */
  238. WaitForElement.prototype.setMilliseconds = function (timeoutMs) {
  239. if (timeoutMs && typeof timeoutMs === 'number') {
  240. return timeoutMs;
  241. }
  242. var globalTimeout = this.client.api.globals.waitForConditionTimeout;
  243. if (typeof globalTimeout !== 'number') {
  244. throw new Error('waitForElement expects second parameter to have a global default ' +
  245. '(waitForConditionTimeout) to be specified if not passed as the second parameter ');
  246. }
  247. return globalTimeout;
  248. };
  249. module.exports = WaitForElement;