until.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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. /**
  18. * @fileoverview Defines common conditions for use with
  19. * {@link webdriver.WebDriver#wait WebDriver wait}.
  20. *
  21. * Sample usage:
  22. *
  23. * driver.get('http://www.google.com/ncr');
  24. *
  25. * var query = driver.wait(until.elementLocated(By.name('q')));
  26. * query.sendKeys('webdriver\n');
  27. *
  28. * driver.wait(until.titleIs('webdriver - Google Search'));
  29. *
  30. * To define a custom condition, simply call WebDriver.wait with a function
  31. * that will eventually return a truthy-value (neither null, undefined, false,
  32. * 0, or the empty string):
  33. *
  34. * driver.wait(function() {
  35. * return driver.getTitle().then(function(title) {
  36. * return title === 'webdriver - Google Search';
  37. * });
  38. * }, 1000);
  39. */
  40. 'use strict';
  41. const by = require('./by');
  42. const By = require('./by').By;
  43. const error = require('./error');
  44. const webdriver = require('./webdriver'),
  45. Condition = webdriver.Condition,
  46. WebElementCondition = webdriver.WebElementCondition;
  47. /**
  48. * Creates a condition that will wait until the input driver is able to switch
  49. * to the designated frame. The target frame may be specified as
  50. *
  51. * 1. a numeric index into
  52. * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames)
  53. * for the currently selected frame.
  54. * 2. a {@link ./webdriver.WebElement}, which must reference a FRAME or IFRAME
  55. * element on the current page.
  56. * 3. a locator which may be used to first locate a FRAME or IFRAME on the
  57. * current page before attempting to switch to it.
  58. *
  59. * Upon successful resolution of this condition, the driver will be left
  60. * focused on the new frame.
  61. *
  62. * @param {!(number|./webdriver.WebElement|By|
  63. * function(!./webdriver.WebDriver): !./webdriver.WebElement)} frame
  64. * The frame identifier.
  65. * @return {!Condition<boolean>} A new condition.
  66. */
  67. exports.ableToSwitchToFrame = function ableToSwitchToFrame(frame) {
  68. var condition;
  69. if (typeof frame === 'number' || frame instanceof webdriver.WebElement) {
  70. condition = driver => attemptToSwitchFrames(driver, frame);
  71. } else {
  72. condition = function(driver) {
  73. let locator = /** @type {!(By|Function)} */(frame);
  74. return driver.findElements(locator).then(function(els) {
  75. if (els.length) {
  76. return attemptToSwitchFrames(driver, els[0]);
  77. }
  78. });
  79. };
  80. }
  81. return new Condition('to be able to switch to frame', condition);
  82. function attemptToSwitchFrames(driver, frame) {
  83. return driver.switchTo().frame(frame).then(
  84. function() { return true; },
  85. function(e) {
  86. if (!(e instanceof error.NoSuchFrameError)) {
  87. throw e;
  88. }
  89. });
  90. }
  91. };
  92. /**
  93. * Creates a condition that waits for an alert to be opened. Upon success, the
  94. * returned promise will be fulfilled with the handle for the opened alert.
  95. *
  96. * @return {!Condition<!./webdriver.Alert>} The new condition.
  97. */
  98. exports.alertIsPresent = function alertIsPresent() {
  99. return new Condition('for alert to be present', function(driver) {
  100. return driver.switchTo().alert().catch(function(e) {
  101. if (!(e instanceof error.NoSuchAlertError
  102. // XXX: Workaround for GeckoDriver error `TypeError: can't convert null
  103. // to object`. For more details, see
  104. // https://github.com/SeleniumHQ/selenium/pull/2137
  105. || (e instanceof error.WebDriverError
  106. && e.message === `can't convert null to object`)
  107. )) {
  108. throw e;
  109. }
  110. });
  111. });
  112. };
  113. /**
  114. * Creates a condition that will wait for the current page's title to match the
  115. * given value.
  116. *
  117. * @param {string} title The expected page title.
  118. * @return {!Condition<boolean>} The new condition.
  119. */
  120. exports.titleIs = function titleIs(title) {
  121. return new Condition(
  122. 'for title to be ' + JSON.stringify(title),
  123. function(driver) {
  124. return driver.getTitle().then(function(t) {
  125. return t === title;
  126. });
  127. });
  128. };
  129. /**
  130. * Creates a condition that will wait for the current page's title to contain
  131. * the given substring.
  132. *
  133. * @param {string} substr The substring that should be present in the page
  134. * title.
  135. * @return {!Condition<boolean>} The new condition.
  136. */
  137. exports.titleContains = function titleContains(substr) {
  138. return new Condition(
  139. 'for title to contain ' + JSON.stringify(substr),
  140. function(driver) {
  141. return driver.getTitle().then(function(title) {
  142. return title.indexOf(substr) !== -1;
  143. });
  144. });
  145. };
  146. /**
  147. * Creates a condition that will wait for the current page's title to match the
  148. * given regular expression.
  149. *
  150. * @param {!RegExp} regex The regular expression to test against.
  151. * @return {!Condition<boolean>} The new condition.
  152. */
  153. exports.titleMatches = function titleMatches(regex) {
  154. return new Condition('for title to match ' + regex, function(driver) {
  155. return driver.getTitle().then(function(title) {
  156. return regex.test(title);
  157. });
  158. });
  159. };
  160. /**
  161. * Creates a condition that will wait for the current page's url to match the
  162. * given value.
  163. *
  164. * @param {string} url The expected page url.
  165. * @return {!Condition<boolean>} The new condition.
  166. */
  167. exports.urlIs = function urlIs(url) {
  168. return new Condition(
  169. 'for URL to be ' + JSON.stringify(url),
  170. function(driver) {
  171. return driver.getCurrentUrl().then(function(u) {
  172. return u === url;
  173. });
  174. });
  175. };
  176. /**
  177. * Creates a condition that will wait for the current page's url to contain
  178. * the given substring.
  179. *
  180. * @param {string} substrUrl The substring that should be present in the current
  181. * URL.
  182. * @return {!Condition<boolean>} The new condition.
  183. */
  184. exports.urlContains = function urlContains(substrUrl) {
  185. return new Condition(
  186. 'for URL to contain ' + JSON.stringify(substrUrl),
  187. function(driver) {
  188. return driver.getCurrentUrl().then(function(url) {
  189. return url.indexOf(substrUrl) !== -1;
  190. });
  191. });
  192. };
  193. /**
  194. * Creates a condition that will wait for the current page's url to match the
  195. * given regular expression.
  196. *
  197. * @param {!RegExp} regex The regular expression to test against.
  198. * @return {!Condition<boolean>} The new condition.
  199. */
  200. exports.urlMatches = function urlMatches(regex) {
  201. return new Condition('for URL to match ' + regex, function(driver) {
  202. return driver.getCurrentUrl().then(function(url) {
  203. return regex.test(url);
  204. });
  205. });
  206. };
  207. /**
  208. * Creates a condition that will loop until an element is
  209. * {@link ./webdriver.WebDriver#findElement found} with the given locator.
  210. *
  211. * @param {!(By|Function)} locator The locator to use.
  212. * @return {!WebElementCondition} The new condition.
  213. */
  214. exports.elementLocated = function elementLocated(locator) {
  215. locator = by.checkedLocator(locator);
  216. let locatorStr =
  217. typeof locator === 'function' ? 'by function()' : locator + '';
  218. return new WebElementCondition('for element to be located ' + locatorStr,
  219. function(driver) {
  220. return driver.findElements(locator).then(function(elements) {
  221. return elements[0];
  222. });
  223. });
  224. };
  225. /**
  226. * Creates a condition that will loop until at least one element is
  227. * {@link ./webdriver.WebDriver#findElement found} with the given locator.
  228. *
  229. * @param {!(By|Function)} locator The locator to use.
  230. * @return {!Condition<!Array<!./webdriver.WebElement>>} The new
  231. * condition.
  232. */
  233. exports.elementsLocated = function elementsLocated(locator) {
  234. locator = by.checkedLocator(locator);
  235. let locatorStr =
  236. typeof locator === 'function' ? 'by function()' : locator + '';
  237. return new Condition(
  238. 'for at least one element to be located ' + locatorStr,
  239. function(driver) {
  240. return driver.findElements(locator).then(function(elements) {
  241. return elements.length > 0 ? elements : null;
  242. });
  243. });
  244. };
  245. /**
  246. * Creates a condition that will wait for the given element to become stale. An
  247. * element is considered stale once it is removed from the DOM, or a new page
  248. * has loaded.
  249. *
  250. * @param {!./webdriver.WebElement} element The element that should become stale.
  251. * @return {!Condition<boolean>} The new condition.
  252. */
  253. exports.stalenessOf = function stalenessOf(element) {
  254. return new Condition('element to become stale', function() {
  255. return element.getTagName().then(
  256. function() { return false; },
  257. function(e) {
  258. if (e instanceof error.StaleElementReferenceError) {
  259. return true;
  260. }
  261. throw e;
  262. });
  263. });
  264. };
  265. /**
  266. * Creates a condition that will wait for the given element to become visible.
  267. *
  268. * @param {!./webdriver.WebElement} element The element to test.
  269. * @return {!WebElementCondition} The new condition.
  270. * @see ./webdriver.WebDriver#isDisplayed
  271. */
  272. exports.elementIsVisible = function elementIsVisible(element) {
  273. return new WebElementCondition('until element is visible', function() {
  274. return element.isDisplayed().then(v => v ? element : null);
  275. });
  276. };
  277. /**
  278. * Creates a condition that will wait for the given element to be in the DOM,
  279. * yet not visible to the user.
  280. *
  281. * @param {!./webdriver.WebElement} element The element to test.
  282. * @return {!WebElementCondition} The new condition.
  283. * @see ./webdriver.WebDriver#isDisplayed
  284. */
  285. exports.elementIsNotVisible = function elementIsNotVisible(element) {
  286. return new WebElementCondition('until element is not visible', function() {
  287. return element.isDisplayed().then(v => v ? null : element);
  288. });
  289. };
  290. /**
  291. * Creates a condition that will wait for the given element to be enabled.
  292. *
  293. * @param {!./webdriver.WebElement} element The element to test.
  294. * @return {!WebElementCondition} The new condition.
  295. * @see webdriver.WebDriver#isEnabled
  296. */
  297. exports.elementIsEnabled = function elementIsEnabled(element) {
  298. return new WebElementCondition('until element is enabled', function() {
  299. return element.isEnabled().then(v => v ? element : null);
  300. });
  301. };
  302. /**
  303. * Creates a condition that will wait for the given element to be disabled.
  304. *
  305. * @param {!./webdriver.WebElement} element The element to test.
  306. * @return {!WebElementCondition} The new condition.
  307. * @see webdriver.WebDriver#isEnabled
  308. */
  309. exports.elementIsDisabled = function elementIsDisabled(element) {
  310. return new WebElementCondition('until element is disabled', function() {
  311. return element.isEnabled().then(v => v ? null : element);
  312. });
  313. };
  314. /**
  315. * Creates a condition that will wait for the given element to be selected.
  316. * @param {!./webdriver.WebElement} element The element to test.
  317. * @return {!WebElementCondition} The new condition.
  318. * @see webdriver.WebDriver#isSelected
  319. */
  320. exports.elementIsSelected = function elementIsSelected(element) {
  321. return new WebElementCondition('until element is selected', function() {
  322. return element.isSelected().then(v => v ? element : null);
  323. });
  324. };
  325. /**
  326. * Creates a condition that will wait for the given element to be deselected.
  327. *
  328. * @param {!./webdriver.WebElement} element The element to test.
  329. * @return {!WebElementCondition} The new condition.
  330. * @see webdriver.WebDriver#isSelected
  331. */
  332. exports.elementIsNotSelected = function elementIsNotSelected(element) {
  333. return new WebElementCondition('until element is not selected', function() {
  334. return element.isSelected().then(v => v ? null : element);
  335. });
  336. };
  337. /**
  338. * Creates a condition that will wait for the given element's
  339. * {@link webdriver.WebDriver#getText visible text} to match the given
  340. * {@code text} exactly.
  341. *
  342. * @param {!./webdriver.WebElement} element The element to test.
  343. * @param {string} text The expected text.
  344. * @return {!WebElementCondition} The new condition.
  345. * @see webdriver.WebDriver#getText
  346. */
  347. exports.elementTextIs = function elementTextIs(element, text) {
  348. return new WebElementCondition('until element text is', function() {
  349. return element.getText().then(t => t === text ? element : null);
  350. });
  351. };
  352. /**
  353. * Creates a condition that will wait for the given element's
  354. * {@link webdriver.WebDriver#getText visible text} to contain the given
  355. * substring.
  356. *
  357. * @param {!./webdriver.WebElement} element The element to test.
  358. * @param {string} substr The substring to search for.
  359. * @return {!WebElementCondition} The new condition.
  360. * @see webdriver.WebDriver#getText
  361. */
  362. exports.elementTextContains = function elementTextContains(element, substr) {
  363. return new WebElementCondition('until element text contains', function() {
  364. return element.getText()
  365. .then(t => t.indexOf(substr) != -1 ? element : null);
  366. });
  367. };
  368. /**
  369. * Creates a condition that will wait for the given element's
  370. * {@link webdriver.WebDriver#getText visible text} to match a regular
  371. * expression.
  372. *
  373. * @param {!./webdriver.WebElement} element The element to test.
  374. * @param {!RegExp} regex The regular expression to test against.
  375. * @return {!WebElementCondition} The new condition.
  376. * @see webdriver.WebDriver#getText
  377. */
  378. exports.elementTextMatches = function elementTextMatches(element, regex) {
  379. return new WebElementCondition('until element text matches', function() {
  380. return element.getText().then(t => regex.test(t) ? element : null);
  381. });
  382. };