until_test.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. // Licensed to the Software Freedom Conservancy (SFC) under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. The SFC licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing,
  12. // software distributed under the License is distributed on an
  13. // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. // KIND, either express or implied. See the License for the
  15. // specific language governing permissions and limitations
  16. // under the License.
  17. 'use strict';
  18. const assert = require('assert');
  19. const By = require('../../lib/by').By;
  20. const CommandName = require('../../lib/command').Name;
  21. const error = require('../../lib/error');
  22. const promise = require('../../lib/promise');
  23. const until = require('../../lib/until');
  24. const webdriver = require('../../lib/webdriver'),
  25. WebElement = webdriver.WebElement;
  26. describe('until', function() {
  27. let driver, executor;
  28. class TestExecutor {
  29. constructor() {
  30. this.handlers_ = {};
  31. }
  32. on(cmd, handler) {
  33. this.handlers_[cmd] = handler;
  34. return this;
  35. }
  36. execute(cmd) {
  37. let self = this;
  38. return Promise.resolve().then(function() {
  39. if (!self.handlers_[cmd.getName()]) {
  40. throw new error.UnknownCommandError(cmd.getName());
  41. }
  42. return self.handlers_[cmd.getName()](cmd);
  43. });
  44. }
  45. }
  46. function fail(opt_msg) {
  47. throw new assert.AssertionError({message: opt_msg});
  48. }
  49. beforeEach(function setUp() {
  50. executor = new TestExecutor();
  51. driver = new webdriver.WebDriver('session-id', executor);
  52. });
  53. describe('ableToSwitchToFrame', function() {
  54. it('failsFastForNonSwitchErrors', function() {
  55. let e = Error('boom');
  56. executor.on(CommandName.SWITCH_TO_FRAME, function() {
  57. throw e;
  58. });
  59. return driver.wait(until.ableToSwitchToFrame(0), 100)
  60. .then(fail, (e2) => assert.strictEqual(e2, e));
  61. });
  62. const ELEMENT_ID = 'some-element-id';
  63. const ELEMENT_INDEX = 1234;
  64. function onSwitchFrame(expectedId) {
  65. if (typeof expectedId === 'string') {
  66. expectedId = WebElement.buildId(expectedId);
  67. } else {
  68. assert.equal(typeof expectedId, 'number', 'must be string or number');
  69. }
  70. return cmd => {
  71. assert.deepEqual(
  72. cmd.getParameter('id'), expectedId, 'frame ID not specified');
  73. return true;
  74. };
  75. }
  76. it('byIndex', function() {
  77. executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_INDEX));
  78. return driver.wait(until.ableToSwitchToFrame(ELEMENT_INDEX), 100);
  79. });
  80. it('byWebElement', function() {
  81. executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID));
  82. var el = new webdriver.WebElement(driver, ELEMENT_ID);
  83. return driver.wait(until.ableToSwitchToFrame(el), 100);
  84. });
  85. it('byWebElementPromise', function() {
  86. executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID));
  87. var el = new webdriver.WebElementPromise(driver,
  88. Promise.resolve(new webdriver.WebElement(driver, ELEMENT_ID)));
  89. return driver.wait(until.ableToSwitchToFrame(el), 100);
  90. });
  91. it('byLocator', function() {
  92. executor.on(CommandName.FIND_ELEMENTS, () => [WebElement.buildId(ELEMENT_ID)]);
  93. executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID));
  94. return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 100);
  95. });
  96. it('byLocator_elementNotInitiallyFound', function() {
  97. let foundResponses = [[], [], [WebElement.buildId(ELEMENT_ID)]];
  98. executor.on(CommandName.FIND_ELEMENTS, () => foundResponses.shift());
  99. executor.on(CommandName.SWITCH_TO_FRAME, onSwitchFrame(ELEMENT_ID));
  100. return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 2000)
  101. .then(() => assert.deepEqual(foundResponses, []));
  102. });
  103. it('timesOutIfNeverAbletoSwitchFrames', function() {
  104. var count = 0;
  105. executor.on(CommandName.SWITCH_TO_FRAME, function() {
  106. count += 1;
  107. throw new error.NoSuchFrameError;
  108. });
  109. return driver.wait(until.ableToSwitchToFrame(0), 100)
  110. .then(fail, function(e) {
  111. assert.ok(count > 0);
  112. assert.ok(
  113. e.message.startsWith('Waiting to be able to switch to frame'),
  114. 'Wrong message: ' + e.message);
  115. });
  116. });
  117. });
  118. describe('alertIsPresent', function() {
  119. it('failsFastForNonAlertSwitchErrors', function() {
  120. return driver.wait(until.alertIsPresent(), 100).then(fail, function(e) {
  121. assert.ok(e instanceof error.UnknownCommandError);
  122. assert.equal(e.message, CommandName.GET_ALERT_TEXT);
  123. });
  124. });
  125. it('waitsForAlert', function() {
  126. var count = 0;
  127. executor.on(CommandName.GET_ALERT_TEXT, function() {
  128. if (count++ < 3) {
  129. throw new error.NoSuchAlertError;
  130. } else {
  131. return true;
  132. }
  133. }).on(CommandName.DISMISS_ALERT, () => true);
  134. return driver.wait(until.alertIsPresent(), 1000).then(function(alert) {
  135. assert.equal(count, 4);
  136. return alert.dismiss();
  137. });
  138. });
  139. // TODO: Remove once GeckoDriver doesn't throw this unwanted error.
  140. // See https://github.com/SeleniumHQ/selenium/pull/2137
  141. describe('workaround for GeckoDriver', function() {
  142. it('doesNotFailWhenCannotConvertNullToObject', function() {
  143. var count = 0;
  144. executor.on(CommandName.GET_ALERT_TEXT, function() {
  145. if (count++ < 3) {
  146. throw new error.WebDriverError(`can't convert null to object`);
  147. } else {
  148. return true;
  149. }
  150. }).on(CommandName.DISMISS_ALERT, () => true);
  151. return driver.wait(until.alertIsPresent(), 1000).then(function(alert) {
  152. assert.equal(count, 4);
  153. return alert.dismiss();
  154. });
  155. });
  156. it('keepsRaisingRegularWebdriverError', function() {
  157. var webDriverError = new error.WebDriverError;
  158. executor.on(CommandName.GET_ALERT_TEXT, function() {
  159. throw webDriverError;
  160. });
  161. return driver.wait(until.alertIsPresent(), 1000).then(function() {
  162. throw new Error('driver did not fail against WebDriverError');
  163. }, function(error) {
  164. assert.equal(error, webDriverError);
  165. });
  166. })
  167. });
  168. });
  169. it('testUntilTitleIs', function() {
  170. var titles = ['foo', 'bar', 'baz'];
  171. executor.on(CommandName.GET_TITLE, () => titles.shift());
  172. return driver.wait(until.titleIs('bar'), 3000).then(function() {
  173. assert.deepStrictEqual(titles, ['baz']);
  174. });
  175. });
  176. it('testUntilTitleContains', function() {
  177. var titles = ['foo', 'froogle', 'google'];
  178. executor.on(CommandName.GET_TITLE, () => titles.shift());
  179. return driver.wait(until.titleContains('oogle'), 3000).then(function() {
  180. assert.deepStrictEqual(titles, ['google']);
  181. });
  182. });
  183. it('testUntilTitleMatches', function() {
  184. var titles = ['foo', 'froogle', 'aaaabc', 'aabbbc', 'google'];
  185. executor.on(CommandName.GET_TITLE, () => titles.shift());
  186. return driver.wait(until.titleMatches(/^a{2,3}b+c$/), 3000)
  187. .then(function() {
  188. assert.deepStrictEqual(titles, ['google']);
  189. });
  190. });
  191. it('testUntilUrlIs', function() {
  192. var urls = ['http://www.foo.com', 'https://boo.com', 'http://docs.yes.com'];
  193. executor.on(CommandName.GET_CURRENT_URL, () => urls.shift());
  194. return driver.wait(until.urlIs('https://boo.com'), 3000).then(function() {
  195. assert.deepStrictEqual(urls, ['http://docs.yes.com']);
  196. });
  197. });
  198. it('testUntilUrlContains', function() {
  199. var urls =
  200. ['http://foo.com', 'https://groups.froogle.com', 'http://google.com'];
  201. executor.on(CommandName.GET_CURRENT_URL, () => urls.shift());
  202. return driver.wait(until.urlContains('oogle.com'), 3000).then(function() {
  203. assert.deepStrictEqual(urls, ['http://google.com']);
  204. });
  205. });
  206. it('testUntilUrlMatches', function() {
  207. var urls = ['foo', 'froogle', 'aaaabc', 'aabbbc', 'google'];
  208. executor.on(CommandName.GET_CURRENT_URL, () => urls.shift());
  209. return driver.wait(until.urlMatches(/^a{2,3}b+c$/), 3000)
  210. .then(function() {
  211. assert.deepStrictEqual(urls, ['google']);
  212. });
  213. });
  214. it('testUntilElementLocated', function() {
  215. var responses = [
  216. [],
  217. [WebElement.buildId('abc123'), WebElement.buildId('foo')],
  218. ['end']
  219. ];
  220. executor.on(CommandName.FIND_ELEMENTS, () => responses.shift());
  221. let element = driver.wait(until.elementLocated(By.id('quux')), 2000);
  222. assert.ok(element instanceof webdriver.WebElementPromise);
  223. return element.getId().then(function(id) {
  224. assert.deepStrictEqual(responses, [['end']]);
  225. assert.equal(id, 'abc123');
  226. });
  227. });
  228. describe('untilElementLocated, elementNeverFound', function() {
  229. function runNoElementFoundTest(locator, locatorStr) {
  230. executor.on(CommandName.FIND_ELEMENTS, () => []);
  231. function expectedFailure() {
  232. fail('expected condition to timeout');
  233. }
  234. return driver.wait(until.elementLocated(locator), 100)
  235. .then(expectedFailure, function(error) {
  236. var expected = 'Waiting for element to be located ' + locatorStr;
  237. var lines = error.message.split(/\n/, 2);
  238. assert.equal(lines[0], expected);
  239. let regex = /^Wait timed out after \d+ms$/;
  240. assert.ok(regex.test(lines[1]),
  241. `Lines <${lines[1]}> does not match ${regex}`);
  242. });
  243. }
  244. it('byLocator', function() {
  245. return runNoElementFoundTest(
  246. By.id('quux'), 'By(css selector, *[id="quux"])');
  247. });
  248. it('byHash', function() {
  249. return runNoElementFoundTest(
  250. {id: 'quux'}, 'By(css selector, *[id="quux"])');
  251. });
  252. it('byFunction', function() {
  253. return runNoElementFoundTest(function() {}, 'by function()');
  254. });
  255. });
  256. it('testUntilElementsLocated', function() {
  257. var responses = [
  258. [],
  259. [WebElement.buildId('abc123'), WebElement.buildId('foo')],
  260. ['end']
  261. ];
  262. executor.on(CommandName.FIND_ELEMENTS, () => responses.shift());
  263. return driver.wait(until.elementsLocated(By.id('quux')), 2000)
  264. .then(function(els) {
  265. return Promise.all(els.map(e => e.getId()));
  266. }).then(function(ids) {
  267. assert.deepStrictEqual(responses, [['end']]);
  268. assert.equal(ids.length, 2);
  269. assert.equal(ids[0], 'abc123');
  270. assert.equal(ids[1], 'foo');
  271. });
  272. });
  273. describe('untilElementsLocated, noElementsFound', function() {
  274. function runNoElementsFoundTest(locator, locatorStr) {
  275. executor.on(CommandName.FIND_ELEMENTS, () => []);
  276. function expectedFailure() {
  277. fail('expected condition to timeout');
  278. }
  279. return driver.wait(until.elementsLocated(locator), 100)
  280. .then(expectedFailure, function(error) {
  281. var expected =
  282. 'Waiting for at least one element to be located ' + locatorStr;
  283. var lines = error.message.split(/\n/, 2);
  284. assert.equal(lines[0], expected);
  285. let regex = /^Wait timed out after \d+ms$/;
  286. assert.ok(regex.test(lines[1]),
  287. `Lines <${lines[1]}> does not match ${regex}`);
  288. });
  289. }
  290. it('byLocator', function() {
  291. return runNoElementsFoundTest(
  292. By.id('quux'), 'By(css selector, *[id="quux"])');
  293. });
  294. it('byHash', function() {
  295. return runNoElementsFoundTest(
  296. {id: 'quux'}, 'By(css selector, *[id="quux"])');
  297. });
  298. it('byFunction', function() {
  299. return runNoElementsFoundTest(function() {}, 'by function()');
  300. });
  301. });
  302. it('testUntilStalenessOf', function() {
  303. let count = 0;
  304. executor.on(CommandName.GET_ELEMENT_TAG_NAME, function() {
  305. while (count < 3) {
  306. count += 1;
  307. return 'body';
  308. }
  309. throw new error.StaleElementReferenceError('now stale');
  310. });
  311. var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'});
  312. return driver.wait(until.stalenessOf(el), 2000)
  313. .then(() => assert.equal(count, 3));
  314. });
  315. describe('element state conditions', function() {
  316. function runElementStateTest(predicate, command, responses, var_args) {
  317. let original = new webdriver.WebElement(driver, 'foo');
  318. let predicateArgs = [original];
  319. if (arguments.length > 3) {
  320. predicateArgs = predicateArgs.concat(arguments[1]);
  321. command = arguments[2];
  322. responses = arguments[3];
  323. }
  324. assert.ok(responses.length > 1);
  325. responses = responses.concat(['end']);
  326. executor.on(command, () => responses.shift());
  327. let result = driver.wait(predicate.apply(null, predicateArgs), 2000);
  328. assert.ok(result instanceof webdriver.WebElementPromise);
  329. return result.then(function(value) {
  330. assert.ok(value instanceof webdriver.WebElement);
  331. assert.ok(!(value instanceof webdriver.WebElementPromise));
  332. return value.getId();
  333. }).then(function(id) {
  334. assert.equal('foo', id);
  335. assert.deepStrictEqual(responses, ['end']);
  336. });
  337. }
  338. it('elementIsVisible', function() {
  339. return runElementStateTest(
  340. until.elementIsVisible,
  341. CommandName.IS_ELEMENT_DISPLAYED, [false, false, true]);
  342. });
  343. it('elementIsNotVisible', function() {
  344. return runElementStateTest(
  345. until.elementIsNotVisible,
  346. CommandName.IS_ELEMENT_DISPLAYED, [true, true, false]);
  347. });
  348. it('elementIsEnabled', function() {
  349. return runElementStateTest(
  350. until.elementIsEnabled,
  351. CommandName.IS_ELEMENT_ENABLED, [false, false, true]);
  352. });
  353. it('elementIsDisabled', function() {
  354. return runElementStateTest(
  355. until.elementIsDisabled,
  356. CommandName.IS_ELEMENT_ENABLED, [true, true, false]);
  357. });
  358. it('elementIsSelected', function() {
  359. return runElementStateTest(
  360. until.elementIsSelected,
  361. CommandName.IS_ELEMENT_SELECTED, [false, false, true]);
  362. });
  363. it('elementIsNotSelected', function() {
  364. return runElementStateTest(
  365. until.elementIsNotSelected,
  366. CommandName.IS_ELEMENT_SELECTED, [true, true, false]);
  367. });
  368. it('elementTextIs', function() {
  369. return runElementStateTest(
  370. until.elementTextIs, 'foobar',
  371. CommandName.GET_ELEMENT_TEXT,
  372. ['foo', 'fooba', 'foobar']);
  373. });
  374. it('elementTextContains', function() {
  375. return runElementStateTest(
  376. until.elementTextContains, 'bar',
  377. CommandName.GET_ELEMENT_TEXT,
  378. ['foo', 'foobaz', 'foobarbaz']);
  379. });
  380. it('elementTextMatches', function() {
  381. return runElementStateTest(
  382. until.elementTextMatches, /fo+bar{3}/,
  383. CommandName.GET_ELEMENT_TEXT,
  384. ['foo', 'foobar', 'fooobarrr']);
  385. });
  386. });
  387. describe('WebElementCondition', function() {
  388. it('fails if wait completes with a non-WebElement value', function() {
  389. let result = driver.wait(
  390. new webdriver.WebElementCondition('testing', () => 123), 1000);
  391. return result.then(
  392. () => assert.fail('expected to fail'),
  393. function(e) {
  394. assert.ok(e instanceof TypeError);
  395. assert.equal(
  396. 'WebElementCondition did not resolve to a WebElement: '
  397. + '[object Number]',
  398. e.message);
  399. });
  400. });
  401. });
  402. });