compat.js

  1. /**
  2. * The compat module adds support for Intern 1.x functional testing APIs based on WD.js 0.2.2 to Leadfoot.
  3. *
  4. * @deprecated Use the standard Leadfoot APIs.
  5. * @module leadfoot/compat
  6. */
  7. var Command = require('./Command');
  8. var Promise = require('dojo/Promise');
  9. var pollUntil = require('./helpers/pollUntil');
  10. var strategies = require('./lib/strategies');
  11. var topic = require('dojo/topic');
  12. /**
  13. * Deprecates `fromMethod` for `toMethod` and returns the correct call to `toMethod`.
  14. *
  15. * @private
  16. * @param {string} fromMethod
  17. * @param {string} toMethod
  18. * @returns {Function}
  19. */
  20. function deprecate(fromMethod, toMethod) {
  21. return function () {
  22. warn('Command#' + fromMethod, 'Command#' + toMethod);
  23. return this[toMethod].apply(this, arguments);
  24. };
  25. }
  26. /**
  27. * Deprecates the element context signature of a method and returns a new command with the correct call to
  28. * `toMethod` on the element.
  29. *
  30. * @private
  31. * @param {string} fromMethod
  32. * The name of the old method.
  33. *
  34. * @param {string=} toMethod
  35. * The name of the replacement method on the element, if it is different than the name of the old method. If
  36. * omitted, it is assumed that the method name
  37. *
  38. * @param {Function=} fn
  39. * An optional function that will be invoked in lieu of a call to the original method if a non-element signature
  40. * is used.
  41. *
  42. * @returns {Function}
  43. */
  44. function deprecateElementSig(fromMethod, toMethod, fn) {
  45. return function (element) {
  46. if (element && element.elementId) {
  47. warn('Command#' + fromMethod + '(element)', 'Command#find then Command#' + fromMethod + ', or ' +
  48. 'Command#find then Command#then(function (element) { return element.' +
  49. (toMethod || fromMethod) + '(); }');
  50. var args = Array.prototype.slice.call(arguments, 1);
  51. return new this.constructor(this, function () {
  52. return element[toMethod || fromMethod].apply(this, args);
  53. });
  54. }
  55. return fn ? fn.apply(this, arguments) : Command.prototype[fromMethod].apply(this, arguments);
  56. };
  57. }
  58. /**
  59. * Deprecates the element context signature of a method as well as its non-element signature to go to `toMethod`.
  60. *
  61. * @private
  62. * @param {string} fromMethod
  63. * @param {string} toMethod
  64. * @returns {Function}
  65. */
  66. function deprecateElementAndStandardSig(fromMethod, toMethod) {
  67. return deprecateElementSig(fromMethod, toMethod, deprecate(fromMethod, toMethod));
  68. }
  69. /**
  70. * Warns a user once that the method given by `name` is deprecated.
  71. *
  72. * @private
  73. * @method
  74. * @param {string} name The name of the old method.
  75. * @param {string=} replacement Replacement instructions, if a direct replacement for the old method exists.
  76. * @param {string=} extra Extra information about the deprecation.
  77. */
  78. var warn = (function () {
  79. var warned = {};
  80. return function (name, replacement, extra) {
  81. if (warned[name]) {
  82. return;
  83. }
  84. warned[name] = true;
  85. topic.publish('/deprecated', name, replacement, extra);
  86. };
  87. })();
  88. var methods = {
  89. get sessionID() {
  90. warn('Command#sessionID', 'the Command#session.sessionId property');
  91. return this.session.sessionId;
  92. },
  93. status: function () {
  94. warn('Command#status');
  95. return new this.constructor(this, function () {
  96. return this.session.server.getStatus();
  97. });
  98. },
  99. init: function () {
  100. warn('Command#init');
  101. return this;
  102. },
  103. sessions: function () {
  104. warn('Command#sessions');
  105. return new this.constructor(this, function () {
  106. return this.session.server.getSessions();
  107. });
  108. },
  109. sessionCapabilities: function () {
  110. warn('Command#sessionCapabilities', 'the Command#session.capabilities property');
  111. return new this.constructor(this, function () {
  112. return this.session.capabilities;
  113. });
  114. },
  115. altSessionCapabilities: function () {
  116. warn('Command#altSessionCapabilities', 'the Command#session.capabilities property');
  117. return new this.constructor(this, function () {
  118. return this.session.capabilities;
  119. });
  120. },
  121. getSessionId: function () {
  122. warn('Command#getSessionId', 'the Command#session.sessionId property');
  123. return new this.constructor(this, function () {
  124. return this.session.sessionId;
  125. });
  126. },
  127. getSessionID: function () {
  128. warn('Command#getSessionID', 'the Command#session.sessionId property');
  129. return new this.constructor(this, function () {
  130. return this.session.sessionId;
  131. });
  132. },
  133. setAsyncScriptTimeout: deprecate('setAsyncScriptTimeout', 'setExecuteAsyncTimeout'),
  134. setWaitTimeout: deprecate('setWaitTimeout', 'setFindTimeout'),
  135. setImplicitWaitTimeout: deprecate('setImplicitWaitTimeout', 'setFindTimeout'),
  136. windowHandle: deprecate('windowHandle', 'getCurrentWindowHandle'),
  137. windowHandles: deprecate('windowHandles', 'getAllWindowHandles'),
  138. url: deprecate('url', 'getCurrentUrl'),
  139. forward: deprecate('forward', 'goForward'),
  140. back: deprecate('back', 'goBack'),
  141. safeExecute: deprecate('safeExecute', 'execute'),
  142. eval: function (code) {
  143. warn('Command#eval', 'Command#execute with a return call');
  144. return this.execute('return eval(arguments[0]);', [ code ]);
  145. },
  146. safeEval: function (code) {
  147. warn('Command#safeEval', 'Command#execute with a return call');
  148. return this.execute('return eval(arguments[0]);', [ code ]);
  149. },
  150. safeExecuteAsync: deprecate('safeExecuteAsync', 'executeAsync'),
  151. frame: deprecate('frame', 'switchToFrame'),
  152. window: deprecate('window', 'switchToWindow'),
  153. close: deprecate('close', 'closeCurrentWindow'),
  154. windowSize: deprecate('windowSize', 'setWindowSize'),
  155. setWindowSize: function () {
  156. var args = Array.prototype.slice.call(arguments, 0);
  157. if (args.length === 3 && typeof args[0] === 'number') {
  158. warn('Command#setWindowSize(width, height, handle)',
  159. 'Command#setWindowSize(handle, width, height)');
  160. args.unshift(args.pop());
  161. }
  162. return Command.prototype.setWindowSize.apply(this, args);
  163. },
  164. setWindowPosition: function () {
  165. var args = Array.prototype.slice.call(arguments, 0);
  166. if (args.length === 3 && typeof args[0] === 'number') {
  167. warn('Command#setWindowPosition(x, y, handle)',
  168. 'Command#setWindowPosition(handle, x, y)');
  169. args.unshift(args.pop());
  170. }
  171. return Command.prototype.setWindowPosition.apply(this, args);
  172. },
  173. maximize: deprecate('maximize', 'maximizeWindow'),
  174. allCookies: deprecate('allCookies', 'getCookies'),
  175. deleteAllCookies: deprecate('deleteAllCookies', 'clearCookies'),
  176. source: deprecate('source', 'getPageSource'),
  177. title: deprecate('title', 'getPageTitle'),
  178. element: deprecate('element', 'find'),
  179. elementByClassName: deprecate('elementByClassName', 'findByClassName'),
  180. elementByCssSelector: deprecate('elementByCssSelector', 'findByCssSelector'),
  181. elementById: deprecate('elementById', 'findById'),
  182. elementByName: deprecate('elementByName', 'findByName'),
  183. elementByLinkText: deprecate('elementByLinkText', 'findByLinkText'),
  184. elementByPartialLinkText: deprecate('elementByPartialLinkText', 'findByPartialLinkText'),
  185. elementByTagName: deprecate('elementByTagName', 'findByTagName'),
  186. elementByXPath: deprecate('elementByXPath', 'findByXpath'),
  187. elementByCss: deprecate('elementByCss', 'findByCssSelector'),
  188. elements: deprecate('elements', 'findAll'),
  189. elementsByClassName: deprecate('elementsByClassName', 'findAllByClassName'),
  190. elementsByCssSelector: deprecate('elementsByCssSelector', 'findAllByCssSelector'),
  191. elementsById: function (value) {
  192. warn('Command#elementsById', 'Command#findById');
  193. return this.findAll('id', value);
  194. },
  195. elementsByName: deprecate('elementsByName', 'findAllByName'),
  196. elementsByLinkText: deprecate('elementsByLinkText', 'findAllByLinkText'),
  197. elementsByPartialLinkText: deprecate('elementsByPartialLinkText', 'findAllByPartialLinkText'),
  198. elementsByTagName: deprecate('elementsByTagName', 'findAllByTagName'),
  199. elementsByXPath: deprecate('elementsByXPath', 'findAllByXpath'),
  200. elementsByCss: deprecate('elementsByCss', 'findAllByCssSelector'),
  201. elementOrNull: function (using, value) {
  202. warn('Command#elementOrNull', 'Command#find and Command#finally, or Command#findAll');
  203. return this.find(using, value).catch(function () {
  204. return null;
  205. });
  206. },
  207. elementIfExists: function (using, value) {
  208. warn('Command#elementIfExists', 'Command#find and Command#finally, or Command#findAll');
  209. return this.find(using, value).catch(function () {});
  210. },
  211. hasElement: function (using, value) {
  212. warn('Command#hasElement', 'Command#find and Command#then(exists, doesNotExist)');
  213. return this.find(using, value).then(function () {
  214. return true;
  215. }, function () {
  216. return false;
  217. });
  218. },
  219. active: deprecate('active', 'getActiveElement'),
  220. clickElement: deprecateElementAndStandardSig('clickElement', 'click'),
  221. submit: deprecateElementSig('submit'),
  222. text: function (element) {
  223. return new this.constructor(this, function () {
  224. if ((!element || element === 'body') && !this.context.length) {
  225. if (element === 'body') {
  226. warn('Command#text(\'body\')', 'Command#findByTagName(\'body\') then Command#getVisibleText');
  227. }
  228. else {
  229. warn('Command#text with no element', 'Command#findByTagName(\'body\') then Command#getVisibleText');
  230. }
  231. return this.session.findByTagName('body').then(function (body) {
  232. return body.getVisibleText();
  233. });
  234. }
  235. else if (element && element.elementId) {
  236. warn('Command#text(element)', 'Command#find then Command#getVisibleText, or ' +
  237. 'Command#find then Command#then(function (element) { return element.getVisibleText(); }');
  238. return element.getVisibleText();
  239. }
  240. else {
  241. warn('Command#text', 'Command#getVisibleText');
  242. if (this.context.isSingle) {
  243. return this.context[0].getVisibleText();
  244. }
  245. else {
  246. return Promise.all(this.context.map(function (element) {
  247. return element.getVisibleText();
  248. }));
  249. }
  250. }
  251. });
  252. },
  253. // This method had a two-argument version according to the WD.js docs but they inexplicably swapped the first
  254. // and second arguments so it probably never would have worked properly in Intern
  255. textPresent: function (searchText, element) {
  256. warn('Command#textPresent', 'Command#getVisibleText and a promise helper');
  257. function test(text) {
  258. return text.indexOf(searchText) > -1;
  259. }
  260. if (element) {
  261. return new this.constructor(this, function () {
  262. return element.getVisibleText().then(test);
  263. });
  264. }
  265. return this.getVisibleText().then(test);
  266. },
  267. type: deprecateElementSig('type'),
  268. keys: deprecate('keys', 'pressKeys'),
  269. getTagName: deprecateElementSig('getTagName'),
  270. clear: deprecateElementAndStandardSig('clear', 'clearValue'),
  271. isSelected: deprecateElementSig('isSelected'),
  272. isEnabled: deprecateElementSig('isEnabled'),
  273. enabled: deprecateElementAndStandardSig('enabled', 'isEnabled'),
  274. getAttribute: deprecateElementSig('getAttribute'),
  275. getValue: function (element) {
  276. if (element && element.elementId) {
  277. warn('Command#getValue(element)', 'Command#find then Command#getProperty(\'value\'), or ' +
  278. 'Command#find then Command#then(function (element) { ' +
  279. 'return element.getProperty(\'value\'); }');
  280. return new this.constructor(this, function () {
  281. return element.getProperty('value');
  282. });
  283. }
  284. warn('Command#getValue', 'Command#find then Command#getProperty(\'value\')');
  285. return this.getProperty('value');
  286. },
  287. equalsElement: function (element, other) {
  288. if (other && other.elementId) {
  289. warn('Command#equalsElement(element, other)', 'element.equals(other)');
  290. return new this.constructor(this, function () {
  291. return element.equals(other);
  292. });
  293. }
  294. warn('Command#equalsElement', 'Command#equals');
  295. return this.equals(element);
  296. },
  297. isDisplayed: deprecateElementSig('isDisplayed'),
  298. displayed: deprecateElementAndStandardSig('displayed', 'isDisplayed'),
  299. getLocation: deprecateElementAndStandardSig('getLocation', 'getPosition'),
  300. getLocationInView: function () {
  301. warn(
  302. 'Command#getLocationInView',
  303. 'Command#getPosition',
  304. 'This command is defined in the spec as internal and should never have been exposed to end users. ' +
  305. 'The returned value of this command will be the same as Command#getPosition, which may not match ' +
  306. 'prior behaviour.'
  307. );
  308. return this.getPosition.apply(this, arguments);
  309. },
  310. getSize: deprecateElementSig('getSize'),
  311. getComputedCss: deprecateElementAndStandardSig('getComputedCss', 'getComputedStyle'),
  312. getComputedCSS: deprecateElementAndStandardSig('getComputedCSS', 'getComputedStyle'),
  313. alertText: deprecate('alertText', 'getAlertText'),
  314. alertKeys: deprecate('alertKeys', 'typeInPrompt'),
  315. moveTo: deprecateElementAndStandardSig('moveTo', 'moveMouseTo'),
  316. click: function (button) {
  317. if (typeof button === 'number') {
  318. warn('Command#click(button)', 'Command#clickMouseButton(button)');
  319. return this.clickMouseButton(button);
  320. }
  321. return Command.prototype.click.apply(this, arguments);
  322. },
  323. buttonDown: deprecate('buttonDown', 'pressMouseButton'),
  324. buttonUp: deprecate('buttonUp', 'releaseMouseButton'),
  325. doubleclick: deprecate('doubleclick', 'doubleClick'),
  326. // TODO: There is no tap on elements
  327. tapElement: deprecateElementSig('tapElement', 'tap'),
  328. // TODO: There is no flick on elements
  329. flick: deprecate('flick', 'flickFinger'),
  330. setLocalStorageKey: deprecate('setLocalStorageKey', 'setLocalStorageItem'),
  331. getLocalStorageKey: deprecate('getLocalStorageKey', 'getLocalStorageItem'),
  332. removeLocalStorageKey: deprecate('removeLocalStorageKey', 'deleteLocalStorageItem'),
  333. log: deprecate('log', 'getLogsFor'),
  334. logTypes: deprecate('logTypes', 'getAvailableLogTypes'),
  335. newWindow: function (url, name) {
  336. warn('Command#newWindow', 'Command#execute');
  337. return this.execute('window.open(arguments[0], arguments[1]);', [ url, name ]);
  338. },
  339. windowName: function () {
  340. warn('Command#windowName', 'Command#execute');
  341. return this.execute('return window.name;');
  342. },
  343. setHTTPInactivityTimeout: function () {
  344. warn('Command#setHTTPInactivityTimeout');
  345. return this;
  346. },
  347. getPageIndex: function (element) {
  348. warn('Command#getPageIndex', null, 'This command is not part of any specification.');
  349. if (element && element.elementId) {
  350. return new this.constructor(this, function () {
  351. return element._get('pageIndex');
  352. });
  353. }
  354. return new this.constructor(this, function () {
  355. if (this.context.isSingle) {
  356. return this.context[0]._get('pageIndex');
  357. }
  358. else {
  359. return Promise.all(this.context.map(function (element) {
  360. return element._get('pageIndex');
  361. }));
  362. }
  363. });
  364. },
  365. uploadFile: function () {
  366. warn(
  367. 'Command#uploadFile',
  368. 'Command#type to type a file path into a file upload form control',
  369. 'This command is not part of any specification. This command is a no-op.'
  370. );
  371. return this;
  372. },
  373. waitForCondition: function (expression, timeout, pollInterval) {
  374. timeout = timeout || 1000;
  375. pollInterval = pollInterval || 100;
  376. warn('Command#waitForCondition', 'Command#executeAsync or leadfoot/helpers/pollUntil');
  377. return this.then(pollUntil('return eval(arguments[0]) ? true : null;', [ expression ], timeout, pollInterval));
  378. },
  379. waitForConditionInBrowser: function (expression, timeout, pollInterval) {
  380. timeout = timeout || 1000;
  381. pollInterval = pollInterval || 100;
  382. warn('Command#waitForConditionInBrowser', 'Command#executeAsync or leadfoot/helpers/pollUntil');
  383. return this.then(pollUntil('return eval(arguments[0]) ? true : null;', [ expression ], timeout, pollInterval));
  384. },
  385. sauceJobUpdate: function () {
  386. warn(
  387. 'Command#sauceJobUpdate',
  388. null,
  389. 'This command is not part of any specification. This command is a no-op.'
  390. );
  391. return this;
  392. },
  393. sauceJobStatus: function () {
  394. warn(
  395. 'Command#sauceJobStatus',
  396. null,
  397. 'This command is not part of any specification. This command is a no-op.'
  398. );
  399. return this;
  400. },
  401. reset: function () {
  402. warn(
  403. 'Command#reset',
  404. 'a previously stored Command instance'
  405. );
  406. return this;
  407. },
  408. waitForElement: function (using, value, timeout) {
  409. warn(
  410. 'Command#waitForElement',
  411. 'Command#setFindTimeout and Command#find',
  412. 'This command is implemented using implicit timeouts, which may not match the prior behaviour.'
  413. );
  414. // This is effectively what the WD.js code does, though there it's because the property is never validated,
  415. // so the end date becomes NaN; not an intentional design choice
  416. if (typeof timeout === 'undefined') {
  417. timeout = Infinity;
  418. }
  419. var command = this;
  420. return this.getFindTimeout().then(function (originalTimeout) {
  421. return command.setFindTimeout(timeout)
  422. .find(using, value)
  423. .then(function () {
  424. return command.setFindTimeout(originalTimeout).then(function () {
  425. return null;
  426. });
  427. }, function (error) {
  428. return command.setFindTimeout(originalTimeout).then(function () {
  429. throw error;
  430. });
  431. });
  432. });
  433. },
  434. waitForVisible: function (using, value, timeout) {
  435. warn(
  436. 'Command#waitForVisible',
  437. null,
  438. 'This command is partially implemented using implicit timeouts, which may not match the prior ' +
  439. 'behaviour.'
  440. );
  441. // This is effectively what the WD.js code does, though there it's because the property is never validated,
  442. // so the end date becomes NaN; not an intentional design choice
  443. if (typeof timeout === 'undefined') {
  444. timeout = Infinity;
  445. }
  446. var startTime = Date.now();
  447. var command = this;
  448. return this.getFindTimeout().then(function (originalTimeout) {
  449. return command.setFindTimeout(timeout)
  450. .find(using, value)
  451. .then(function (element) {
  452. return pollUntil(/* istanbul ignore next */ function (element) {
  453. return element.offsetWidth && element.offsetHeight ? true : null;
  454. }, [ element ], timeout - (startTime - Date.now())).call(this);
  455. }).then(function (isVisible) {
  456. return command.setFindTimeout(originalTimeout).then(function () {
  457. if (!isVisible) {
  458. throw new Error('Element didn\'t become visible');
  459. }
  460. });
  461. }, function (error) {
  462. return command.setFindTimeout(originalTimeout).then(function () {
  463. throw error;
  464. });
  465. });
  466. });
  467. },
  468. isVisible: function () {
  469. warn(
  470. 'Command#isVisible',
  471. 'Command#isDisplayed',
  472. 'This command is implemented using Command#isDisplayed, which may not match the prior behaviour.'
  473. );
  474. if (arguments.length === 2) {
  475. var using = arguments[0];
  476. var value = arguments[1];
  477. return this.find(using, value).isDisplayed().catch(function () {
  478. return false;
  479. });
  480. }
  481. else if (arguments.length === 1) {
  482. var element = arguments[0];
  483. if (element && element.elementId) {
  484. return new this.constructor(this, function () {
  485. return element.isDisplayed();
  486. });
  487. }
  488. }
  489. return new this.constructor(this, function () {
  490. if (this.context.isSingle) {
  491. return this.context[0].isDisplayed();
  492. }
  493. else {
  494. return Promise.all(this.context.map(function (element) {
  495. return element.isDisplayed();
  496. }));
  497. }
  498. });
  499. },
  500. otherwise: deprecate('otherwise', 'catch'),
  501. always: deprecate('always', 'finally'),
  502. wait: deprecate('wait', 'sleep')
  503. };
  504. strategies.suffixes.forEach(function (suffix, index) {
  505. function addStrategy(method, toMethod, suffix, wdSuffix, using) {
  506. methods[method + 'OrNull'] = function (value) {
  507. return this.elementOrNull(using, value);
  508. };
  509. methods[method + 'IfExists'] = function (value) {
  510. return this.elementIfExists(using, value);
  511. };
  512. methods['hasElementBy' + wdSuffix] = function (value) {
  513. return this.hasElement(using, value);
  514. };
  515. methods['waitForElementBy' + wdSuffix] = function (value, timeout) {
  516. return this.waitForElement(using, value, timeout);
  517. };
  518. methods['waitForVisibleBy' + wdSuffix] = function (value, timeout) {
  519. return this.waitForVisible(using, value, timeout);
  520. };
  521. }
  522. var wdSuffix = suffix === 'Xpath' ? 'XPath' : suffix;
  523. var method = 'elementBy' + wdSuffix;
  524. var toMethod = 'findBy' + suffix;
  525. var using = strategies[index];
  526. addStrategy(method, toMethod, suffix, wdSuffix, using);
  527. if (suffix === 'CssSelector') {
  528. addStrategy('elementByCss', toMethod, suffix, 'Css', using);
  529. }
  530. });
  531. module.exports = {
  532. /**
  533. * Applies the methods from compat to a {@link module:leadfoot/Command} prototype or instance.
  534. *
  535. * @param {module:leadfoot/Command} prototype A {@link module:leadfoot/Command} prototype or instance.
  536. */
  537. applyTo: function (prototype) {
  538. for (var key in methods) {
  539. Object.defineProperty(prototype, key, Object.getOwnPropertyDescriptor(methods, key));
  540. }
  541. }
  542. };