diff options
-rw-r--r-- | chrome/browser/dom_ui/file_browse_browsertest.cc | 4 | ||||
-rw-r--r-- | chrome/chrome.gyp | 4 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 | ||||
-rw-r--r-- | chrome/renderer/resources/dom_automation.js | 294 | ||||
-rw-r--r-- | chrome/test/automation/dom_automation_browsertest.cc | 115 | ||||
-rw-r--r-- | chrome/test/automation/dom_element_proxy.cc | 304 | ||||
-rw-r--r-- | chrome/test/automation/dom_element_proxy.h | 141 | ||||
-rw-r--r-- | chrome/test/automation/javascript_execution_controller.cc | 116 | ||||
-rw-r--r-- | chrome/test/automation/javascript_execution_controller.h | 141 | ||||
-rw-r--r-- | chrome/test/automation/javascript_message_utils.h | 144 | ||||
-rw-r--r-- | chrome/test/automation/tab_proxy.cc | 32 | ||||
-rw-r--r-- | chrome/test/automation/tab_proxy.h | 23 | ||||
-rw-r--r-- | chrome/test/data/dom_automation/wait/test.html | 21 | ||||
-rw-r--r-- | chrome/test/ui/ui_test.cc | 2 | ||||
-rw-r--r-- | chrome/test/ui_test_utils.cc | 6 |
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, |