UNPKG

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