123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- // Licensed to the Software Freedom Conservancy (SFC) under one
- // or more contributor license agreements. See the NOTICE file
- // distributed with this work for additional information
- // regarding copyright ownership. The SFC licenses this file
- // to you under the Apache License, Version 2.0 (the
- // "License"); you may not use this file except in compliance
- // with the License. You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing,
- // software distributed under the License is distributed on an
- // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- // KIND, either express or implied. See the License for the
- // specific language governing permissions and limitations
- // under the License.
- 'use strict';
- /**
- * @fileoverview Factory methods for the supported locator strategies.
- */
- /**
- * Short-hand expressions for the primary element locator strategies.
- * For example the following two statements are equivalent:
- *
- * var e1 = driver.findElement(By.id('foo'));
- * var e2 = driver.findElement({id: 'foo'});
- *
- * Care should be taken when using JavaScript minifiers (such as the
- * Closure compiler), as locator hashes will always be parsed using
- * the un-obfuscated properties listed.
- *
- * @typedef {(
- * {className: string}|
- * {css: string}|
- * {id: string}|
- * {js: string}|
- * {linkText: string}|
- * {name: string}|
- * {partialLinkText: string}|
- * {tagName: string}|
- * {xpath: string})}
- */
- var ByHash;
- /**
- * Error thrown if an invalid character is encountered while escaping a CSS
- * identifier.
- * @see https://drafts.csswg.org/cssom/#serialize-an-identifier
- */
- class InvalidCharacterError extends Error {
- constructor() {
- super();
- this.name = this.constructor.name;
- }
- }
- /**
- * Escapes a CSS string.
- * @param {string} css the string to escape.
- * @return {string} the escaped string.
- * @throws {TypeError} if the input value is not a string.
- * @throws {InvalidCharacterError} if the string contains an invalid character.
- * @see https://drafts.csswg.org/cssom/#serialize-an-identifier
- */
- function escapeCss(css) {
- if (typeof css !== 'string') {
- throw new TypeError('input must be a string');
- }
- let ret = '';
- const n = css.length;
- for (let i = 0; i < n; i++) {
- const c = css.charCodeAt(i);
- if (c == 0x0) {
- throw new InvalidCharacterError();
- }
- if ((c >= 0x0001 && c <= 0x001F)
- || c == 0x007F
- || (i == 0 && c >= 0x0030 && c <= 0x0039)
- || (i == 1 && c >= 0x0030 && c <= 0x0039
- && css.charCodeAt(0) == 0x002D)) {
- ret += '\\' + c.toString(16) + ' ';
- continue;
- }
- if (i == 0 && c == 0x002D && n == 1) {
- ret += '\\' + css.charAt(i);
- continue;
- }
- if (c >= 0x0080
- || c == 0x002D // -
- || c == 0x005F // _
- || (c >= 0x0030 && c <= 0x0039) // [0-9]
- || (c >= 0x0041 && c <= 0x005A) // [A-Z]
- || (c >= 0x0061 && c <= 0x007A)) { // [a-z]
- ret += css.charAt(i);
- continue;
- }
- ret += '\\' + css.charAt(i);
- }
- return ret;
- }
- /**
- * Describes a mechanism for locating an element on the page.
- * @final
- */
- class By {
- /**
- * @param {string} using the name of the location strategy to use.
- * @param {string} value the value to search for.
- */
- constructor(using, value) {
- /** @type {string} */
- this.using = using;
- /** @type {string} */
- this.value = value;
- }
- /**
- * Locates elements that have a specific class name.
- *
- * @param {string} name The class name to search for.
- * @return {!By} The new locator.
- * @see http://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
- * @see http://www.w3.org/TR/CSS2/selector.html#class-html
- */
- static className(name) {
- let names = name.split(/\s+/g)
- .filter(s => s.length > 0)
- .map(s => escapeCss(s));
- return By.css('.' + names.join('.'));
- }
- /**
- * Locates elements using a CSS selector.
- *
- * @param {string} selector The CSS selector to use.
- * @return {!By} The new locator.
- * @see http://www.w3.org/TR/CSS2/selector.html
- */
- static css(selector) {
- return new By('css selector', selector);
- }
- /**
- * Locates elements by the ID attribute. This locator uses the CSS selector
- * `*[id="$ID"]`, _not_ `document.getElementById`.
- *
- * @param {string} id The ID to search for.
- * @return {!By} The new locator.
- */
- static id(id) {
- return By.css('*[id="' + escapeCss(id) + '"]');
- }
- /**
- * Locates link elements whose
- * {@linkplain webdriver.WebElement#getText visible text} matches the given
- * string.
- *
- * @param {string} text The link text to search for.
- * @return {!By} The new locator.
- */
- static linkText(text) {
- return new By('link text', text);
- }
- /**
- * Locates an elements by evaluating a
- * {@linkplain webdriver.WebDriver#executeScript JavaScript expression}.
- * The result of this expression must be an element or list of elements.
- *
- * @param {!(string|Function)} script The script to execute.
- * @param {...*} var_args The arguments to pass to the script.
- * @return {function(!./webdriver.WebDriver): !./promise.Promise}
- * A new JavaScript-based locator function.
- */
- static js(script, var_args) {
- let args = Array.prototype.slice.call(arguments, 0);
- return function(driver) {
- return driver.executeScript.apply(driver, args);
- };
- }
- /**
- * Locates elements whose `name` attribute has the given value.
- *
- * @param {string} name The name attribute to search for.
- * @return {!By} The new locator.
- */
- static name(name) {
- return By.css('*[name="' + escapeCss(name) + '"]');
- }
- /**
- * Locates link elements whose
- * {@linkplain webdriver.WebElement#getText visible text} contains the given
- * substring.
- *
- * @param {string} text The substring to check for in a link's visible text.
- * @return {!By} The new locator.
- */
- static partialLinkText(text) {
- return new By('partial link text', text);
- }
- /**
- * Locates elements with a given tag name.
- *
- * @param {string} name The tag name to search for.
- * @return {!By} The new locator.
- * @deprecated Use {@link By.css() By.css(tagName)} instead.
- */
- static tagName(name) {
- return By.css(name);
- }
- /**
- * Locates elements matching a XPath selector. Care should be taken when
- * using an XPath selector with a {@link webdriver.WebElement} as WebDriver
- * will respect the context in the specified in the selector. For example,
- * given the selector `//div`, WebDriver will search from the document root
- * regardless of whether the locator was used with a WebElement.
- *
- * @param {string} xpath The XPath selector to use.
- * @return {!By} The new locator.
- * @see http://www.w3.org/TR/xpath/
- */
- static xpath(xpath) {
- return new By('xpath', xpath);
- }
- /** @override */
- toString() {
- // The static By.name() overrides this.constructor.name. Shame...
- return `By(${this.using}, ${this.value})`;
- }
- }
- /**
- * Checks if a value is a valid locator.
- * @param {!(By|Function|ByHash)} locator The value to check.
- * @return {!(By|Function)} The valid locator.
- * @throws {TypeError} If the given value does not define a valid locator
- * strategy.
- */
- function check(locator) {
- if (locator instanceof By || typeof locator === 'function') {
- return locator;
- }
- if (locator
- && typeof locator === 'object'
- && typeof locator.using === 'string'
- && typeof locator.value === 'string') {
- return new By(locator.using, locator.value);
- }
- for (let key in locator) {
- if (locator.hasOwnProperty(key) && By.hasOwnProperty(key)) {
- return By[key](locator[key]);
- }
- }
- throw new TypeError('Invalid locator');
- }
- // PUBLIC API
- module.exports = {
- By: By,
- checkedLocator: check,
- };
|