summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/dom_ui/file_browse_browsertest.cc4
-rw-r--r--chrome/chrome.gyp4
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/renderer/resources/dom_automation.js294
-rw-r--r--chrome/test/automation/dom_automation_browsertest.cc115
-rw-r--r--chrome/test/automation/dom_element_proxy.cc304
-rw-r--r--chrome/test/automation/dom_element_proxy.h141
-rw-r--r--chrome/test/automation/javascript_execution_controller.cc116
-rw-r--r--chrome/test/automation/javascript_execution_controller.h141
-rw-r--r--chrome/test/automation/javascript_message_utils.h144
-rw-r--r--chrome/test/automation/tab_proxy.cc32
-rw-r--r--chrome/test/automation/tab_proxy.h23
-rw-r--r--chrome/test/data/dom_automation/wait/test.html21
-rw-r--r--chrome/test/ui/ui_test.cc2
-rw-r--r--chrome/test/ui_test_utils.cc6
15 files changed, 901 insertions, 447 deletions
diff --git a/chrome/browser/dom_ui/file_browse_browsertest.cc b/chrome/browser/dom_ui/file_browse_browsertest.cc
index 20ff287..507e178 100644
--- a/chrome/browser/dom_ui/file_browse_browsertest.cc
+++ b/chrome/browser/dom_ui/file_browse_browsertest.cc
@@ -19,6 +19,8 @@
namespace {
+typedef DOMElementProxy::By By;
+
class FileBrowseBrowserTest : public InProcessBrowserTest {
public:
FileBrowseBrowserTest() {
@@ -130,7 +132,7 @@ IN_PROC_BROWSER_TEST_F(FileBrowseBrowserTest, InputFileTriggerFileBrowse) {
DOMElementProxyRef doc = ui_test_utils::GetActiveDOMDocument(browser());
- DOMElementProxyRef input_file = doc->FindBySelectors(".single");
+ DOMElementProxyRef input_file = doc->FindElement(By::Selectors(".single"));
ASSERT_TRUE(input_file);
// Creates FileBrowseUiObserver before we click.
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index c9b2698..9f23d90 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -1472,8 +1472,12 @@
'test/automation/automation_proxy.h',
'test/automation/browser_proxy.cc',
'test/automation/browser_proxy.h',
+ 'test/automation/dom_element_proxy.cc',
+ 'test/automation/dom_element_proxy.h',
'test/automation/extension_proxy.cc',
'test/automation/extension_proxy.h',
+ 'test/automation/javascript_execution_controller.cc',
+ 'test/automation/javascript_execution_controller.h',
'test/automation/tab_proxy.cc',
'test/automation/tab_proxy.h',
'test/automation/window_proxy.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 94070dfe..dc8cb1e 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -117,6 +117,7 @@
'test/automation/extension_proxy.h',
'test/automation/javascript_execution_controller.cc',
'test/automation/javascript_execution_controller.h',
+ 'test/automation/javascript_message_utils.h',
'test/automation/tab_proxy.cc',
'test/automation/tab_proxy.h',
'test/automation/window_proxy.cc',
diff --git a/chrome/renderer/resources/dom_automation.js b/chrome/renderer/resources/dom_automation.js
index 16a4dd0..0af645b 100644
--- a/chrome/renderer/resources/dom_automation.js
+++ b/chrome/renderer/resources/dom_automation.js
@@ -15,8 +15,21 @@ var domAutomation = domAutomation || {};
// 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) {
@@ -51,20 +64,84 @@ var domAutomation = domAutomation || {};
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 {
- var result = [true, "", getConvertedValue(eval(javascript))];
+ sendCompletedResponse(eval(javascript));
}
catch (exception) {
- var message = exception.message;
- if (typeof message != "string")
- message = JSON.stringify(message);
- var result = [false, message, exception];
+ sendErrorResponse(exception);
}
- domAutomationController.sendJSON(JSON.stringify(result));
+ }
+
+ // 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|.
@@ -80,9 +157,18 @@ var domAutomation = domAutomation || {};
// 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 = [];
@@ -109,24 +195,49 @@ var domAutomation = domAutomation || {};
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);
+ // 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;
+ }
- for (var i = 0; i < frameNames.length; i++) {
- frame = frame.frames[frameNames[i]];
- if (typeof frame == "undefined" || !frame) {
- return null;
+ // 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 frame.document;
+ return true;
}
- domAutomation.findByXPath = function(context, xpath) {
+ // 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);
@@ -137,39 +248,107 @@ var domAutomation = domAutomation || {};
return elements;
}
- domAutomation.find1ByXPath = function(context, xpath) {
+ // 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;
}
- domAutomation.findBySelectors = function(context, selectors) {
+ // 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));
}
- domAutomation.find1BySelectors = function(context, 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);
}
- domAutomation.findByText = function(context, text) {
+ // 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 = domAutomation.findByXPath(context, xpath);
+ var elements = findByXPath(context, xpath);
// Limit to what is visible.
- var final_list = [];
- for (var i = 0; i < elements.length; i++) {
- if (domAutomation.isVisible(elements[i]))
- final_list.push(elements[i]);
+ 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);
}
- return final_list;
}
- domAutomation.find1ByText = function(context, text) {
- return domAutomation.findByText(context, text)[0];
+ 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) {
@@ -201,34 +380,39 @@ var domAutomation = domAutomation || {};
return false;
}
- domAutomation.getText = function(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 += domAutomation.getText(element.childNodes[i]);
- }
- return childrenText;
+ domAutomation.getProperty = function(element, property) {
+ return element[property];
}
- domAutomation.getInnerHTML = function(element) {
- return trim(element.innerHTML);
+ domAutomation.getAttribute = function(element, attribute) {
+ return element.getAttribute(attribute);
}
- domAutomation.isVisible = function(element) {
- while (element.style) {
- if (element.style.display == 'none' ||
- element.style.visibility == 'hidden' ||
- element.style.visibility == 'collapse') {
- return false;
- }
- element = element.parentNode;
+ domAutomation.getValue = function(element, type) {
+ if (type == "text") {
+ return getText(element);
+ } else if (type == "innerhtml") {
+ return trim(element.innerHTML);
+ } else if (type == "visibility") {
+ return isVisible(element);
+ } else if (type == "id") {
+ return element.id;
+ } else if (type == "contentdocument") {
+ return element.contentDocument;
}
- return true;
+ }
+
+ 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);
+ }
+ })();
}
})();
diff --git a/chrome/test/automation/dom_automation_browsertest.cc b/chrome/test/automation/dom_automation_browsertest.cc
index c861f89..85ef229 100644
--- a/chrome/test/automation/dom_automation_browsertest.cc
+++ b/chrome/test/automation/dom_automation_browsertest.cc
@@ -5,6 +5,7 @@
#include "base/ref_counted.h"
#include "base/utf_string_conversions.h"
#include "chrome/test/automation/dom_element_proxy.h"
+#include "chrome/test/automation/javascript_execution_controller.h"
#include "chrome/browser/browser.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
@@ -15,6 +16,7 @@ class DOMAutomationTest : public InProcessBrowserTest {
public:
DOMAutomationTest() {
EnableDOMAutomation();
+ JavaScriptExecutionController::set_timeout(30000);
}
GURL GetTestURL(const char* path) {
@@ -24,6 +26,8 @@ class DOMAutomationTest : public InProcessBrowserTest {
}
};
+typedef DOMElementProxy::By By;
+
IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindByXPath) {
StartHTTPServer();
ui_test_utils::NavigateToURL(browser(),
@@ -31,39 +35,39 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindByXPath) {
DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
// Find first element.
- DOMElementProxyRef first_div = main_doc->FindByXPath("//div");
+ DOMElementProxyRef first_div = main_doc->FindElement(By::XPath("//div"));
ASSERT_TRUE(first_div);
ASSERT_NO_FATAL_FAILURE(first_div->EnsureNameMatches("0"));
// Find many elements.
std::vector<DOMElementProxyRef> elements;
- ASSERT_TRUE(main_doc->FindByXPath("//div", &elements));
+ ASSERT_TRUE(main_doc->FindElements(By::XPath("//div"), &elements));
ASSERT_EQ(2u, elements.size());
for (size_t i = 0; i < elements.size(); i++)
ASSERT_NO_FATAL_FAILURE(elements[i]->EnsureNameMatches(UintToString(i)));
// Find 0 elements.
- ASSERT_FALSE(main_doc->FindByXPath("//nosuchtag"));
+ ASSERT_FALSE(main_doc->FindElement(By::XPath("//nosuchtag")));
elements.clear();
- ASSERT_TRUE(main_doc->FindByXPath("//nosuchtag", &elements));
+ ASSERT_TRUE(main_doc->FindElements(By::XPath("//nosuchtag"), &elements));
elements.clear();
ASSERT_EQ(0u, elements.size());
// Find with invalid xpath.
- ASSERT_FALSE(main_doc->FindByXPath("'invalid'"));
- ASSERT_FALSE(main_doc->FindByXPath(" / / "));
- ASSERT_FALSE(main_doc->FindByXPath("'invalid'", &elements));
- ASSERT_FALSE(main_doc->FindByXPath(" / / ", &elements));
+ ASSERT_FALSE(main_doc->FindElement(By::XPath("'invalid'")));
+ ASSERT_FALSE(main_doc->FindElement(By::XPath(" / / ")));
+ ASSERT_FALSE(main_doc->FindElements(By::XPath("'invalid'"), &elements));
+ ASSERT_FALSE(main_doc->FindElements(By::XPath(" / / "), &elements));
// Find nested elements.
int nested_count = 0;
std::string span_name;
- DOMElementProxyRef node = main_doc->FindByXPath("/html/body/span");
+ DOMElementProxyRef node = main_doc->FindElement(By::XPath("/html/body/span"));
while (node) {
nested_count++;
span_name.append("span");
ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name));
- node = node->FindByXPath("./span");
+ node = node->FindElement(By::XPath("./span"));
}
ASSERT_EQ(3, nested_count);
}
@@ -76,36 +80,36 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindBySelectors) {
// Find first element.
DOMElementProxyRef first_myclass =
- main_doc->FindBySelectors(".myclass");
+ main_doc->FindElement(By::Selectors(".myclass"));
ASSERT_TRUE(first_myclass);
ASSERT_NO_FATAL_FAILURE(first_myclass->EnsureNameMatches("0"));
// Find many elements.
std::vector<DOMElementProxyRef> elements;
- ASSERT_TRUE(main_doc->FindBySelectors(".myclass", &elements));
+ ASSERT_TRUE(main_doc->FindElements(By::Selectors(".myclass"), &elements));
ASSERT_EQ(2u, elements.size());
for (size_t i = 0; i < elements.size(); i++)
ASSERT_NO_FATAL_FAILURE(elements[i]->EnsureNameMatches(UintToString(i)));
// Find 0 elements.
- ASSERT_FALSE(main_doc->FindBySelectors("#nosuchid"));
+ ASSERT_FALSE(main_doc->FindElement(By::Selectors("#nosuchid")));
elements.clear();
- ASSERT_TRUE(main_doc->FindBySelectors("#nosuchid", &elements));
+ ASSERT_TRUE(main_doc->FindElements(By::Selectors("#nosuchid"), &elements));
ASSERT_EQ(0u, elements.size());
// Find with invalid selectors.
- ASSERT_FALSE(main_doc->FindBySelectors("1#2"));
- ASSERT_FALSE(main_doc->FindBySelectors("1#2", &elements));
+ ASSERT_FALSE(main_doc->FindElement(By::Selectors("1#2")));
+ ASSERT_FALSE(main_doc->FindElements(By::Selectors("1#2"), &elements));
// Find nested elements.
int nested_count = 0;
std::string span_name;
- DOMElementProxyRef node = main_doc->FindBySelectors("span");
+ DOMElementProxyRef node = main_doc->FindElement(By::Selectors("span"));
while (node) {
nested_count++;
span_name.append("span");
ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name));
- node = node->FindBySelectors("span");
+ node = node->FindElement(By::Selectors("span"));
}
ASSERT_EQ(3, nested_count);
}
@@ -117,43 +121,76 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindByText) {
DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
// Find first element.
- DOMElementProxyRef first_text = main_doc->FindByText("div_text");
+ DOMElementProxyRef first_text = main_doc->FindElement(By::Text("div_text"));
ASSERT_TRUE(first_text);
ASSERT_NO_FATAL_FAILURE(first_text->EnsureNameMatches("0"));
// Find many elements.
std::vector<DOMElementProxyRef> elements;
- ASSERT_TRUE(main_doc->FindByText("div_text", &elements));
+ ASSERT_TRUE(main_doc->FindElements(By::Text("div_text"), &elements));
ASSERT_EQ(2u, elements.size());
for (size_t i = 0; i < elements.size(); i++)
ASSERT_NO_FATAL_FAILURE(elements[i]->EnsureNameMatches(UintToString(i)));
// Find 0 elements.
- ASSERT_FALSE(main_doc->FindByText("nosuchtext"));
+ ASSERT_FALSE(main_doc->FindElement(By::Text("nosuchtext")));
elements.clear();
- ASSERT_TRUE(main_doc->FindByText("nosuchtext", &elements));
+ ASSERT_TRUE(main_doc->FindElements(By::Text("nosuchtext"), &elements));
ASSERT_EQ(0u, elements.size());
// Find nested elements.
int nested_count = 0;
std::string span_name;
- DOMElementProxyRef node = main_doc->FindByText("span_text");
+ DOMElementProxyRef node = main_doc->FindElement(By::Text("span_text"));
while (node) {
nested_count++;
span_name.append("span");
ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name));
- node = node->FindByText("span_text");
+ node = node->FindElement(By::Text("span_text"));
}
ASSERT_EQ(3, nested_count);
// Find only visible text.
- DOMElementProxyRef shown_td = main_doc->FindByText("table_text");
+ DOMElementProxyRef shown_td = main_doc->FindElement(By::Text("table_text"));
ASSERT_TRUE(shown_td);
ASSERT_NO_FATAL_FAILURE(shown_td->EnsureNameMatches("shown"));
// Find text in inputs.
- ASSERT_TRUE(main_doc->FindByText("textarea_text"));
- ASSERT_TRUE(main_doc->FindByText("input_text"));
+ ASSERT_TRUE(main_doc->FindElement(By::Text("textarea_text")));
+ ASSERT_TRUE(main_doc->FindElement(By::Text("input_text")));
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, WaitFor1VisibleElement) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(), GetTestURL("wait/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ DOMElementProxyRef div =
+ main_doc->WaitFor1VisibleElement(By::Selectors("div"));
+ ASSERT_TRUE(div.get());
+ ASSERT_NO_FATAL_FAILURE(div->EnsureInnerHTMLMatches("div_inner"));
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, WaitForElementsToDisappear) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(), GetTestURL("wait/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ ASSERT_TRUE(main_doc->WaitForElementsToDisappear(By::Selectors("img")));
+ std::vector<DOMElementProxyRef> img_elements;
+ ASSERT_TRUE(main_doc->FindElements(By::Selectors("img"), &img_elements));
+ ASSERT_EQ(0u, img_elements.size());
+}
+
+IN_PROC_BROWSER_TEST_F(DOMAutomationTest, EnsureAttributeEventuallyMatches) {
+ StartHTTPServer();
+ ui_test_utils::NavigateToURL(browser(), GetTestURL("wait/test.html"));
+ DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
+
+ DOMElementProxyRef anchor = main_doc->FindElement(By::Selectors("a"));
+ ASSERT_TRUE(anchor.get());
+ ASSERT_NO_FATAL_FAILURE(anchor->EnsureAttributeEventuallyMatches(
+ "href", "http://www.google.com"));
}
IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Frames) {
@@ -163,17 +200,18 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Frames) {
// Get both frame elements.
std::vector<DOMElementProxyRef> frame_elements;
- ASSERT_TRUE(main_doc->FindByXPath("//frame", &frame_elements));
+ ASSERT_TRUE(main_doc->FindElements(By::XPath("//frame"), &frame_elements));
ASSERT_EQ(2u, frame_elements.size());
// Get both frames, checking their contents are correct.
DOMElementProxyRef frame1 = frame_elements[0]->GetContentDocument();
DOMElementProxyRef frame2 = frame_elements[1]->GetContentDocument();
ASSERT_TRUE(frame1 && frame2);
- DOMElementProxyRef frame_div = frame1->FindByXPath("/html/body/div");
+ DOMElementProxyRef frame_div =
+ frame1->FindElement(By::XPath("/html/body/div"));
ASSERT_TRUE(frame_div);
ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("frame 1"));
- frame_div = frame2->FindByXPath("/html/body/div");
+ frame_div = frame2->FindElement(By::XPath("/html/body/div"));
ASSERT_TRUE(frame_div);
ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("frame 2"));
@@ -183,10 +221,10 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Frames) {
DOMElementProxyRef iframe2 =
frame2->GetDocumentFromFrame("0");
ASSERT_TRUE(iframe1 && iframe2);
- frame_div = iframe1->FindByXPath("/html/body/div");
+ frame_div = iframe1->FindElement(By::XPath("/html/body/div"));
ASSERT_TRUE(frame_div);
ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("iframe 1"));
- frame_div = iframe2->FindByXPath("/html/body/div");
+ frame_div = iframe2->FindElement(By::XPath("/html/body/div"));
ASSERT_TRUE(frame_div);
ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("iframe 2"));
@@ -201,13 +239,14 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Events) {
DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
// Click link and make sure text changes.
- DOMElementProxyRef link = main_doc->FindBySelectors("a");
+ DOMElementProxyRef link = main_doc->FindElement(By::Selectors("a"));
ASSERT_TRUE(link && link->Click());
ASSERT_NO_FATAL_FAILURE(link->EnsureTextMatches("clicked"));
// Click input button and make sure textfield changes.
- DOMElementProxyRef button = main_doc->FindBySelectors("#button");
- DOMElementProxyRef textfield = main_doc->FindBySelectors("#textfield");
+ DOMElementProxyRef button = main_doc->FindElement(By::Selectors("#button"));
+ DOMElementProxyRef textfield =
+ main_doc->FindElement(By::Selectors("#textfield"));
ASSERT_TRUE(textfield && button && button->Click());
ASSERT_NO_FATAL_FAILURE(textfield->EnsureTextMatches("clicked"));
@@ -216,7 +255,8 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Events) {
ASSERT_NO_FATAL_FAILURE(textfield->EnsureTextMatches("test"));
// Type in the textarea.
- DOMElementProxyRef textarea = main_doc->FindBySelectors("textarea");
+ DOMElementProxyRef textarea =
+ main_doc->FindElement(By::Selectors("textarea"));
ASSERT_TRUE(textarea && textarea->Type("test"));
ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches("textareatest"));
}
@@ -227,7 +267,8 @@ IN_PROC_BROWSER_TEST_F(DOMAutomationTest, StringEscape) {
GetTestURL("string_escape/test.html"));
DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser());
- DOMElementProxyRef textarea = main_doc->FindBySelectors("textarea");
+ DOMElementProxyRef textarea =
+ main_doc->FindElement(By::Selectors("textarea"));
ASSERT_TRUE(textarea);
ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches(WideToUTF8(L"\u00FF")));
diff --git a/chrome/test/automation/dom_element_proxy.cc b/chrome/test/automation/dom_element_proxy.cc
index c7bc6da..feeeebf 100644
--- a/chrome/test/automation/dom_element_proxy.cc
+++ b/chrome/test/automation/dom_element_proxy.cc
@@ -4,61 +4,63 @@
#include "chrome/test/automation/dom_element_proxy.h"
-#include "base/json/string_escape.h"
-#include "base/string_util.h"
-#include "base/utf_string_conversions.h"
+#include "chrome/test/automation/javascript_execution_controller.h"
+#include "chrome/test/automation/javascript_message_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
-namespace {
+using javascript_utils::JavaScriptPrintf;
-// Convenience wrapper for GetDoubleQuotedJson function.
-std::string GetDoubleQuotedJson(std::string utf8_string) {
- return base::GetDoubleQuotedJson(UTF8ToUTF16(utf8_string));
-}
+// JavaScriptObjectProxy methods
+JavaScriptObjectProxy::JavaScriptObjectProxy(
+ JavaScriptExecutionController* executor, int handle)
+ : executor_(executor->AsWeakPtr()), handle_(handle) {}
-} // namespace
+JavaScriptObjectProxy::~JavaScriptObjectProxy() {
+ if (is_valid())
+ executor_->Remove(handle_);
+}
-DOMElementProxyRef DOMElementProxy::GetContentDocument() {
- const char* script = "%s.contentDocument;";
- DOMElementProxy* element = NULL;
- executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str()), &element);
- return element;
+// DOMElementProxy::By methods
+// static
+DOMElementProxy::By DOMElementProxy::By::XPath(
+ const std::string& xpath) {
+ return By(TYPE_XPATH, xpath);
}
-DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
- const std::vector<std::string>& frame_names) {
- if (!is_valid())
- return NULL;
+// static
+DOMElementProxy::By DOMElementProxy::By::Selectors(
+ const std::string& selectors) {
+ return By(TYPE_SELECTORS, selectors);
+}
- const char* script = "domAutomation.getDocumentFromFrame(%s, %s);";
- std::string string_script = StringPrintf(
- script, this->GetReferenceJavaScript().c_str(),
- JavaScriptExecutionController::Serialize(frame_names).c_str());
- DOMElementProxy* element = NULL;
- executor_->ExecuteJavaScriptAndParse(string_script, &element);
- return element;
+// static
+DOMElementProxy::By DOMElementProxy::By::Text(
+ const std::string& text) {
+ return By(TYPE_TEXT, text);
}
-DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
- const std::string& frame_name) {
- if (!is_valid())
+// DOMElementProxy methods
+DOMElementProxyRef DOMElementProxy::GetContentDocument() {
+ int element_handle;
+ if (!GetValue("contentdocument", &element_handle))
return NULL;
-
- std::vector<std::string> frame_names;
- frame_names.push_back(frame_name);
- return GetDocumentFromFrame(frame_names);
+ return executor_->GetObjectProxy<DOMElementProxy>(element_handle);
}
DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
- const std::string& frame_name1, const std::string& frame_name2) {
+ const std::vector<std::string>& frame_names) {
if (!is_valid())
return NULL;
- std::vector<std::string> frame_names;
- frame_names.push_back(frame_name1);
- frame_names.push_back(frame_name2);
- return GetDocumentFromFrame(frame_names);
+ const char* script = "domAutomation.getDocumentFromFrame("
+ "domAutomation.getObject(%s), %s);";
+ int element_handle;
+ if (!executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), frame_names),
+ &element_handle)) {
+ return NULL;
+ }
+ return executor_->GetObjectProxy<DOMElementProxy>(element_handle);
}
DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
@@ -69,137 +71,115 @@ DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame(
std::vector<std::string> frame_names;
frame_names.push_back(frame_name1);
- frame_names.push_back(frame_name2);
- frame_names.push_back(frame_name3);
+ if (!frame_name2.empty())
+ frame_names.push_back(frame_name2);
+ if (!frame_name3.empty())
+ frame_names.push_back(frame_name3);
return GetDocumentFromFrame(frame_names);
}
-bool DOMElementProxy::FindByXPath(const std::string& xpath,
- std::vector<DOMElementProxyRef>* elements) {
- DCHECK(elements);
- if (!is_valid())
- return false;
-
- const char* script = "domAutomation.findByXPath(%s, %s);";
- std::vector<DOMElementProxy*> element_pointers;
- if (!executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(xpath).c_str()),
- &element_pointers))
- return false;
- for (size_t i = 0; i < element_pointers.size(); i++)
- elements->push_back(element_pointers[i]);
- return true;
-}
-
-DOMElementProxyRef DOMElementProxy::FindByXPath(const std::string& xpath) {
+DOMElementProxyRef DOMElementProxy::FindElement(const By& by) {
if (!is_valid())
return NULL;
- const char* script = "domAutomation.find1ByXPath(%s, %s);";
- DOMElementProxy* element = NULL;
- executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(xpath).c_str()),
- &element);
- return element;
+ const char* script = "domAutomation.findElement("
+ "domAutomation.getObject(%s), %s);";
+ int element_handle;
+ if (!executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), by), &element_handle)) {
+ return NULL;
+ }
+ return executor_->GetObjectProxy<DOMElementProxy>(element_handle);
}
-bool DOMElementProxy::FindBySelectors(
- const std::string& selectors, std::vector<DOMElementProxyRef>* elements) {
+bool DOMElementProxy::FindElements(const By& by,
+ std::vector<DOMElementProxyRef>* elements) {
DCHECK(elements);
if (!is_valid())
return false;
- const char* script = "domAutomation.findBySelectors(%s, %s);";
- std::vector<DOMElementProxy*> element_pointers;
- if (!executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(selectors).c_str()),
- &element_pointers))
+ const char* script = "domAutomation.findElements("
+ "domAutomation.getObject(%s), %s);";
+ std::vector<int> element_handles;
+ if (!executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), by), &element_handles)) {
return false;
- for (size_t i = 0; i < element_pointers.size(); i++)
- elements->push_back(element_pointers[i]);
+ }
+ for (size_t i = 0; i < element_handles.size(); i++) {
+ elements->push_back(executor_->GetObjectProxy<DOMElementProxy>(
+ element_handles[i]));
+ }
return true;
}
-DOMElementProxyRef DOMElementProxy::FindBySelectors(
- const std::string& selectors) {
- if (!is_valid())
- return NULL;
-
- const char* script = "domAutomation.find1BySelectors(%s, %s);";
- DOMElementProxy* element = NULL;
- executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(selectors).c_str()),
- &element);
- return element;
-}
-
-bool DOMElementProxy::FindByText(const std::string& text,
- std::vector<DOMElementProxyRef>* elements) {
+bool DOMElementProxy::WaitForVisibleElementCount(
+ const By& by, int count, std::vector<DOMElementProxyRef>* elements) {
DCHECK(elements);
if (!is_valid())
return false;
- const char* script = "domAutomation.findByText(%s, %s);";
- std::vector<DOMElementProxy*> element_pointers;
- if (!executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(text).c_str()),
- &element_pointers))
+ const char* script = "domAutomation.waitForVisibleElementCount("
+ "domAutomation.getObject(%s), %s, %s,"
+ "domAutomation.getCallId());";
+ std::vector<int> element_handles;
+ if (!executor_->ExecuteAsyncJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), by, count), &element_handles)) {
return false;
- for (size_t i = 0; i < element_pointers.size(); i++)
- elements->push_back(element_pointers[i]);
- return true;
+ }
+ if (static_cast<int>(element_handles.size()) == count) {
+ for (size_t i = 0; i < element_handles.size(); i++) {
+ elements->push_back(executor_->GetObjectProxy<DOMElementProxy>(
+ element_handles[i]));
+ }
+ }
+ return static_cast<int>(element_handles.size()) == count;
}
-DOMElementProxyRef DOMElementProxy::FindByText(const std::string& text) {
- if (!is_valid())
+DOMElementProxyRef DOMElementProxy::WaitFor1VisibleElement(const By& by) {
+ std::vector<DOMElementProxyRef> elements;
+ if (!WaitForVisibleElementCount(by, 1, &elements))
return NULL;
+ return elements[0];
+}
- const char* script = "domAutomation.find1ByText(%s, %s);";
- DOMElementProxy* element = NULL;
- executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(text).c_str()),
- &element);
- return element;
+bool DOMElementProxy::WaitForElementsToDisappear(const By& by) {
+ std::vector<DOMElementProxyRef> elements;
+ return WaitForVisibleElementCount(by, 0, &elements);
}
bool DOMElementProxy::Click() {
- const char* script = "domAutomation.click(%s);";
if (!is_valid())
return false;
+ const char* script = "domAutomation.click(domAutomation.getObject(%s));";
return executor_->ExecuteJavaScript(
- StringPrintf(script, this->GetReferenceJavaScript().c_str()));
+ JavaScriptPrintf(script, this->handle()));
}
bool DOMElementProxy::Type(const std::string& text) {
- const char* script = "domAutomation.type(%s, %s);";
if (!is_valid())
return false;
+ const char* script = "domAutomation.type(domAutomation.getObject(%s), %s);";
bool success = false;
- executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(text).c_str()),
- &success);
+ if (!executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), text), &success)) {
+ return false;
+ }
return success;
}
bool DOMElementProxy::SetText(const std::string& text) {
- const char* script = "domAutomation.setText(%s, %s);";
if (!is_valid())
return false;
+ const char* script = "domAutomation.setText("
+ "domAutomation.getObject(%s), %s);";
bool success = false;
- executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(text).c_str()),
- &success);
+ if (!executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), text), &success)) {
+ return false;
+ }
return success;
}
@@ -209,11 +189,10 @@ bool DOMElementProxy::GetProperty(const std::string& property,
if (!is_valid())
return false;
- const char* script = "%s.%s;";
- return executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(property).c_str()),
- out);
+ const char* script = "domAutomation.getProperty("
+ "domAutomation.getObject(%s), %s);";
+ return executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), property), out);
}
bool DOMElementProxy::GetAttribute(const std::string& attribute,
@@ -222,41 +201,22 @@ bool DOMElementProxy::GetAttribute(const std::string& attribute,
if (!is_valid())
return false;
- const char* script = "%s.getAttribute(%s);";
- return executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str(),
- GetDoubleQuotedJson(attribute).c_str()),
- out);
+ const char* script = "domAutomation.getAttribute("
+ "domAutomation.getObject(%s), %s);";
+ return executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), attribute), out);
}
bool DOMElementProxy::GetText(std::string* text) {
- DCHECK(text);
- if (!is_valid())
- return false;
-
- const char* script = "domAutomation.getText(%s);";
- return executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str()), text);
+ return GetValue("text", text);
}
bool DOMElementProxy::GetInnerHTML(std::string* html) {
- DCHECK(html);
- if (!is_valid())
- return false;
-
- const char* script = "domAutomation.getInnerHTML(%s);";
- return executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str()), html);
+ return GetValue("innerhtml", html);
}
bool DOMElementProxy::GetId(std::string* id) {
- DCHECK(id);
- if (!is_valid())
- return false;
-
- const char* script = "%s.id;";
- return executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str()), id);
+ return GetValue("id", id);
}
bool DOMElementProxy::GetName(std::string* name) {
@@ -264,14 +224,13 @@ bool DOMElementProxy::GetName(std::string* name) {
}
bool DOMElementProxy::GetVisibility(bool* visibility) {
- DCHECK(visibility);
- if (!is_valid())
- return false;
+ return GetValue("visibility", visibility);
+}
- const char* script = "domAutomation.isVisible(%s);";
- return executor_->ExecuteJavaScriptAndParse(
- StringPrintf(script, this->GetReferenceJavaScript().c_str()),
- visibility);
+void DOMElementProxy::EnsureFindNoElements(const By& by) {
+ std::vector<DOMElementProxyRef> elements;
+ ASSERT_TRUE(FindElements(by, &elements));
+ ASSERT_EQ(0u, elements.size());
}
void DOMElementProxy::EnsureTextMatches(const std::string& expected_text) {
@@ -297,3 +256,28 @@ void DOMElementProxy::EnsureVisibilityMatches(bool expected_visibility) {
ASSERT_TRUE(GetVisibility(&visibility));
ASSERT_EQ(expected_visibility, visibility);
}
+
+void DOMElementProxy::EnsureAttributeEventuallyMatches(
+ const std::string& attribute, const std::string& new_value) {
+ ASSERT_TRUE(is_valid());
+
+ const char* script = "domAutomation.waitForAttribute("
+ "domAutomation.getObject(%s), %s, %s,"
+ "domAutomation.getCallId())";
+ if (!executor_->ExecuteAsyncJavaScript(
+ JavaScriptPrintf(script, this->handle(), attribute, new_value))) {
+ FAIL() << "Executing or parsing JavaScript failed";
+ }
+}
+
+template <typename T>
+bool DOMElementProxy::GetValue(const std::string& type, T* out) {
+ DCHECK(out);
+ if (!is_valid())
+ return false;
+
+ const char* script = "domAutomation.getValue("
+ "domAutomation.getObject(%s), %s);";
+ return executor_->ExecuteJavaScriptAndGetReturn(
+ JavaScriptPrintf(script, this->handle(), type), out);
+}
diff --git a/chrome/test/automation/dom_element_proxy.h b/chrome/test/automation/dom_element_proxy.h
index 2057e0c0..06d2af3 100644
--- a/chrome/test/automation/dom_element_proxy.h
+++ b/chrome/test/automation/dom_element_proxy.h
@@ -9,17 +9,70 @@
#include <vector>
#include "base/ref_counted.h"
-#include "chrome/test/automation/javascript_execution_controller.h"
+#include "base/weak_ptr.h"
class DOMElementProxy;
+class JavaScriptExecutionController;
typedef scoped_refptr<DOMElementProxy> DOMElementProxyRef;
+// This class is a proxy to an object in JavaScript. It holds a handle which
+// can be used to retrieve the actual object in JavaScript scripts.
+class JavaScriptObjectProxy
+ : public base::RefCountedThreadSafe<JavaScriptObjectProxy> {
+ public:
+ JavaScriptObjectProxy(JavaScriptExecutionController* executor, int handle);
+ ~JavaScriptObjectProxy();
+
+ int handle() const { return handle_; }
+ bool is_valid() const { return executor_.get() != NULL; }
+
+ protected:
+ base::WeakPtr<JavaScriptExecutionController> executor_;
+ int handle_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(JavaScriptObjectProxy);
+};
+
// This class presents the interface to actions that can be performed on
// a given DOM element. Note that this object can be invalidated at any
// time. In that case, any subsequent calls will return false immediately.
+// This class should never be instantiated directly, except by a
+// JavaScriptExecutionController.
class DOMElementProxy : public JavaScriptObjectProxy {
public:
+ // This class represents the various methods by which elements are located
+ // in the DOM.
+ class By {
+ public:
+ enum ByType {
+ TYPE_XPATH,
+ TYPE_SELECTORS,
+ TYPE_TEXT
+ };
+
+ // Returns a By for locating an element using an XPath query.
+ static By XPath(const std::string& xpath);
+
+ // Returns a By for locating an element using CSS selectors.
+ static By Selectors(const std::string& selectors);
+
+ // Returns a By for locating an element by its contained text. For inputs
+ // and textareas, this includes the element's value.
+ static By Text(const std::string& text);
+
+ ByType type() const { return type_; }
+ std::string query() const { return query_; }
+
+ private:
+ By(ByType type, const std::string& query)
+ : type_(type), query_(query) {}
+
+ ByType type_;
+ std::string query_;
+ };
+
DOMElementProxy(JavaScriptExecutionController* executor, int handle)
: JavaScriptObjectProxy(executor, handle) {}
@@ -36,44 +89,39 @@ class DOMElementProxy : public JavaScriptObjectProxy {
const std::vector<std::string>& frame_names);
// Same as above but with different argument for convenience.
- DOMElementProxyRef GetDocumentFromFrame(const std::string& frame_name);
-
- // Same as above but with different argument for convenience.
- DOMElementProxyRef GetDocumentFromFrame(const std::string& frame_name1,
- const std::string& frame_name2);
-
- // Same as above but with different argument for convenience.
DOMElementProxyRef GetDocumentFromFrame(const std::string& frame_name1,
- const std::string& frame_name2, const std::string& frame_name3);
-
- // Adds the elements from this element's descendants that satisfy the
- // XPath query |xpath| to the vector |elements|.
- // Returns true on success.
- bool FindByXPath(const std::string& xpath,
- std::vector<DOMElementProxyRef>* elements);
-
- // Same as above, but returns the first element, or NULL if none.
- DOMElementProxyRef FindByXPath(const std::string& xpath);
-
- // Adds the elements from this element's descendants that match the
- // CSS Selectors |selectors| to the vector |elements|.
- // Returns true on success.
- bool FindBySelectors(const std::string& selectors,
- std::vector<DOMElementProxyRef>* elements);
-
- // Same as above, but returns the first element, or NULL if none.
- DOMElementProxyRef FindBySelectors(const std::string& selectors);
-
- // Adds the elements from this element's descendants which have text that
- // matches |text|. This includes text from input elements.
- // Returns true on success.
- bool FindByText(const std::string& text,
- std::vector<DOMElementProxyRef>* elements);
-
- // Same as above, but returns the first element, or NULL if none.
- DOMElementProxyRef FindByText(const std::string& text);
-
- // Dispatches a click MouseEvent to the element and all its parents.
+ const std::string& frame_name2 = "",
+ const std::string& frame_name3 = "");
+
+
+ // Finds the first element found by the given locator method |by|, or NULL
+ // if no element was found.
+ DOMElementProxyRef FindElement(const By& by);
+
+ // Finds all the elements found by the given locator method and appends
+ // them to the given list. Returns true on success.
+ bool FindElements(const By& by,
+ std::vector<DOMElementProxyRef>* elements);
+
+ // Waits until the number of visible elements satisfying the given locator
+ // method |by| equals |count|, and appends them to the given list. Returns
+ // true when |count| matches the number of visible elements or false if
+ // the timeout is exceeded while waiting. If false, the list is not modified.
+ bool WaitForVisibleElementCount(const By& by, int count,
+ std::vector<DOMElementProxyRef>* elements);
+
+ // Waits until exactly 1 element is visible which satisifies the given
+ // locator method. Returns the found element, or NULL if the timeout is
+ // exceeded. If it is possible for more than 1 element to safisfy the query,
+ // use WaitForVisibleElementCount instead.
+ DOMElementProxyRef WaitFor1VisibleElement(const By& by);
+
+ // Waits until no visible elements satisify the given locator method.
+ // Returns true when no more visible elements are found or false if the
+ // timeout is exceeded while waiting.
+ bool WaitForElementsToDisappear(const By& by);
+
+ // Dispatches a click MouseEvent to this element and all its parents.
// Returns true on success.
bool Click();
@@ -109,6 +157,9 @@ class DOMElementProxy : public JavaScriptObjectProxy {
// Retrieves the element's visibility. Returns true on success.
bool GetVisibility(bool* visilibity);
+ // Ensures that no elements can be found by the given locator method.
+ void EnsureFindNoElements(const By& by);
+
// Asserts that |expected_text| matches all the text in this element. This
// includes the value of textfields and inputs.
void EnsureTextMatches(const std::string& expected_text);
@@ -121,6 +172,20 @@ class DOMElementProxy : public JavaScriptObjectProxy {
// Asserts that |expected_visibility| matches the element's visibility.
void EnsureVisibilityMatches(bool expected_visibility);
+
+ // Asserts that |expected_value| eventually matches the element's value for
+ // |attribute|. This function will block until the timeout is exceeded, in
+ // which case it will fail, or until the two values match.
+ void EnsureAttributeEventuallyMatches(const std::string& attribute,
+ const std::string& expected_value);
+
+ private:
+ // Gets the element's value for the given type. This is a helper method
+ // for simple get methods.
+ template <typename T>
+ bool GetValue(const std::string& type, T* out);
+
+ DISALLOW_COPY_AND_ASSIGN(DOMElementProxy);
};
#endif // CHROME_TEST_AUTOMATION_DOM_ELEMENT_PROXY_H_
diff --git a/chrome/test/automation/javascript_execution_controller.cc b/chrome/test/automation/javascript_execution_controller.cc
index 3cd0753..f743c9e 100644
--- a/chrome/test/automation/javascript_execution_controller.cc
+++ b/chrome/test/automation/javascript_execution_controller.cc
@@ -4,76 +4,75 @@
#include "chrome/test/automation/javascript_execution_controller.h"
-#include "base/json/string_escape.h"
-#include "base/string_util.h"
-#include "base/utf_string_conversions.h"
#include "chrome/common/json_value_serializer.h"
+#include "chrome/test/automation/javascript_message_utils.h"
-using base::GetDoubleQuotedJson;
+using javascript_utils::JavaScriptPrintf;
-// JavaScriptObjectProxy methods
-JavaScriptObjectProxy::JavaScriptObjectProxy(
- JavaScriptExecutionController* executor, int handle)
- : executor_(executor->AsWeakPtr()), handle_(handle) {}
+// Initialize this timeout to an invalid value. Each test framework or test
+// must set an appropriate timeout using set_timeout, or the
+// JavaScriptExecutionController will complain.
+int JavaScriptExecutionController::timeout_ms_ = -1;
-JavaScriptObjectProxy::~JavaScriptObjectProxy() {
- if (is_valid())
- executor_->Remove(handle_);
-}
-
-std::string JavaScriptObjectProxy::GetReferenceJavaScript() {
- return JavaScriptExecutionController::GetReferenceJavaScript(this);
-}
-
-// JavaScriptExecutionController methods
bool JavaScriptExecutionController::ExecuteJavaScript(
const std::string& script) {
- std::string json;
- return ExecuteJavaScript(script, &json);
-}
-
-// static
-std::string JavaScriptExecutionController::GetReferenceJavaScript(
- JavaScriptObjectProxy* object) {
- return StringPrintf("domAutomation.getObject(%d)", object->handle());
+ scoped_ptr<Value> return_value;
+ return ExecuteAndParseHelper(WrapJavaScript(script), &return_value);
}
-// static
-std::string JavaScriptExecutionController::Serialize(
- const std::vector<std::string>& vector) {
- std::string javascript = "[";
- for (size_t i = 0; i < vector.size(); i++) {
- javascript.append(GetDoubleQuotedJson(UTF8ToUTF16(vector[i])));
- if (i < vector.size() - 1)
- javascript.append(",");
- }
- javascript.append("]");
- return javascript;
+bool JavaScriptExecutionController::ExecuteAsyncJavaScript(
+ const std::string& script) {
+ scoped_ptr<Value> return_value;
+ return ExecuteAndParseHelper(WrapAsyncJavaScript(script), &return_value);
}
void JavaScriptExecutionController::Remove(int handle) {
- ExecuteJavaScript(StringPrintf("domAutomation.removeObject(%d);", handle));
handle_to_object_.erase(handle);
if (handle_to_object_.empty())
LastObjectRemoved();
}
-bool JavaScriptExecutionController::ExecuteJavaScript(
- const std::string& original_script, std::string* json) {
- std::string script =
+std::string JavaScriptExecutionController::WrapJavaScript(
+ const std::string& original_script) {
+ const char* script =
"domAutomationController.setAutomationId(0);"
- "domAutomation.evaluateJavaScript(";
- script.append(GetDoubleQuotedJson(UTF8ToUTF16(original_script)));
- script.append(");");
- return ExecuteJavaScriptAndGetJSON(script, json);
+ "domAutomation.evaluateJavaScript(%s);";
+ return JavaScriptPrintf(script, original_script);
}
-bool JavaScriptExecutionController::ParseJSON(const std::string& json,
- scoped_ptr<Value>* result) {
+std::string JavaScriptExecutionController::WrapAsyncJavaScript(
+ const std::string& original_script) {
+ if (timeout_ms_ == -1) {
+ NOTREACHED() << "Timeout for asynchronous JavaScript methods has not been "
+ << "set. Please use JavaScriptExecutionController::"
+ << "set_timeout(timeout_in_ms).";
+ }
+ const char* script =
+ "domAutomationController.setAutomationId(0);"
+ "domAutomation.evaluateAsyncJavaScript(%s, %s);";
+ return JavaScriptPrintf(script, original_script, timeout_ms_);
+}
+
+bool JavaScriptExecutionController::ExecuteAndParseHelper(
+ const std::string& script, scoped_ptr<Value>* result) {
+ std::string json;
+ if (!ExecuteJavaScriptAndGetJSON(script, &json)) {
+ LOG(ERROR) << "JavaScript either did not execute or did not respond.";
+ return false;
+ }
+
+ // Deserialize the json to a Value.
JSONStringValueSerializer parse(json);
std::string parsing_error;
scoped_ptr<Value> root_value(parse.Deserialize(NULL, &parsing_error));
+ // Parse the response.
+ // The response must be a list of 3 components:
+ // - success (boolean): whether the javascript was evaluated with no errors
+ // - error (string): the evaluation error message or the empty string if
+ // no error occurred
+ // - result (string): the result of the evaluation (in JSON), or the
+ // exact error if an error occurred (in JSON)
if (!root_value.get()) {
if (parsing_error.length())
LOG(ERROR) << "Cannot parse JSON response: " << parsing_error;
@@ -82,12 +81,6 @@ bool JavaScriptExecutionController::ParseJSON(const std::string& json,
return false;
}
- // The response must be a list of 3 components:
- // -success(boolean): whether the javascript was evaluated with no errors
- // -error(string): the evaluation error message or the empty string if
- // no error occurred
- // -result(string): the result of the evaluation (in JSON), or the
- // exact error if an error occurred (in JSON)
bool success;
std::string evaluation_error;
Value* evaluation_result_value;
@@ -103,25 +96,10 @@ bool JavaScriptExecutionController::ParseJSON(const std::string& json,
return false;
}
if (!success) {
- LOG(WARNING) << "JavaScript evaluation did not complete successfully."
+ LOG(WARNING) << "JavaScript evaluation did not complete successfully: "
<< evaluation_error;
return false;
}
result->reset(evaluation_result_value);
return true;
}
-
-bool JavaScriptExecutionController::ConvertResponse(Value* value,
- bool* result) {
- return value->GetAsBoolean(result);
-}
-
-bool JavaScriptExecutionController::ConvertResponse(Value* value,
- int* result) {
- return value->GetAsInteger(result);
-}
-
-bool JavaScriptExecutionController::ConvertResponse(Value* value,
- std::string* result) {
- return value->GetAsString(result);
-}
diff --git a/chrome/test/automation/javascript_execution_controller.h b/chrome/test/automation/javascript_execution_controller.h
index 0472eee..745f90f 100644
--- a/chrome/test/automation/javascript_execution_controller.h
+++ b/chrome/test/automation/javascript_execution_controller.h
@@ -7,38 +7,13 @@
#include <map>
#include <string>
-#include <vector>
-#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "base/values.h"
#include "base/weak_ptr.h"
-#include "testing/gtest/include/gtest/gtest_prod.h"
+#include "chrome/test/automation/javascript_message_utils.h"
-class JavaScriptExecutionController;
-
-// This class is a proxy to an object in JavaScript. It holds a handle which
-// can be used to retrieve the actual object in JavaScript scripts.
-class JavaScriptObjectProxy
- : public base::RefCountedThreadSafe<JavaScriptObjectProxy> {
- public:
- JavaScriptObjectProxy(JavaScriptExecutionController* executor, int handle);
- virtual ~JavaScriptObjectProxy();
-
- // Returns JavaScript which can be used for retrieving the actual object
- // associated with this proxy.
- std::string GetReferenceJavaScript();
-
- int handle() const { return handle_; }
- bool is_valid() const { return executor_; }
-
- protected:
- base::WeakPtr<JavaScriptExecutionController> executor_;
- int handle_;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(JavaScriptObjectProxy);
-};
+class JavaScriptObjectProxy;
// This class handles the execution of arbitrary JavaScript, preparing it for
// execution, and parsing its result (in JSON). It keeps track of all returned
@@ -49,29 +24,55 @@ class JavaScriptExecutionController
JavaScriptExecutionController() {}
virtual ~JavaScriptExecutionController() {}
- // Executes |script| and parse return value.
- // A corresponding ConvertResponse(Value* value, T* result) must exist
- // for type T.
+ // Executes |script| and parse the return value. Returns whether the
+ // execution and parsing succeeded.
template <typename T>
- bool ExecuteJavaScriptAndParse(const std::string& script, T* result) {
- std::string json;
- if (!ExecuteJavaScript(script, &json))
- return false;
- scoped_ptr<Value> value;
- if (!ParseJSON(json, &value))
+ bool ExecuteJavaScriptAndGetReturn(const std::string& script, T* result) {
+ scoped_ptr<Value> returnValue;
+ if (!ExecuteAndParseHelper(WrapJavaScript(script), &returnValue))
return false;
- return ConvertResponse(value.get(), result);
+ return ValueConversionTraits<T>::SetFromValue(returnValue.get(), result);
}
- // Executes |script| with no return.
+ // Similar to above, except that it does not get the return value.
bool ExecuteJavaScript(const std::string& script);
- // Returns JavaScript which can be used for retrieving the actual object
- // associated with the proxy |object|.
- static std::string GetReferenceJavaScript(JavaScriptObjectProxy* object);
+ // Executes |script|, waits for it to send a JSON response, and parses the
+ // return value. This call itself blocks, but the JavaScript responds
+ // asynchronously. Returns whether the execution and parsing succeeded.
+ // Will return false on timeouts.
+ template <typename T>
+ bool ExecuteAsyncJavaScriptAndGetReturn(const std::string& script,
+ T* result) {
+ scoped_ptr<Value> returnValue;
+ if (!ExecuteAndParseHelper(WrapAsyncJavaScript(script), &returnValue))
+ return false;
+ return ValueConversionTraits<T>::SetFromValue(returnValue.get(), result);
+ }
+
+ // Similar to above, except that it does not get the return value.
+ bool ExecuteAsyncJavaScript(const std::string& script);
+
+ // Returns the proxy associated with |handle|, creating one if necessary.
+ // The proxy must be a type of JavaScriptObjectProxy.
+ template<class JavaScriptObject>
+ JavaScriptObject* GetObjectProxy(int handle) {
+ JavaScriptObject* obj = NULL;
+ HandleToObjectMap::const_iterator iter = handle_to_object_.find(handle);
+ if (iter == handle_to_object_.end()) {
+ obj = new JavaScriptObject(this, handle);
+ if (handle_to_object_.empty())
+ FirstObjectAdded();
+ handle_to_object_.insert(std::make_pair(handle, obj));
+ } else {
+ obj = static_cast<JavaScriptObject*>(iter->second);
+ }
+ return obj;
+ }
- // Returns the equivalent JSON for |vector|.
- static std::string Serialize(const std::vector<std::string>& vector);
+ // Sets a timeout to be used for all JavaScript methods in which a response
+ // is returned asynchronously.
+ static void set_timeout(int timeout_ms) { timeout_ms_ = timeout_ms; }
protected:
// Executes |script| and sets the JSON response |json|. Returns true
@@ -91,51 +92,25 @@ class JavaScriptExecutionController
typedef std::map<int, JavaScriptObjectProxy*> HandleToObjectMap;
friend class JavaScriptObjectProxy;
- // Called by JavaScriptObjectProxy on destruct.
+ // Called by JavaScriptObjectProxy on destruction.
void Remove(int handle);
- bool ParseJSON(const std::string& json, scoped_ptr<Value>* result);
-
- bool ExecuteJavaScript(const std::string& script, std::string* json);
+ // Helper method for executing JavaScript and parsing the JSON response.
+ // If successful, returns true and sets |returnValue| as the script's return
+ // value.
+ bool ExecuteAndParseHelper(const std::string& script,
+ scoped_ptr<Value>* returnValue);
- bool ConvertResponse(Value* value, bool* result);
- bool ConvertResponse(Value* value, int* result);
- bool ConvertResponse(Value* value, std::string* result);
+ // Returns |script| wrapped and prepared for proper JavaScript execution,
+ // via the JavaScript function domAutomation.evaluateJavaScript.
+ std::string WrapJavaScript(const std::string& script);
- template<class JavaScriptObject>
- bool ConvertResponse(Value* value, JavaScriptObject** result) {
- int handle;
- if (!value->GetAsInteger(&handle))
- return false;
+ // Returns |script| wrapped and prepared for proper JavaScript execution
+ // via the JavaScript function domAutomation.evaluateAsyncJavaScript.
+ std::string WrapAsyncJavaScript(const std::string& script);
- HandleToObjectMap::const_iterator iter = handle_to_object_.find(handle);
- if (iter == handle_to_object_.end()) {
- *result = new JavaScriptObject(this, handle);
- if (handle_to_object_.empty())
- FirstObjectAdded();
- handle_to_object_.insert(std::make_pair(handle, *result));
- } else {
- *result = static_cast<JavaScriptObject*>(iter->second);
- }
- return true;
- }
-
- template<typename T>
- bool ConvertResponse(Value* value, std::vector<T>* result) {
- if (!value->IsType(Value::TYPE_LIST))
- return false;
-
- ListValue* list = static_cast<ListValue*>(value);
- for (size_t i = 0; i < list->GetSize(); i++) {
- Value* inner_value;
- if (!list->Get(i, &inner_value))
- return false;
- T item;
- ConvertResponse(inner_value, &item);
- result->push_back(item);
- }
- return true;
- }
+ // Timeout to use for all asynchronous methods.
+ static int timeout_ms_;
// Weak pointer to all the object proxies that we create.
HandleToObjectMap handle_to_object_;
diff --git a/chrome/test/automation/javascript_message_utils.h b/chrome/test/automation/javascript_message_utils.h
new file mode 100644
index 0000000..a571b07
--- /dev/null
+++ b/chrome/test/automation/javascript_message_utils.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2010 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.
+
+#ifndef CHROME_TEST_AUTOMATION_JAVASCRIPT_MESSAGE_UTILS_H_
+#define CHROME_TEST_AUTOMATION_JAVASCRIPT_MESSAGE_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/json/json_writer.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/test/automation/dom_element_proxy.h"
+
+// ValueConversionTraits contains functions for creating a value from a
+// type, and setting a type from a value.
+// This is general-purpose and can be moved to a common location if needed.
+template <class T>
+struct ValueConversionTraits {
+};
+
+template <>
+struct ValueConversionTraits<int> {
+ static Value* CreateValue(int t) {
+ return Value::CreateIntegerValue(t);
+ }
+ static bool SetFromValue(Value* value, int* t) {
+ return value->GetAsInteger(t);
+ }
+};
+
+template <>
+struct ValueConversionTraits<bool> {
+ static Value* CreateValue(bool t) {
+ return Value::CreateBooleanValue(t);
+ }
+ static bool SetFromValue(Value* value, bool* t) {
+ return value->GetAsBoolean(t);
+ }
+};
+
+template <>
+struct ValueConversionTraits<std::string> {
+ static Value* CreateValue(const std::string& t) {
+ return Value::CreateStringValue(t);
+ }
+ static bool SetFromValue(Value* value, std::string* t) {
+ return value->GetAsString(t);
+ }
+};
+
+template <>
+struct ValueConversionTraits<DOMElementProxy::By> {
+ typedef DOMElementProxy::By type;
+ static Value* CreateValue(const type& t) {
+ DictionaryValue* value = new DictionaryValue();
+ std::string by_type;
+ switch (t.type()) {
+ case type::TYPE_XPATH:
+ by_type = "xpath";
+ break;
+ case type::TYPE_SELECTORS:
+ by_type = "selectors";
+ break;
+ case type::TYPE_TEXT:
+ by_type = "text";
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ value->SetString(L"type", by_type);
+ value->SetString(L"queryString", t.query());
+ return value;
+ }
+};
+
+template <typename T>
+struct ValueConversionTraits<std::vector<T> > {
+ static Value* CreateValue(const std::vector<T>& t) {
+ ListValue* value = new ListValue();
+ for (size_t i = 0; i < t.size(); i++) {
+ value->Append(ValueConversionTraits<T>::CreateValue(t[i]));
+ }
+ return value;
+ }
+ static bool SetFromValue(Value* value, std::vector<T>* t) {
+ if (!value->IsType(Value::TYPE_LIST))
+ return false;
+
+ ListValue* list_value = static_cast<ListValue*>(value);
+ ListValue::const_iterator iter;
+ for (iter = list_value->begin(); iter != list_value->end(); ++iter) {
+ T inner_value;
+ ValueConversionTraits<T>::SetFromValue(*iter, &inner_value);
+ t->push_back(inner_value);
+ }
+ return true;
+ }
+};
+
+namespace javascript_utils {
+
+// Converts |arg| to a JSON string.
+template <typename T>
+std::string JSONStringify(const T& arg) {
+ std::string javascript;
+ scoped_ptr<Value> value(ValueConversionTraits<T>::CreateValue(arg));
+ base::JSONWriter::Write(value.get(), false, &javascript);
+ return javascript;
+}
+
+// Converts |arg| to a JSON string and returns a string formatted as
+// |format| specifies. |format| should only expect string arguments.
+template <typename T>
+std::string JavaScriptPrintf(const std::string& format, const T& arg) {
+ return StringPrintf(format.c_str(), JSONStringify(arg).c_str());
+}
+
+// Similar to above, but with an additional argument.
+template <typename T1, typename T2>
+std::string JavaScriptPrintf(const std::string& format, const T1& arg1,
+ const T2& arg2) {
+ return StringPrintf(format.c_str(),
+ JSONStringify(arg1).c_str(),
+ JSONStringify(arg2).c_str());
+}
+
+// Similar to above, but with an additional argument.
+template <typename T1, typename T2, typename T3>
+std::string JavaScriptPrintf(const std::string& format, const T1& arg1,
+ const T2& arg2, const T3& arg3) {
+ return StringPrintf(format.c_str(),
+ JSONStringify(arg1).c_str(),
+ JSONStringify(arg2).c_str(),
+ JSONStringify(arg3).c_str());
+}
+
+} // namespace javascript_utils
+
+#endif // CHROME_TEST_AUTOMATION_JAVASCRIPT_MESSAGE_UTILS_H_
diff --git a/chrome/test/automation/tab_proxy.cc b/chrome/test/automation/tab_proxy.cc
index 36d7d4d..ed4db9e 100644
--- a/chrome/test/automation/tab_proxy.cc
+++ b/chrome/test/automation/tab_proxy.cc
@@ -7,6 +7,7 @@
#include <algorithm>
#include "base/logging.h"
+#include "base/utf_string_conversions.h"
#include "chrome/common/json_value_serializer.h"
#include "chrome/test/automation/automation_constants.h"
#include "chrome/test/automation/automation_messages.h"
@@ -345,6 +346,16 @@ bool TabProxy::ExecuteAndExtractValue(const std::wstring& frame_xpath,
return *value != NULL;
}
+DOMElementProxyRef TabProxy::GetDOMDocument() {
+ if (!is_valid())
+ return NULL;
+
+ int element_handle;
+ if (!ExecuteJavaScriptAndGetReturn("document", &element_handle))
+ return NULL;
+ return GetObjectProxy<DOMElementProxy>(element_handle);
+}
+
bool TabProxy::SetEnableExtensionAutomation(
const std::vector<std::string>& functions_enabled) {
if (!is_valid())
@@ -757,3 +768,24 @@ void TabProxy::OnChannelError() {
AutoLock lock(list_lock_);
FOR_EACH_OBSERVER(TabProxyDelegate, observers_list_, OnChannelError(this));
}
+
+bool TabProxy::ExecuteJavaScriptAndGetJSON(const std::string& script,
+ std::string* json) {
+ if (!is_valid())
+ return false;
+ if (!json) {
+ NOTREACHED();
+ return false;
+ }
+ return sender_->Send(new AutomationMsg_DomOperation(0, handle_, L"",
+ UTF8ToWide(script),
+ json));
+}
+
+void TabProxy::FirstObjectAdded() {
+ AddRef();
+}
+
+void TabProxy::LastObjectRemoved() {
+ Release();
+}
diff --git a/chrome/test/automation/tab_proxy.h b/chrome/test/automation/tab_proxy.h
index e7a7951..8115871 100644
--- a/chrome/test/automation/tab_proxy.h
+++ b/chrome/test/automation/tab_proxy.h
@@ -21,6 +21,8 @@
#include "chrome/browser/tab_contents/security_style.h"
#include "chrome/test/automation/automation_constants.h"
#include "chrome/test/automation/automation_handle_tracker.h"
+#include "chrome/test/automation/dom_element_proxy.h"
+#include "chrome/test/automation/javascript_execution_controller.h"
class GURL;
class Value;
@@ -40,7 +42,8 @@ enum AutomationPageFontSize {
LARGEST_FONT = 36
};
-class TabProxy : public AutomationResourceProxy {
+class TabProxy : public AutomationResourceProxy,
+ public JavaScriptExecutionController {
public:
class TabProxyDelegate {
public:
@@ -88,6 +91,10 @@ class TabProxy : public AutomationResourceProxy {
const std::wstring& jscript,
Value** value) WARN_UNUSED_RESULT;
+ // Returns a DOMElementProxyRef to the tab's current DOM document.
+ // This proxy is invalidated when the document changes.
+ DOMElementProxyRef GetDOMDocument();
+
// Configure extension automation mode. When extension automation
// mode is turned on, the automation host can overtake extension API calls
// e.g. to make UI tests for extensions easier to write. Returns true if
@@ -385,6 +392,20 @@ class TabProxy : public AutomationResourceProxy {
void OnChannelError();
protected:
virtual ~TabProxy() {}
+
+ // Override JavaScriptExecutionController methods.
+ // Executes |script| and gets the response JSON. Returns true on success.
+ bool ExecuteJavaScriptAndGetJSON(const std::string& script,
+ std::string* json) WARN_UNUSED_RESULT;
+
+ // Called when tracking the first object. Used for reference counting
+ // purposes.
+ void FirstObjectAdded();
+
+ // Called when no longer tracking any objects. Used for reference counting
+ // purposes.
+ void LastObjectRemoved();
+
private:
Lock list_lock_; // Protects the observers_list_.
ObserverList<TabProxyDelegate> observers_list_;
diff --git a/chrome/test/data/dom_automation/wait/test.html b/chrome/test/data/dom_automation/wait/test.html
new file mode 100644
index 0000000..2a7941a
--- /dev/null
+++ b/chrome/test/data/dom_automation/wait/test.html
@@ -0,0 +1,21 @@
+<html>
+<script>
+window.onload = function() {
+ window.setTimeout(function() {
+ document.getElementById("anchor").href = "http://www.google.com";
+ }, 2000);
+
+ window.setTimeout(function() {
+ var div = document.createElement("div");
+ div.innerHTML = "div_inner";
+ document.body.appendChild(div);
+ }, 2000);
+
+ window.setTimeout(function() {
+ document.body.removeChild(document.querySelector("img"));
+ }, 2000);
+}
+</script>
+<a id="anchor"></a>
+<img width=100 height=100>
+</html>
diff --git a/chrome/test/ui/ui_test.cc b/chrome/test/ui/ui_test.cc
index f9c5151..b7dd90a 100644
--- a/chrome/test/ui/ui_test.cc
+++ b/chrome/test/ui/ui_test.cc
@@ -34,6 +34,7 @@
#include "chrome/test/automation/automation_messages.h"
#include "chrome/test/automation/automation_proxy.h"
#include "chrome/test/automation/browser_proxy.h"
+#include "chrome/test/automation/javascript_execution_controller.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/automation/window_proxy.h"
#include "chrome/test/chrome_process_util.h"
@@ -166,6 +167,7 @@ void UITestBase::SetUp() {
}
InitializeTimeouts();
+ JavaScriptExecutionController::set_timeout(action_max_timeout_ms_);
LaunchBrowserAndServer();
}
diff --git a/chrome/test/ui_test_utils.cc b/chrome/test/ui_test_utils.cc
index 9f01ff2..8b86ff8 100644
--- a/chrome/test/ui_test_utils.cc
+++ b/chrome/test/ui_test_utils.cc
@@ -430,9 +430,9 @@ DOMElementProxyRef GetActiveDOMDocument(Browser* browser) {
JavaScriptExecutionController* executor =
new InProcessJavaScriptExecutionController(
browser->GetSelectedTabContents()->render_view_host());
- DOMElementProxy* main_doc = NULL;
- executor->ExecuteJavaScriptAndParse("document;", &main_doc);
- return main_doc;
+ int element_handle;
+ executor->ExecuteJavaScriptAndGetReturn("document;", &element_handle);
+ return executor->GetObjectProxy<DOMElementProxy>(element_handle);
}
Value* ExecuteJavaScript(RenderViewHost* render_view_host,