by.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. /**
  19. * @fileoverview Factory methods for the supported locator strategies.
  20. */
  21. /**
  22. * Short-hand expressions for the primary element locator strategies.
  23. * For example the following two statements are equivalent:
  24. *
  25. * var e1 = driver.findElement(By.id('foo'));
  26. * var e2 = driver.findElement({id: 'foo'});
  27. *
  28. * Care should be taken when using JavaScript minifiers (such as the
  29. * Closure compiler), as locator hashes will always be parsed using
  30. * the un-obfuscated properties listed.
  31. *
  32. * @typedef {(
  33. * {className: string}|
  34. * {css: string}|
  35. * {id: string}|
  36. * {js: string}|
  37. * {linkText: string}|
  38. * {name: string}|
  39. * {partialLinkText: string}|
  40. * {tagName: string}|
  41. * {xpath: string})}
  42. */
  43. var ByHash;
  44. /**
  45. * Error thrown if an invalid character is encountered while escaping a CSS
  46. * identifier.
  47. * @see https://drafts.csswg.org/cssom/#serialize-an-identifier
  48. */
  49. class InvalidCharacterError extends Error {
  50. constructor() {
  51. super();
  52. this.name = this.constructor.name;
  53. }
  54. }
  55. /**
  56. * Escapes a CSS string.
  57. * @param {string} css the string to escape.
  58. * @return {string} the escaped string.
  59. * @throws {TypeError} if the input value is not a string.
  60. * @throws {InvalidCharacterError} if the string contains an invalid character.
  61. * @see https://drafts.csswg.org/cssom/#serialize-an-identifier
  62. */
  63. function escapeCss(css) {
  64. if (typeof css !== 'string') {
  65. throw new TypeError('input must be a string');
  66. }
  67. let ret = '';
  68. const n = css.length;
  69. for (let i = 0; i < n; i++) {
  70. const c = css.charCodeAt(i);
  71. if (c == 0x0) {
  72. throw new InvalidCharacterError();
  73. }
  74. if ((c >= 0x0001 && c <= 0x001F)
  75. || c == 0x007F
  76. || (i == 0 && c >= 0x0030 && c <= 0x0039)
  77. || (i == 1 && c >= 0x0030 && c <= 0x0039
  78. && css.charCodeAt(0) == 0x002D)) {
  79. ret += '\\' + c.toString(16) + ' ';
  80. continue;
  81. }
  82. if (i == 0 && c == 0x002D && n == 1) {
  83. ret += '\\' + css.charAt(i);
  84. continue;
  85. }
  86. if (c >= 0x0080
  87. || c == 0x002D // -
  88. || c == 0x005F // _
  89. || (c >= 0x0030 && c <= 0x0039) // [0-9]
  90. || (c >= 0x0041 && c <= 0x005A) // [A-Z]
  91. || (c >= 0x0061 && c <= 0x007A)) { // [a-z]
  92. ret += css.charAt(i);
  93. continue;
  94. }
  95. ret += '\\' + css.charAt(i);
  96. }
  97. return ret;
  98. }
  99. /**
  100. * Describes a mechanism for locating an element on the page.
  101. * @final
  102. */
  103. class By {
  104. /**
  105. * @param {string} using the name of the location strategy to use.
  106. * @param {string} value the value to search for.
  107. */
  108. constructor(using, value) {
  109. /** @type {string} */
  110. this.using = using;
  111. /** @type {string} */
  112. this.value = value;
  113. }
  114. /**
  115. * Locates elements that have a specific class name.
  116. *
  117. * @param {string} name The class name to search for.
  118. * @return {!By} The new locator.
  119. * @see http://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
  120. * @see http://www.w3.org/TR/CSS2/selector.html#class-html
  121. */
  122. static className(name) {
  123. let names = name.split(/\s+/g)
  124. .filter(s => s.length > 0)
  125. .map(s => escapeCss(s));
  126. return By.css('.' + names.join('.'));
  127. }
  128. /**
  129. * Locates elements using a CSS selector.
  130. *
  131. * @param {string} selector The CSS selector to use.
  132. * @return {!By} The new locator.
  133. * @see http://www.w3.org/TR/CSS2/selector.html
  134. */
  135. static css(selector) {
  136. return new By('css selector', selector);
  137. }
  138. /**
  139. * Locates elements by the ID attribute. This locator uses the CSS selector
  140. * `*[id="$ID"]`, _not_ `document.getElementById`.
  141. *
  142. * @param {string} id The ID to search for.
  143. * @return {!By} The new locator.
  144. */
  145. static id(id) {
  146. return By.css('*[id="' + escapeCss(id) + '"]');
  147. }
  148. /**
  149. * Locates link elements whose
  150. * {@linkplain webdriver.WebElement#getText visible text} matches the given
  151. * string.
  152. *
  153. * @param {string} text The link text to search for.
  154. * @return {!By} The new locator.
  155. */
  156. static linkText(text) {
  157. return new By('link text', text);
  158. }
  159. /**
  160. * Locates an elements by evaluating a
  161. * {@linkplain webdriver.WebDriver#executeScript JavaScript expression}.
  162. * The result of this expression must be an element or list of elements.
  163. *
  164. * @param {!(string|Function)} script The script to execute.
  165. * @param {...*} var_args The arguments to pass to the script.
  166. * @return {function(!./webdriver.WebDriver): !./promise.Promise}
  167. * A new JavaScript-based locator function.
  168. */
  169. static js(script, var_args) {
  170. let args = Array.prototype.slice.call(arguments, 0);
  171. return function(driver) {
  172. return driver.executeScript.apply(driver, args);
  173. };
  174. }
  175. /**
  176. * Locates elements whose `name` attribute has the given value.
  177. *
  178. * @param {string} name The name attribute to search for.
  179. * @return {!By} The new locator.
  180. */
  181. static name(name) {
  182. return By.css('*[name="' + escapeCss(name) + '"]');
  183. }
  184. /**
  185. * Locates link elements whose
  186. * {@linkplain webdriver.WebElement#getText visible text} contains the given
  187. * substring.
  188. *
  189. * @param {string} text The substring to check for in a link's visible text.
  190. * @return {!By} The new locator.
  191. */
  192. static partialLinkText(text) {
  193. return new By('partial link text', text);
  194. }
  195. /**
  196. * Locates elements with a given tag name.
  197. *
  198. * @param {string} name The tag name to search for.
  199. * @return {!By} The new locator.
  200. * @deprecated Use {@link By.css() By.css(tagName)} instead.
  201. */
  202. static tagName(name) {
  203. return By.css(name);
  204. }
  205. /**
  206. * Locates elements matching a XPath selector. Care should be taken when
  207. * using an XPath selector with a {@link webdriver.WebElement} as WebDriver
  208. * will respect the context in the specified in the selector. For example,
  209. * given the selector `//div`, WebDriver will search from the document root
  210. * regardless of whether the locator was used with a WebElement.
  211. *
  212. * @param {string} xpath The XPath selector to use.
  213. * @return {!By} The new locator.
  214. * @see http://www.w3.org/TR/xpath/
  215. */
  216. static xpath(xpath) {
  217. return new By('xpath', xpath);
  218. }
  219. /** @override */
  220. toString() {
  221. // The static By.name() overrides this.constructor.name. Shame...
  222. return `By(${this.using}, ${this.value})`;
  223. }
  224. }
  225. /**
  226. * Checks if a value is a valid locator.
  227. * @param {!(By|Function|ByHash)} locator The value to check.
  228. * @return {!(By|Function)} The valid locator.
  229. * @throws {TypeError} If the given value does not define a valid locator
  230. * strategy.
  231. */
  232. function check(locator) {
  233. if (locator instanceof By || typeof locator === 'function') {
  234. return locator;
  235. }
  236. if (locator
  237. && typeof locator === 'object'
  238. && typeof locator.using === 'string'
  239. && typeof locator.value === 'string') {
  240. return new By(locator.using, locator.value);
  241. }
  242. for (let key in locator) {
  243. if (locator.hasOwnProperty(key) && By.hasOwnProperty(key)) {
  244. return By[key](locator[key]);
  245. }
  246. }
  247. throw new TypeError('Invalid locator');
  248. }
  249. // PUBLIC API
  250. module.exports = {
  251. By: By,
  252. checkedLocator: check,
  253. };