Skip to content

Commit dc974c4

Browse files
committed
Add support for custom locators in webdriverjs
Fixes issue 6826
1 parent 410f334 commit dc974c4

File tree

10 files changed

+449
-214
lines changed

10 files changed

+449
-214
lines changed

javascript/node/selenium-webdriver/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Introduced `Promise#thenCatch()` and `Promise#thenFinally()`.
77
* Deprecated `Promise#addCallback()`, `Promise#addCallbacks()`,
88
`Promise#addErrback()`, and `Promise#addBoth()`.
9+
* FIXED: 6826: Added support for custom locators.
910

1011
## v2.39.0
1112

javascript/node/selenium-webdriver/index.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ exports.ActionSequence = base.require('webdriver.ActionSequence');
3636
exports.Builder = builder.Builder;
3737

3838

39+
/** @type {webdriver.By.} */
40+
exports.By = base.require('webdriver.By');
41+
42+
3943
/** @type {function(new: webdriver.Capabilities)} */
4044
exports.Capabilities = base.require('webdriver.Capabilities');
4145

@@ -76,12 +80,6 @@ exports.WebElement = base.require('webdriver.WebElement');
7680
}));
7781

7882

79-
/** @type {webdriver.Locator.Strategy.} */
80-
(exports.__defineGetter__('By', function() {
81-
return base.require('webdriver.Locator.Strategy');
82-
}));
83-
84-
8583
/** @type {webdriver.Capability.} */
8684
(exports.__defineGetter__('Capability', function() {
8785
return base.require('webdriver.Capability');

javascript/webdriver/browser/commandexecutor.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,21 @@ webdriver.browser.CommandExecutor.prototype.sendKeysToElement_ = function(
429429
};
430430

431431

432+
/**
433+
* Tests whether an element is displayed on the page.
434+
* @param {!webdriver.Command} command The command.
435+
* @return {boolean} Whether the element is displayed.
436+
* @throws {bot.Error} If the element reference is no longer valid.
437+
* @private
438+
*/
439+
webdriver.browser.CommandExecutor.prototype.isElementDisplayed_ = function(
440+
command) {
441+
var id = command.getParameter('id')[bot.inject.ELEMENT_KEY];
442+
var el = bot.inject.cache.getElement(id, this.window_.document);
443+
return bot.dom.isShown(el);
444+
};
445+
446+
432447
/**
433448
* Executes a user-supplier snippet of JavaScript in the context of the page
434449
* under test.
@@ -530,4 +545,5 @@ map[name.GET_ELEMENT_ATTRIBUTE] = proto.getElementAttribute_;
530545
map[name.GET_ELEMENT_TEXT] = proto.getElementText_;
531546
map[name.GET_ELEMENT_TAG_NAME] = proto.getElementTagName_;
532547
map[name.SEND_KEYS_TO_ELEMENT] = proto.sendKeysToElement_;
548+
map[name.IS_ELEMENT_DISPLAYED] = proto.isElementDisplayed_;
533549
}); // goog.scope

javascript/webdriver/exports/exports.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ goog.require('bot.response');
2323
goog.require('webdriver.ActionSequence');
2424
goog.require('webdriver.Builder');
2525
goog.require('webdriver.Button');
26+
goog.require('webdriver.By');
2627
goog.require('webdriver.Capabilities');
2728
goog.require('webdriver.Capability');
2829
goog.require('webdriver.Command');
2930
goog.require('webdriver.CommandName');
3031
goog.require('webdriver.EventEmitter');
3132
goog.require('webdriver.Key');
32-
goog.require('webdriver.Locator');
3333
goog.require('webdriver.Session');
3434
goog.require('webdriver.WebDriver');
3535
goog.require('webdriver.WebElement');
@@ -47,7 +47,7 @@ goog.require('webdriver.testing.asserts');
4747
exports.ActionSequence = webdriver.ActionSequence;
4848
exports.Builder = webdriver.Builder;
4949
exports.Button = webdriver.Button;
50-
exports.By = webdriver.Locator.Strategy;
50+
exports.By = webdriver.By;
5151
exports.Capabilities = webdriver.Capabilities;
5252
exports.Capability = webdriver.Capability;
5353
exports.Command = webdriver.Command;

javascript/webdriver/locators.js

Lines changed: 176 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
* @fileoverview Factory methods for the supported locator strategies.
1717
*/
1818

19+
goog.provide('webdriver.By');
1920
goog.provide('webdriver.Locator');
2021
goog.provide('webdriver.Locator.Strategy');
2122

22-
goog.require('bot.json');
23+
goog.require('goog.array');
2324
goog.require('goog.object');
25+
goog.require('goog.string');
2426

2527

2628

@@ -38,7 +40,7 @@ webdriver.Locator = function(using, value) {
3840
*/
3941
this.using = using;
4042

41-
/**u
43+
/**
4244
* The search target for this locator.
4345
* @type {string}
4446
*/
@@ -47,9 +49,9 @@ webdriver.Locator = function(using, value) {
4749

4850

4951
/**
50-
* Creates a factory function for a {@code webdriver.Locator}.
52+
* Creates a factory function for a {@link webdriver.Locator}.
5153
* @param {string} type The type of locator for the factory.
52-
* @return {function(string):!webdriver.Locator} The new factory function.
54+
* @return {function(string): !webdriver.Locator} The new factory function.
5355
* @private
5456
*/
5557
webdriver.Locator.factory_ = function(type) {
@@ -60,63 +62,192 @@ webdriver.Locator.factory_ = function(type) {
6062

6163

6264
/**
63-
* Factory methods for the supported locator strategies.
64-
* @type {Object.<function(string):!webdriver.Locator>}
65+
* A collection of factory functions for creating {@link webdriver.Locator}
66+
* instances.
6567
*/
66-
webdriver.Locator.Strategy = {
67-
'className': webdriver.Locator.factory_('class name'),
68-
'class name': webdriver.Locator.factory_('class name'),
69-
'css': webdriver.Locator.factory_('css selector'),
70-
'id': webdriver.Locator.factory_('id'),
71-
'js': webdriver.Locator.factory_('js'),
72-
'linkText': webdriver.Locator.factory_('link text'),
73-
'link text': webdriver.Locator.factory_('link text'),
74-
'name': webdriver.Locator.factory_('name'),
75-
'partialLinkText': webdriver.Locator.factory_('partial link text'),
76-
'partial link text': webdriver.Locator.factory_('partial link text'),
77-
'tagName': webdriver.Locator.factory_('tag name'),
78-
'tag name': webdriver.Locator.factory_('tag name'),
79-
'xpath': webdriver.Locator.factory_('xpath')
68+
webdriver.By = {};
69+
// Exported to the global scope for legacy reasons.
70+
goog.exportSymbol('By', webdriver.By);
71+
72+
73+
/**
74+
* Short-hand expressions for the primary element locator strategies.
75+
* For example the following two statements are equivalent:
76+
* <code><pre>
77+
* var e1 = driver.findElement(webdriver.By.id('foo'));
78+
* var e2 = driver.findElement({id: 'foo'});
79+
* </pre></code>
80+
*
81+
* <p>Care should be taken when using JavaScript minifiers (such as the
82+
* Closure compiler), as locator hashes will always be parsed using
83+
* the un-obfuscated properties listed below.
84+
*
85+
* @typedef {(
86+
* {className: string}|
87+
* {css: string}|
88+
* {id: string}|
89+
* {js: string}|
90+
* {linkText: string}|
91+
* {name: string}|
92+
* {partialLinkText: string}|
93+
* {tagName: string}|
94+
* {xpath: string})}
95+
*/
96+
webdriver.By.Hash;
97+
98+
99+
/**
100+
* Locates elements that have a specific class name. The returned locator
101+
* is equivalent to searching for elements with the CSS selector ".clazz".
102+
*
103+
* @param {string} className The class name to search for.
104+
* @return {!webdriver.Locator} The new locator.
105+
* @see http://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
106+
* @see http://www.w3.org/TR/CSS2/selector.html#class-html
107+
*/
108+
webdriver.By.className = webdriver.Locator.factory_('class name');
109+
110+
111+
/**
112+
* Locates elements using a CSS selector. For browsers that do not support
113+
* CSS selectors, WebDriver implementations may return an
114+
* {@link bot.Error.State.INVALID_SELECTOR invalid selector} error. An
115+
* implementation may, however, emulate the CSS selector API.
116+
*
117+
* @param {string} selector The CSS selector to use.
118+
* @return {!webdriver.Locator} The new locator.
119+
* @see http://www.w3.org/TR/CSS2/selector.html
120+
*/
121+
webdriver.By.css = webdriver.Locator.factory_('css selector');
122+
123+
124+
/**
125+
* Locates an element by its ID.
126+
*
127+
* @param {string} id The ID to search for.
128+
* @return {!webdriver.Locator} The new locator.
129+
*/
130+
webdriver.By.id = webdriver.Locator.factory_('id');
131+
132+
133+
/**
134+
* Locates link elements whose {@link webdriver.WebElement#getText visible
135+
* text} matches the given string.
136+
*
137+
* @param {string} text The link text to search for.
138+
* @return {!webdriver.Locator} The new locator.
139+
*/
140+
webdriver.By.linkText = webdriver.Locator.factory_('link text');
141+
142+
143+
/**
144+
* Locates an elements by evaluating a
145+
* {@link webdriver.WebDriver#executeScript JavaScript expression}.
146+
* The result of this expression must be an element or list of elements.
147+
*
148+
* @param {!(string|Function)} script The script to execute.
149+
* @param {...*} var_args The arguments to pass to the script.
150+
* @return {function(!webdriver.WebDriver): !webdriver.promise.Promise} A new,
151+
* JavaScript-based locator function.
152+
*/
153+
webdriver.By.js = function(script, var_args) {
154+
var args = goog.array.slice(arguments, 0);
155+
return function(driver) {
156+
return driver.executeScript.apply(driver, args);
157+
};
80158
};
81-
goog.exportSymbol('By', webdriver.Locator.Strategy);
82159

83160

84161
/**
85-
* Creates a new Locator from an object whose only property is also a key in
86-
* the {@code webdriver.Locator.Strategy} map.
87-
* @param {Object.<string>} obj The object to convert into a locator.
88-
* @return {webdriver.Locator} The new locator object.
162+
* Locates elements whose {@code name} attribute has the given value.
163+
*
164+
* @param {string} name The name attribute to search for.
165+
* @return {!webdriver.Locator} The new locator.
89166
*/
90-
webdriver.Locator.createFromObj = function(obj) {
91-
var key = goog.object.getAnyKey(obj);
92-
if (!key) {
93-
throw Error('No keys found in locator hash object');
94-
} else if (key in webdriver.Locator.Strategy) {
95-
return webdriver.Locator.Strategy[key](obj[key]);
96-
}
97-
throw Error('Unsupported locator strategy: ' + key);
167+
webdriver.By.name = webdriver.Locator.factory_('name');
168+
169+
170+
/**
171+
* Locates link elements whose {@link webdriver.WebElement#getText visible
172+
* text} contains the given substring.
173+
*
174+
* @param {string} text The substring to check for in a link's visible text.
175+
* @return {!webdriver.Locator} The new locator.
176+
*/
177+
webdriver.By.partialLinkText = webdriver.Locator.factory_(
178+
'partial link text');
179+
180+
181+
/**
182+
* Locates elements with a given tag name. The returned locator is
183+
* equivalent to using the {@code getElementsByTagName} DOM function.
184+
*
185+
* @param {string} text The substring to check for in a link's visible text.
186+
* @return {!webdriver.Locator} The new locator.
187+
* @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html
188+
*/
189+
webdriver.By.tagName = webdriver.Locator.factory_('tag name');
190+
191+
192+
/**
193+
* Locates elements matching a XPath selector. Care should be taken when
194+
* using an XPath selector with a {@link webdriver.WebElement} as WebDriver
195+
* will respect the context in the specified in the selector. For example,
196+
* given the selector {@code "//div"}, WebDriver will search from the
197+
* document root regardless of whether the locator was used with a
198+
* WebElement.
199+
*
200+
* @param {string} xpath The XPath selector to use.
201+
* @return {!webdriver.Locator} The new locator.
202+
* @see http://www.w3.org/TR/xpath/
203+
*/
204+
webdriver.By.xpath = webdriver.Locator.factory_('xpath');
205+
206+
207+
/**
208+
* Maps {@link webdriver.By.Hash} keys to the appropriate factory function.
209+
* @type {!Object.<string, function(string): !(Function|webdriver.Locator)>}
210+
* @const
211+
*/
212+
webdriver.Locator.Strategy = {
213+
'className': webdriver.By.className,
214+
'css': webdriver.By.css,
215+
'id': webdriver.By.id,
216+
'js': webdriver.By.js,
217+
'linkText': webdriver.By.linkText,
218+
'name': webdriver.By.name,
219+
'partialLinkText': webdriver.By.partialLinkText,
220+
'tagName': webdriver.By.tagName,
221+
'xpath': webdriver.By.xpath
98222
};
99223

100224

101225
/**
102-
* Verifies that a {@code locator} is a valid locator to use for searching for
226+
* Verifies that a {@code value} is a valid locator to use for searching for
103227
* elements on the page.
104-
* @param {webdriver.Locator|Object.<string>} locator The locator
105-
* to verify, or a short-hand object that can be converted into a locator
106-
* to verify.
107-
* @return {!webdriver.Locator} The validated locator.
108-
*/
109-
webdriver.Locator.checkLocator = function(locator) {
110-
if (!locator.using || !locator.value) {
111-
locator = webdriver.Locator.createFromObj(locator);
228+
*
229+
* @param {*} value The value to check is a valid locator.
230+
* @return {!(webdriver.Locator|Function)} A valid locator object or function.
231+
* @throws {TypeError} If the given value is an invalid locator.
232+
*/
233+
webdriver.Locator.checkLocator = function(value) {
234+
if (goog.isFunction(value) || value instanceof webdriver.Locator) {
235+
return value;
236+
}
237+
for (var key in value) {
238+
if (value.hasOwnProperty(key) &&
239+
webdriver.Locator.Strategy.hasOwnProperty(key)) {
240+
return webdriver.Locator.Strategy[key](value[key]);
241+
}
112242
}
113-
return /**@type {!webdriver.Locator} */ (locator);
243+
throw new TypeError('Invalid locator');
114244
};
115245

116246

117-
/** @return {string} String representation of this locator. */
247+
248+
/** @override */
118249
webdriver.Locator.prototype.toString = function() {
119250
return 'By.' + this.using.replace(/ ([a-z])/g, function(all, match) {
120251
return match.toUpperCase();
121-
}) + '(' + bot.json.stringify(this.value) + ')';
252+
}) + '(' + goog.string.quote(this.value) + ')';
122253
};

0 commit comments

Comments
 (0)