blob: 7016bd1cd6b29755cc6221e30d91ce169752956d [file] [log] [blame]
/**
Copyright 2010 WebDriver committers
Copyright 2010 Google Inc.
Licensed 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.
*/
/**
* @fileoverview The selenium element locators.
*/
goog.provide('core.locators');
goog.provide('core.locators.Locator');
goog.require('core.Error');
goog.require('core.LocatorStrategies');
goog.require('goog.dom.NodeType');
goog.require('goog.string');
/**
* @typedef {{type: string, string: string}}
*/
core.locators.Locator;
/**
* Parses a Selenium locator, returning its type and the unprefixed locator
* string as an object.
*
* @param {string} locator The locator to parse.
* @return {!core.locators.Locator} The parsed locator.
* @private
*/
core.locators.parseLocator_ = function(locator) {
var result = locator.match(/^([A-Za-z]+)=.+/);
if (result) {
var type = result[1].toLowerCase();
var actualLocator = locator.substring(type.length + 1);
return {
'type': type,
'string': actualLocator
};
}
// The tyrant that is jsdoc demands homage.
var /**@type{core.locators.Locator}*/ toReturn = {'string': '', 'type': ''};
toReturn['string'] = locator;
if (goog.string.startsWith(locator, ('//'))) {
toReturn['type'] = 'xpath';
} else if (goog.string.startsWith(locator, 'document.')) {
toReturn['type'] = 'dom';
} else {
toReturn['type'] = 'identifier';
}
return toReturn;
};
/**
* Add a locator strategy to those already known about.
*
* @param {string} name The name of the strategy.
* @param {function(string, Document=):Element} strategy The strategy
* implementation.
*/
core.locators.addStrategy = function(name, strategy) {
core.LocatorStrategies[name] = strategy;
};
/**
* Find a locator based on a prefix.
*
* @param {string} locatorType The type of locator to use.
* @param {string} locator The value of the locator to use.
* @param {Document=} opt_doc The document to base the search from.
* @return {Element} The located element or null.
* @private
*/
core.locators.findElementBy_ = function(locatorType, locator, opt_doc) {
var locatorFunction = core.LocatorStrategies[locatorType];
if (!locatorFunction) {
throw new core.Error("Unrecognised locator type: '" + locatorType + "'");
}
return locatorFunction.call(null, locator, opt_doc);
};
/**
* Finds an element recursively in frames and nested frames
* in the specified document, using various lookup protocols.
*
* @param {string} locatorType The type of locator to use.
* @param {string} locatorString The value of the locator to use.
* @param {Document=} opt_doc The document to base the search from.
* @param {Window=} opt_win The window to base the search from.
* @return {Element} The located element or null.
* @private
*/
core.locators.findElementRecursive_ = function(locatorType, locatorString,
opt_doc, opt_win) {
var element = core.locators.findElementBy_(
locatorType, locatorString, opt_doc);
if (element != null) {
return element;
}
if (!opt_win) {
return null;
}
for (var i = 0; i < opt_win.frames.length; i++) {
// On some browsers, the document object is undefined for third-party
// frames. Make sure the document is valid before continuing.
var childDocument;
try {
childDocument = opt_win.frames[i].document;
} catch (e) {
// Tried to go across domains. That's okay
}
if (childDocument) {
element = core.locators.findElementRecursive_(
locatorType, locatorString, childDocument, opt_win.frames[i]);
if (element != null) {
return element;
}
}
}
return null;
};
/**
* Finds an element on the current page, using various lookup protocols.
*
* @param {string} locator The locator to use.
* @param {Window=} opt_win The optional window to base the search from.
* @return {Element} The located element, or null if nothing is found.
*/
core.locators.findElementOrNull = function(locator, opt_win) {
var loc = core.locators.parseLocator_(locator);
var win = opt_win || bot.getWindow();
var element = core.locators.findElementRecursive_(
loc['type'], loc['string'], win.document, win);
return element;
};
/**
* Find an element. The locator used is a selenium locator, that is, one that
* may include a leading type identifier, such as "id=". If no type identifier
* is given, a "best guess" is made. If the locator starts with "//" it is
* assumed to be xpath, otherwise it is assumed to be either a name or an id.
*
* @param {string|!Element} locator The selenium locator to use.
* @param {Document=} opt_doc The document to start the search from.
* @param {Window=} opt_win The optional window to start the search from.
* @return {!Element} The located element.
* @throws {core.Error} If no element can be located.
*/
core.locators.findElement = function(locator, opt_doc, opt_win) {
// Fast path out
if (!goog.isString(locator)) {
return locator;
}
var win = opt_win || bot.getWindow();
var element = core.locators.findElementOrNull(locator, win);
if (element == null) {
throw new core.Error('Element ' + locator + ' not found');
}
return element;
};
/**
* @param {string} locator The selenium locator to use.
* @return {boolean} Whether the element can be found on the DOM.
*/
core.locators.isElementPresent = function(locator) {
return !!core.locators.findElementOrNull(locator);
};
/**
* @param {!Element|!Document} element The element to base the search from.
* @param {function(!Element):boolean} selector The selenium locator to use.
* @return {Element} The first matching child element.
*/
core.locators.elementFindFirstMatchingChild = function(element, selector) {
var childCount = element.childNodes.length;
for (var i = 0; i < childCount; i++) {
var child = element.childNodes[i];
if (child.nodeType == goog.dom.NodeType.ELEMENT) {
if (selector(child)) {
return child;
}
var result = core.locators.elementFindFirstMatchingChild(child, selector);
if (result) {
return result;
}
}
}
return null;
};