
* Browser tests are supposedly faster than ui tests. BUG=118499 TEST= Review URL: http://codereview.chromium.org/9693065 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128362 0039d316-1c4b-4281-b951-d872f2087c98
421 lines
14 KiB
JavaScript
421 lines
14 KiB
JavaScript
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// dom_automation.js
|
|
// Methods for performing common DOM operations. Used in Chrome testing
|
|
// involving the DomAutomationController.
|
|
|
|
var domAutomation = domAutomation || {};
|
|
|
|
(function() {
|
|
// |objects| is used to track objects which are sent back to the
|
|
// DomAutomationController. Since JavaScript does not have a map type,
|
|
// |objects| is simply an object in which the property name and
|
|
// property value serve as the key-value pair. The key is the handle number
|
|
// and the value is the tracked object.
|
|
domAutomation.objects = {};
|
|
|
|
// The next object handle to use.
|
|
domAutomation.nextHandle = 1;
|
|
|
|
// The current call ID for which a response is awaited. Each asynchronous
|
|
// function is given a call ID. When the function has a result to return,
|
|
// it must supply that call ID. If a result has not yet been received for
|
|
// that call ID, a response containing the result will be sent to the
|
|
// domAutomationController.
|
|
domAutomation.currentCallId = 1;
|
|
|
|
// The current timeout for an asynchronous JavaScript evaluation. Can be given
|
|
// to window.clearTimeout.
|
|
domAutomation.currentTimeout = null;
|
|
|
|
// Returns |value| after converting it to an acceptable type for return, if
|
|
// necessary.
|
|
function getConvertedValue(value) {
|
|
if (typeof value == "undefined" || !value) {
|
|
return "";
|
|
}
|
|
if (value instanceof Array) {
|
|
var result = [];
|
|
for (var i = 0; i < value.length; i++) {
|
|
result.push(getConvertedValue(value[i]));
|
|
}
|
|
return result;
|
|
}
|
|
if (typeof(value) == "object") {
|
|
var handle = getHandleForObject(value);
|
|
if (handle == -1) {
|
|
// Track this object.
|
|
var handle = domAutomation.nextHandle++;
|
|
domAutomation.objects[handle] = value;
|
|
}
|
|
return handle;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// Returns the handle for |obj|, or -1 if no handle exists.
|
|
function getHandleForObject(obj) {
|
|
for (var property in domAutomation.objects) {
|
|
if (domAutomation.objects[property] == obj)
|
|
return parseInt(property);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Sends a completed response back to the domAutomationController with a
|
|
// return value, which can be of any type.
|
|
function sendCompletedResponse(returnValue) {
|
|
var result = [true, "", getConvertedValue(returnValue)];
|
|
domAutomationController.sendJSON(JSON.stringify(result));
|
|
}
|
|
|
|
// Sends a error response back to the domAutomationController. |exception|
|
|
// should be a string or an exception.
|
|
function sendErrorResponse(exception) {
|
|
var message = exception.message;
|
|
if (typeof message == "undefined")
|
|
message = exception;
|
|
if (typeof message != "string")
|
|
message = JSON.stringify(message);
|
|
var result = [false, message, exception];
|
|
domAutomationController.sendJSON(JSON.stringify(result));
|
|
}
|
|
|
|
// Safely evaluates |javascript| and sends a response back via the
|
|
// DomAutomationController. See javascript_execution_controller.cc
|
|
// for more details.
|
|
domAutomation.evaluateJavaScript = function(javascript) {
|
|
try {
|
|
sendCompletedResponse(eval(javascript));
|
|
}
|
|
catch (exception) {
|
|
sendErrorResponse(exception);
|
|
}
|
|
}
|
|
|
|
// Called by a function when it has completed successfully. Any value,
|
|
// including undefined, is acceptable for |returnValue|. This should only
|
|
// be used by functions with an asynchronous response.
|
|
function onAsyncJavaScriptComplete(callId, returnValue) {
|
|
if (domAutomation.currentCallId != callId) {
|
|
// We are not waiting for a response for this call anymore,
|
|
// because it already responded.
|
|
return;
|
|
}
|
|
domAutomation.currentCallId++;
|
|
window.clearTimeout(domAutomation.currentTimeout);
|
|
sendCompletedResponse(returnValue);
|
|
}
|
|
|
|
// Calld by a function when it has an error preventing its successful
|
|
// execution. |exception| should be an exception or a string.
|
|
function onAsyncJavaScriptError(callId, exception) {
|
|
if (domAutomation.currentCallId != callId) {
|
|
// We are not waiting for a response for this call anymore,
|
|
// because it already responded.
|
|
return;
|
|
}
|
|
domAutomation.currentCallId++;
|
|
window.clearTimeout(domAutomation.currentTimeout);
|
|
sendErrorResponse(exception);
|
|
}
|
|
|
|
// Returns whether the call with the given ID has already finished. If true,
|
|
// this means that the call timed out or that it already gave a response.
|
|
function didCallFinish(callId) {
|
|
return domAutomation.currentCallId != callId;
|
|
}
|
|
|
|
// Safely evaluates |javascript|. The JavaScript is expected to return
|
|
// a response via either onAsyncJavaScriptComplete or
|
|
// onAsyncJavaScriptError. The script should respond within the |timeoutMs|.
|
|
domAutomation.evaluateAsyncJavaScript = function(javascript, timeoutMs) {
|
|
try {
|
|
eval(javascript);
|
|
}
|
|
catch (exception) {
|
|
onAsyncJavaScriptError(domAutomation.currentCallId, exception);
|
|
return;
|
|
}
|
|
domAutomation.currentTimeout = window.setTimeout(
|
|
onAsyncJavaScriptError, timeoutMs, domAutomation.currentCallId,
|
|
"JavaScript timed out waiting for response.");
|
|
}
|
|
|
|
// Stops tracking the object associated with |handle|.
|
|
domAutomation.removeObject = function(handle) {
|
|
delete domAutomation.objects[handle];
|
|
}
|
|
|
|
// Stops tracking all objects.
|
|
domAutomation.removeAll = function() {
|
|
domAutomation.objects = {};
|
|
domAutomation.nextHandle = 1;
|
|
}
|
|
|
|
// Gets the object associated with this |handle|.
|
|
domAutomation.getObject = function(handle) {
|
|
var obj = domAutomation.objects[handle]
|
|
if (typeof obj == "undefined") {
|
|
throw "Object with handle " + handle + " does not exist."
|
|
}
|
|
return domAutomation.objects[handle];
|
|
}
|
|
|
|
// Gets the ID for this asynchronous call.
|
|
domAutomation.getCallId = function() {
|
|
return domAutomation.currentCallId;
|
|
}
|
|
|
|
// Converts an indexable list with a length property to an array.
|
|
function getArray(list) {
|
|
var arr = [];
|
|
for (var i = 0; i < list.length; i++) {
|
|
arr.push(list[i]);
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
// Removes whitespace at the beginning and end of |text|.
|
|
function trim(text) {
|
|
return text.replace(/^\s+|\s+$/g, "");
|
|
}
|
|
|
|
// Returns the window (which is a sub window of |win|) which
|
|
// directly contains |doc|. May return null.
|
|
function findWindowForDocument(win, doc) {
|
|
if (win.document == doc)
|
|
return win;
|
|
for (var i = 0; i < win.frames.length; i++) {
|
|
if (findWindowForDocument(win.frames[i], doc))
|
|
return win.frames[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Returns |element|'s text. This includes all descendants' text.
|
|
// For textareas and inputs, the text is the element's value. For Text,
|
|
// it is the textContent.
|
|
function getText(element) {
|
|
if (element instanceof Text) {
|
|
return trim(element.textContent);
|
|
} else if (element instanceof HTMLTextAreaElement ||
|
|
element instanceof HTMLInputElement) {
|
|
return element.value || "";
|
|
}
|
|
var childrenText = "";
|
|
for (var i = 0; i < element.childNodes.length; i++) {
|
|
childrenText += getText(element.childNodes[i]);
|
|
}
|
|
return childrenText;
|
|
}
|
|
|
|
// Returns whether |element| is visible.
|
|
function isVisible(element) {
|
|
while (element.style) {
|
|
if (element.style.display == 'none' ||
|
|
element.style.visibility == 'hidden' ||
|
|
element.style.visibility == 'collapse') {
|
|
return false;
|
|
}
|
|
element = element.parentNode;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Returns an array of the visible elements found in the |elements| array.
|
|
function getVisibleElements(elements) {
|
|
var visibleElements = [];
|
|
for (var i = 0; i < elements.length; i++) {
|
|
if (isVisible(elements[i]))
|
|
visibleElements.push(elements[i]);
|
|
}
|
|
return visibleElements;
|
|
}
|
|
|
|
// Finds all the elements which satisfy the xpath query using the context
|
|
// node |context|. This function may throw an exception.
|
|
function findByXPath(context, xpath) {
|
|
var xpathResult =
|
|
document.evaluate(xpath, context, null,
|
|
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
var elements = [];
|
|
for (var i = 0; i < xpathResult.snapshotLength; i++) {
|
|
elements.push(xpathResult.snapshotItem(i));
|
|
}
|
|
return elements;
|
|
}
|
|
|
|
// Finds the first element which satisfies the xpath query using the context
|
|
// node |context|. This function may throw an exception.
|
|
function find1ByXPath(context, xpath) {
|
|
var xpathResult =
|
|
document.evaluate(xpath, context, null,
|
|
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
return xpathResult.singleNodeValue;
|
|
}
|
|
|
|
// Finds all the elements which satisfy the selectors query using the context
|
|
// node |context|. This function may throw an exception.
|
|
function findBySelectors(context, selectors) {
|
|
return getArray(context.querySelectorAll(selectors));
|
|
}
|
|
|
|
// Finds the first element which satisfies the selectors query using the
|
|
// context node |context|. This function may throw an exception.
|
|
function find1BySelectors(context, selectors) {
|
|
return context.querySelector(selectors);
|
|
}
|
|
|
|
// Finds all the elements which contain |text| using the context
|
|
// node |context|. See getText for details about what constitutes the text
|
|
// of an element. This function may throw an exception.
|
|
function findByText(context, text) {
|
|
// Find all elements containing this text and all inputs containing
|
|
// this text.
|
|
var xpath = ".//*[contains(text(), '" + text + "')] | " +
|
|
".//input[contains(@value, '" + text + "')]";
|
|
var elements = findByXPath(context, xpath);
|
|
|
|
// Limit to what is visible.
|
|
return getVisibleElements(elements);
|
|
}
|
|
|
|
// Finds the first element which contains |text| using the context
|
|
// node |context|. See getText for details about what constitutes the text
|
|
// of an element. This function may throw an exception.
|
|
function find1ByText(context, text) {
|
|
var elements = findByText(context, text);
|
|
if (elements.length > 0)
|
|
return findByText(context, text)[0];
|
|
return null;
|
|
}
|
|
|
|
//// DOM Element automation methods
|
|
//// See dom_element_proxy.h for method details.
|
|
|
|
domAutomation.getDocumentFromFrame = function(element, frameNames) {
|
|
// Find the window this element is in.
|
|
var containingDocument = element.ownerDocument || element;
|
|
var frame = findWindowForDocument(window, containingDocument);
|
|
|
|
for (var i = 0; i < frameNames.length; i++) {
|
|
frame = frame.frames[frameNames[i]];
|
|
if (typeof frame == "undefined" || !frame) {
|
|
return null;
|
|
}
|
|
}
|
|
return frame.document;
|
|
}
|
|
|
|
domAutomation.findElement = function(context, query) {
|
|
var type = query.type;
|
|
var queryString = query.queryString;
|
|
if (type == "xpath") {
|
|
return find1ByXPath(context, queryString);
|
|
} else if (type == "selectors") {
|
|
return find1BySelectors(context, queryString);
|
|
} else if (type == "text") {
|
|
return find1ByText(context, queryString);
|
|
}
|
|
}
|
|
|
|
domAutomation.findElements = function(context, query) {
|
|
var type = query.type;
|
|
var queryString = query.queryString;
|
|
if (type == "xpath") {
|
|
return findByXPath(context, queryString);
|
|
} else if (type == "selectors") {
|
|
return findBySelectors(context, queryString);
|
|
} else if (type == "text") {
|
|
return findByText(context, queryString);
|
|
}
|
|
}
|
|
|
|
domAutomation.waitForVisibleElementCount = function(context, query, count,
|
|
callId) {
|
|
(function waitHelper() {
|
|
try {
|
|
var elements = domAutomation.findElements(context, query);
|
|
var visibleElements = getVisibleElements(elements);
|
|
if (visibleElements.length == count)
|
|
onAsyncJavaScriptComplete(callId, visibleElements);
|
|
else if (!didCallFinish(callId))
|
|
window.setTimeout(waitHelper, 500);
|
|
}
|
|
catch (exception) {
|
|
onAsyncJavaScriptError(callId, exception);
|
|
}
|
|
})();
|
|
}
|
|
|
|
domAutomation.click = function(element) {
|
|
var evt = document.createEvent('MouseEvents');
|
|
evt.initMouseEvent('click', true, true, window,
|
|
0, 0, 0, 0, 0, false, false,
|
|
false, false, 0, null);
|
|
while (element) {
|
|
element.dispatchEvent(evt);
|
|
element = element.parentNode;
|
|
}
|
|
}
|
|
|
|
domAutomation.type = function(element, text) {
|
|
if (element instanceof HTMLTextAreaElement ||
|
|
(element instanceof HTMLInputElement && element.type == "text")) {
|
|
element.value += text;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
domAutomation.setText = function(element, text) {
|
|
if (element instanceof HTMLTextAreaElement ||
|
|
(element instanceof HTMLInputElement && element.type == "text")) {
|
|
element.value = text;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
domAutomation.getProperty = function(element, property) {
|
|
return element[property];
|
|
}
|
|
|
|
domAutomation.getAttribute = function(element, attribute) {
|
|
return element.getAttribute(attribute);
|
|
}
|
|
|
|
domAutomation.getValue = function(element, type) {
|
|
if (type == "text") {
|
|
return getText(element);
|
|
} else if (type == "innerhtml") {
|
|
return trim(element.innerHTML);
|
|
} else if (type == "innertext") {
|
|
return trim(element.innerText);
|
|
} else if (type == "visibility") {
|
|
return isVisible(element);
|
|
} else if (type == "id") {
|
|
return element.id;
|
|
} else if (type == "contentdocument") {
|
|
return element.contentDocument;
|
|
}
|
|
}
|
|
|
|
domAutomation.waitForAttribute = function(element, attribute, value, callId) {
|
|
(function waitForAttributeHelper() {
|
|
try {
|
|
if (element.getAttribute(attribute) == value)
|
|
onAsyncJavaScriptComplete(callId);
|
|
else if (!didCallFinish(callId))
|
|
window.setTimeout(waitForAttributeHelper, 200);
|
|
}
|
|
catch (exception) {
|
|
onAsyncJavaScriptError(callId, exception);
|
|
}
|
|
})();
|
|
}
|
|
})();
|