diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-18 23:43:11 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-18 23:43:11 +0000 |
commit | a9602de83c13669096f15e07e3f613461c58afbc (patch) | |
tree | 5a0f434b63ef6311b309ec2ca796c58374add0bc | |
parent | 9d3bd0e747adae3c4caf9ddccf8e08a4de68e3bc (diff) | |
download | chromium_src-a9602de83c13669096f15e07e3f613461c58afbc.zip chromium_src-a9602de83c13669096f15e07e3f613461c58afbc.tar.gz chromium_src-a9602de83c13669096f15e07e3f613461c58afbc.tar.bz2 |
Add support for interacting with the DOM in browser_tests.
BUG=none
TEST=none
Reivew url: http://codereview.chromium.org/660046
Review URL: http://codereview.chromium.org/1051005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42026 0039d316-1c4b-4281-b951-d872f2087c98
26 files changed, 1430 insertions, 2 deletions
diff --git a/base/json/string_escape.cc b/base/json/string_escape.cc index 4e1418c..5bf0b86 100644 --- a/base/json/string_escape.cc +++ b/base/json/string_escape.cc @@ -82,10 +82,22 @@ void JsonDoubleQuote(const std::string& str, JsonDoubleQuoteT(str, put_in_quotes, dst); } +std::string GetDoubleQuotedJson(const std::string& str) { + std::string dst; + JsonDoubleQuote(str, true, &dst); + return dst; +} + void JsonDoubleQuote(const string16& str, bool put_in_quotes, std::string* dst) { JsonDoubleQuoteT(str, put_in_quotes, dst); } +std::string GetDoubleQuotedJson(const string16& str) { + std::string dst; + JsonDoubleQuote(str, true, &dst); + return dst; +} + } // namespace base diff --git a/base/json/string_escape.h b/base/json/string_escape.h index 7d74021..7c64c29 100644 --- a/base/json/string_escape.h +++ b/base/json/string_escape.h @@ -13,7 +13,7 @@ namespace base { -// Escape |str| appropriately for a JSON string litereal, _appending_ the +// Escape |str| appropriately for a JSON string literal, _appending_ the // result to |dst|. This will create unicode escape sequences (\uXXXX). // If |put_in_quotes| is true, the result will be surrounded in double quotes. // The outputted literal, when interpreted by the browser, should result in a @@ -22,10 +22,15 @@ void JsonDoubleQuote(const std::string& str, bool put_in_quotes, std::string* dst); +// Same as above, but always returns the result double quoted. +std::string GetDoubleQuotedJson(const std::string& str); + void JsonDoubleQuote(const string16& str, bool put_in_quotes, std::string* dst); +// Same as above, but always returns the result double quoted. +std::string GetDoubleQuotedJson(const string16& str); } // namespace base diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 0bcd077..67df617 100755 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -45,6 +45,8 @@ # short term I'd like the build to work. 'renderer/automation/dom_automation_controller.cc', 'renderer/automation/dom_automation_controller.h', + 'renderer/automation/dom_automation_v8_extension.cc', + 'renderer/automation/dom_automation_v8_extension.h', 'renderer/extensions/bindings_utils.cc', 'renderer/extensions/bindings_utils.h', 'renderer/extensions/event_bindings.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index e1933dd..a465999 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -100,6 +100,10 @@ '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/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', @@ -1247,6 +1251,7 @@ 'browser/ssl/ssl_browser_tests.cc', 'browser/task_manager_browsertest.cc', 'renderer/form_autocomplete_unittest.cc', + 'test/automation/dom_automation_browsertest.cc', 'test/render_view_test.cc', 'test/render_view_test.h', ], diff --git a/chrome/renderer/automation/dom_automation_controller.cc b/chrome/renderer/automation/dom_automation_controller.cc index 3b7ac43..6c3e8a5 100644 --- a/chrome/renderer/automation/dom_automation_controller.cc +++ b/chrome/renderer/automation/dom_automation_controller.cc @@ -14,6 +14,7 @@ DomAutomationController::DomAutomationController() automation_id_(MSG_ROUTING_NONE) { BindMethod("send", &DomAutomationController::Send); BindMethod("setAutomationId", &DomAutomationController::SetAutomationId); + BindMethod("sendJSON", &DomAutomationController::SendJSON); } void DomAutomationController::Send(const CppArgumentList& args, @@ -86,6 +87,36 @@ void DomAutomationController::Send(const CppArgumentList& args, return; } +void DomAutomationController::SendJSON(const CppArgumentList& args, + CppVariant* result) { + if (args.size() != 1) { + result->SetNull(); + return; + } + + if (automation_id_ == MSG_ROUTING_NONE) { + result->SetNull(); + return; + } + + if (!sender_) { + NOTREACHED(); + result->SetNull(); + return; + } + + if (args[0].type != NPVariantType_String) { + result->SetNull(); + return; + } + + std::string json = args[0].ToString(); + result->Set(sender_->Send( + new ViewHostMsg_DomOperationResponse(routing_id_, json, automation_id_))); + + automation_id_ = MSG_ROUTING_NONE; +} + void DomAutomationController::SetAutomationId( const CppArgumentList& args, CppVariant* result) { if (args.size() != 1) { diff --git a/chrome/renderer/automation/dom_automation_controller.h b/chrome/renderer/automation/dom_automation_controller.h index bc6f5fe..c815418 100644 --- a/chrome/renderer/automation/dom_automation_controller.h +++ b/chrome/renderer/automation/dom_automation_controller.h @@ -84,6 +84,11 @@ class DomAutomationController : public CppBoundClass { // IPC. It sets the return value to null on unexpected errors or arguments. void Send(const CppArgumentList& args, CppVariant* result); + // Makes the renderer send a javascript value to the app. + // The value must be a NPString and should be properly formed JSON. + // This function does not modify/escape the returned string in any way. + void SendJSON(const CppArgumentList& args, CppVariant* result); + void SetAutomationId(const CppArgumentList& args, CppVariant* result); // TODO(vibhor): Implement later diff --git a/chrome/renderer/automation/dom_automation_v8_extension.cc b/chrome/renderer/automation/dom_automation_v8_extension.cc new file mode 100644 index 0000000..efe5ada --- /dev/null +++ b/chrome/renderer/automation/dom_automation_v8_extension.cc @@ -0,0 +1,19 @@ +// 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. + +#include "chrome/renderer/automation/dom_automation_v8_extension.h" + +#include "chrome/renderer/extensions/bindings_utils.h" +#include "grit/renderer_resources.h" + +using bindings_utils::GetStringResource; + +const char* DomAutomationV8Extension::kName = "chrome/domautomation"; + +v8::Extension* DomAutomationV8Extension::Get() { + static v8::Extension* extension = + new bindings_utils::ExtensionBase( + kName, GetStringResource<IDR_DOM_AUTOMATION_JS>(), 0, NULL); + return extension; +} diff --git a/chrome/renderer/automation/dom_automation_v8_extension.h b/chrome/renderer/automation/dom_automation_v8_extension.h new file mode 100644 index 0000000..94201e7 --- /dev/null +++ b/chrome/renderer/automation/dom_automation_v8_extension.h @@ -0,0 +1,16 @@ +// 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_RENDERER_AUTOMATION_DOM_AUTOMATION_V8_EXTENSION_H_ +#define CHROME_RENDERER_AUTOMATION_DOM_AUTOMATION_V8_EXTENSION_H_ + +#include "v8/include/v8.h" + +class DomAutomationV8Extension { + public: + static const char* kName; + static v8::Extension* Get(); +}; + +#endif // CHROME_RENDERER_AUTOMATION_DOM_AUTOMATION_V8_EXTENSION_H_ diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc index ad4ec2a..6448cf5 100644 --- a/chrome/renderer/render_thread.cc +++ b/chrome/renderer/render_thread.cc @@ -42,6 +42,7 @@ #include "base/scoped_handle.h" #include "chrome/plugin/plugin_channel_base.h" #endif +#include "chrome/renderer/automation/dom_automation_v8_extension.h" #include "chrome/renderer/cookie_message_filter.h" #include "chrome/renderer/devtools_agent_filter.h" #include "chrome/renderer/extension_groups.h" @@ -789,6 +790,10 @@ void RenderThread::EnsureWebKitInitialized() { extensions_v8::PlaybackExtension::Get()); } + if (command_line.HasSwitch(switches::kDomAutomationController)) { + WebScriptController::registerExtension(DomAutomationV8Extension::Get()); + } + WebRuntimeFeatures::enableMediaPlayer( RenderProcess::current()->HasInitializedMediaLibrary()); diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index 15c8f90..ac2f91c 100644 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -22,6 +22,7 @@ without changes to the corresponding grd file. fb9 --> <include name="IDR_SAD_PLUGIN" file="resources\sadplugin.png" type="BINDATA" /> <include name="IDR_EXTENSION_APITEST_JS" file="resources\extension_apitest.js" type="BINDATA" /> <include name="IDR_EXTENSION_TOOLSTRIP_CSS" file="resources\extension_toolstrip.css" flattenhtml="true" type="BINDATA" /> + <include name="IDR_DOM_AUTOMATION_JS" file="resources\dom_automation.js" type="BINDATA" /> <include name="IDR_BASE_JS" file="resources\base.js" type="BINDATA" /> </includes> </release> diff --git a/chrome/renderer/resources/dom_automation.js b/chrome/renderer/resources/dom_automation.js new file mode 100644 index 0000000..16a4dd0 --- /dev/null +++ b/chrome/renderer/resources/dom_automation.js @@ -0,0 +1,234 @@ +// 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. + +// dom_automation.js +// Methods for performing common DOM operations. Used in Chrome testing +// involving the DomAutomationController. + +var domAutomation = domAutomation || {}; + +(function() { + // |objects| is used to track objects which are sent back to the + // DomAutomationController. Since JavaScript does not have a map type, + // |objects| is simply an object in which the property name and + // property value serve as the key-value pair. The key is the handle number + // and the value is the tracked object. + domAutomation.objects = {}; + domAutomation.nextHandle = 1; + + // Returns |value| after converting it to an acceptable type for return, if + // necessary. + function getConvertedValue(value) { + if (typeof value == "undefined" || !value) { + return ""; + } + if (value instanceof Array) { + var result = []; + for (var i = 0; i < value.length; i++) { + result.push(getConvertedValue(value[i])); + } + return result; + } + if (typeof(value) == "object") { + var handle = getHandleForObject(value); + if (handle == -1) { + // Track this object. + var handle = domAutomation.nextHandle++; + domAutomation.objects[handle] = value; + } + return handle; + } + return value; + } + + // Returns the handle for |obj|, or -1 if no handle exists. + function getHandleForObject(obj) { + for (var property in domAutomation.objects) { + if (domAutomation.objects[property] == obj) + return parseInt(property); + } + return -1; + } + + // 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))]; + } + catch (exception) { + var message = exception.message; + if (typeof message != "string") + message = JSON.stringify(message); + var result = [false, message, exception]; + } + domAutomationController.sendJSON(JSON.stringify(result)); + } + + // Stops tracking the object associated with |handle|. + domAutomation.removeObject = function(handle) { + delete domAutomation.objects[handle]; + } + + // Stops tracking all objects. + domAutomation.removeAll = function() { + domAutomation.objects = {}; + domAutomation.nextHandle = 1; + } + + // Gets the object associated with this |handle|. + domAutomation.getObject = function(handle) { + return domAutomation.objects[handle]; + } + + // Converts an indexable list with a length property to an array. + function getArray(list) { + var arr = []; + for (var i = 0; i < list.length; i++) { + arr.push(list[i]); + } + return arr; + } + + // Removes whitespace at the beginning and end of |text|. + function trim(text) { + return text.replace(/^\s+|\s+$/g, ""); + } + + // Returns the window (which is a sub window of |win|) which + // directly contains |doc|. May return null. + function findWindowForDocument(win, doc) { + if (win.document == doc) + return win; + for (var i = 0; i < win.frames.length; i++) { + if (findWindowForDocument(win.frames[i], doc)) + return win.frames[i]; + } + return null; + } + + //// 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.findByXPath = function(context, xpath) { + var xpathResult = + document.evaluate(xpath, context, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + var elements = []; + for (var i = 0; i < xpathResult.snapshotLength; i++) { + elements.push(xpathResult.snapshotItem(i)); + } + return elements; + } + + domAutomation.find1ByXPath = function(context, xpath) { + var xpathResult = + document.evaluate(xpath, context, null, + XPathResult.FIRST_ORDERED_NODE_TYPE, null); + return xpathResult.singleNodeValue; + } + + domAutomation.findBySelectors = function(context, selectors) { + return getArray(context.querySelectorAll(selectors)); + } + + domAutomation.find1BySelectors = function(context, selectors) { + return context.querySelector(selectors); + } + + domAutomation.findByText = function(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); + + // 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 final_list; + } + + domAutomation.find1ByText = function(context, text) { + return domAutomation.findByText(context, text)[0]; + } + + domAutomation.click = function(element) { + var evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, + 0, 0, 0, 0, 0, false, false, + false, false, 0, null); + while (element) { + element.dispatchEvent(evt); + element = element.parentNode; + } + } + + domAutomation.type = function(element, text) { + if (element instanceof HTMLTextAreaElement || + (element instanceof HTMLInputElement && element.type == "text")) { + element.value += text; + return true; + } + return false; + } + + domAutomation.setText = function(element, text) { + if (element instanceof HTMLTextAreaElement || + (element instanceof HTMLInputElement && element.type == "text")) { + element.value = text; + return true; + } + return false; + } + + domAutomation.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.getInnerHTML = function(element) { + return trim(element.innerHTML); + } + + 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; + } + return true; + } +})(); diff --git a/chrome/test/automation/dom_automation_browsertest.cc b/chrome/test/automation/dom_automation_browsertest.cc new file mode 100644 index 0000000..c861f89 --- /dev/null +++ b/chrome/test/automation/dom_automation_browsertest.cc @@ -0,0 +1,246 @@ +// 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. + +#include "base/ref_counted.h" +#include "base/utf_string_conversions.h" +#include "chrome/test/automation/dom_element_proxy.h" +#include "chrome/browser/browser.h" +#include "chrome/test/in_process_browser_test.h" +#include "chrome/test/ui_test_utils.h" + +namespace { + +class DOMAutomationTest : public InProcessBrowserTest { + public: + DOMAutomationTest() { + EnableDOMAutomation(); + } + + GURL GetTestURL(const char* path) { + std::string url("http://localhost:1337/files/dom_automation/"); + url.append(path); + return GURL(url); + } +}; + +IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindByXPath) { + StartHTTPServer(); + ui_test_utils::NavigateToURL(browser(), + GetTestURL("find_elements/test.html")); + DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser()); + + // Find first element. + DOMElementProxyRef first_div = main_doc->FindByXPath("//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_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")); + elements.clear(); + ASSERT_TRUE(main_doc->FindByXPath("//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)); + + // Find nested elements. + int nested_count = 0; + std::string span_name; + DOMElementProxyRef node = main_doc->FindByXPath("/html/body/span"); + while (node) { + nested_count++; + span_name.append("span"); + ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name)); + node = node->FindByXPath("./span"); + } + ASSERT_EQ(3, nested_count); +} + +IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindBySelectors) { + StartHTTPServer(); + ui_test_utils::NavigateToURL(browser(), + GetTestURL("find_elements/test.html")); + DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser()); + + // Find first element. + DOMElementProxyRef first_myclass = + main_doc->FindBySelectors(".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_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")); + elements.clear(); + ASSERT_TRUE(main_doc->FindBySelectors("#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)); + + // Find nested elements. + int nested_count = 0; + std::string span_name; + DOMElementProxyRef node = main_doc->FindBySelectors("span"); + while (node) { + nested_count++; + span_name.append("span"); + ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name)); + node = node->FindBySelectors("span"); + } + ASSERT_EQ(3, nested_count); +} + +IN_PROC_BROWSER_TEST_F(DOMAutomationTest, FindByText) { + StartHTTPServer(); + ui_test_utils::NavigateToURL(browser(), + GetTestURL("find_elements/test.html")); + DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser()); + + // Find first element. + DOMElementProxyRef first_text = main_doc->FindByText("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_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")); + elements.clear(); + ASSERT_TRUE(main_doc->FindByText("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"); + while (node) { + nested_count++; + span_name.append("span"); + ASSERT_NO_FATAL_FAILURE(node->EnsureNameMatches(span_name)); + node = node->FindByText("span_text"); + } + ASSERT_EQ(3, nested_count); + + // Find only visible text. + DOMElementProxyRef shown_td = main_doc->FindByText("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")); +} + +IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Frames) { + StartHTTPServer(); + ui_test_utils::NavigateToURL(browser(), GetTestURL("frames/test.html")); + DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser()); + + // Get both frame elements. + std::vector<DOMElementProxyRef> frame_elements; + ASSERT_TRUE(main_doc->FindByXPath("//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"); + ASSERT_TRUE(frame_div); + ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("frame 1")); + frame_div = frame2->FindByXPath("/html/body/div"); + ASSERT_TRUE(frame_div); + ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("frame 2")); + + // Get both inner iframes, checking their contents are correct. + DOMElementProxyRef iframe1 = + frame1->GetDocumentFromFrame("0"); + DOMElementProxyRef iframe2 = + frame2->GetDocumentFromFrame("0"); + ASSERT_TRUE(iframe1 && iframe2); + frame_div = iframe1->FindByXPath("/html/body/div"); + ASSERT_TRUE(frame_div); + ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("iframe 1")); + frame_div = iframe2->FindByXPath("/html/body/div"); + ASSERT_TRUE(frame_div); + ASSERT_NO_FATAL_FAILURE(frame_div->EnsureInnerHTMLMatches("iframe 2")); + + // Get nested frame. + ASSERT_EQ(iframe1.get(), main_doc->GetDocumentFromFrame("0", "0").get()); + ASSERT_EQ(iframe2.get(), main_doc->GetDocumentFromFrame("1", "0").get()); +} + +IN_PROC_BROWSER_TEST_F(DOMAutomationTest, Events) { + StartHTTPServer(); + ui_test_utils::NavigateToURL(browser(), GetTestURL("events/test.html")); + DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser()); + + // Click link and make sure text changes. + DOMElementProxyRef link = main_doc->FindBySelectors("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"); + ASSERT_TRUE(textfield && button && button->Click()); + ASSERT_NO_FATAL_FAILURE(textfield->EnsureTextMatches("clicked")); + + // Type in the textfield. + ASSERT_TRUE(textfield->SetText("test")); + ASSERT_NO_FATAL_FAILURE(textfield->EnsureTextMatches("test")); + + // Type in the textarea. + DOMElementProxyRef textarea = main_doc->FindBySelectors("textarea"); + ASSERT_TRUE(textarea && textarea->Type("test")); + ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches("textareatest")); +} + +IN_PROC_BROWSER_TEST_F(DOMAutomationTest, StringEscape) { + StartHTTPServer(); + ui_test_utils::NavigateToURL(browser(), + GetTestURL("string_escape/test.html")); + DOMElementProxyRef main_doc = ui_test_utils::GetActiveDOMDocument(browser()); + + DOMElementProxyRef textarea = main_doc->FindBySelectors("textarea"); + ASSERT_TRUE(textarea); + ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches(WideToUTF8(L"\u00FF"))); + + const wchar_t* set_and_expect_strings[] = { + L"\u00FF and \u00FF", + L"\n \t \\", + L"' \"" + }; + for (size_t i = 0; i < 3; i++) { + ASSERT_TRUE(textarea->SetText(WideToUTF8(set_and_expect_strings[i]))); + ASSERT_NO_FATAL_FAILURE(textarea->EnsureTextMatches( + WideToUTF8(set_and_expect_strings[i]))); + } +} + +} // namespace diff --git a/chrome/test/automation/dom_element_proxy.cc b/chrome/test/automation/dom_element_proxy.cc new file mode 100644 index 0000000..c7bc6da --- /dev/null +++ b/chrome/test/automation/dom_element_proxy.cc @@ -0,0 +1,299 @@ +// 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. + +#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 "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Convenience wrapper for GetDoubleQuotedJson function. +std::string GetDoubleQuotedJson(std::string utf8_string) { + return base::GetDoubleQuotedJson(UTF8ToUTF16(utf8_string)); +} + +} // namespace + +DOMElementProxyRef DOMElementProxy::GetContentDocument() { + const char* script = "%s.contentDocument;"; + DOMElementProxy* element = NULL; + executor_->ExecuteJavaScriptAndParse( + StringPrintf(script, this->GetReferenceJavaScript().c_str()), &element); + return element; +} + +DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame( + const std::vector<std::string>& frame_names) { + if (!is_valid()) + return NULL; + + 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; +} + +DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame( + const std::string& frame_name) { + if (!is_valid()) + return NULL; + + std::vector<std::string> frame_names; + frame_names.push_back(frame_name); + return GetDocumentFromFrame(frame_names); +} + +DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame( + const std::string& frame_name1, const std::string& frame_name2) { + 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); +} + +DOMElementProxyRef DOMElementProxy::GetDocumentFromFrame( + const std::string& frame_name1, const std::string& frame_name2, + const std::string& frame_name3) { + if (!is_valid()) + return NULL; + + std::vector<std::string> frame_names; + frame_names.push_back(frame_name1); + frame_names.push_back(frame_name2); + 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) { + 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; +} + +bool DOMElementProxy::FindBySelectors( + const std::string& selectors, 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)) + return false; + for (size_t i = 0; i < element_pointers.size(); i++) + elements->push_back(element_pointers[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) { + 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)) + return false; + for (size_t i = 0; i < element_pointers.size(); i++) + elements->push_back(element_pointers[i]); + return true; +} + +DOMElementProxyRef DOMElementProxy::FindByText(const std::string& text) { + if (!is_valid()) + return NULL; + + 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::Click() { + const char* script = "domAutomation.click(%s);"; + if (!is_valid()) + return false; + + return executor_->ExecuteJavaScript( + StringPrintf(script, this->GetReferenceJavaScript().c_str())); +} + +bool DOMElementProxy::Type(const std::string& text) { + const char* script = "domAutomation.type(%s, %s);"; + if (!is_valid()) + return false; + + bool success = false; + executor_->ExecuteJavaScriptAndParse( + StringPrintf(script, this->GetReferenceJavaScript().c_str(), + GetDoubleQuotedJson(text).c_str()), + &success); + return success; +} + +bool DOMElementProxy::SetText(const std::string& text) { + const char* script = "domAutomation.setText(%s, %s);"; + if (!is_valid()) + return false; + + bool success = false; + executor_->ExecuteJavaScriptAndParse( + StringPrintf(script, this->GetReferenceJavaScript().c_str(), + GetDoubleQuotedJson(text).c_str()), + &success); + return success; +} + +bool DOMElementProxy::GetProperty(const std::string& property, + std::string* out) { + DCHECK(out); + if (!is_valid()) + return false; + + const char* script = "%s.%s;"; + return executor_->ExecuteJavaScriptAndParse( + StringPrintf(script, this->GetReferenceJavaScript().c_str(), + GetDoubleQuotedJson(property).c_str()), + out); +} + +bool DOMElementProxy::GetAttribute(const std::string& attribute, + std::string* out) { + DCHECK(out); + 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); +} + +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); +} + +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); +} + +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); +} + +bool DOMElementProxy::GetName(std::string* name) { + return GetAttribute("name", name); +} + +bool DOMElementProxy::GetVisibility(bool* visibility) { + DCHECK(visibility); + if (!is_valid()) + return false; + + const char* script = "domAutomation.isVisible(%s);"; + return executor_->ExecuteJavaScriptAndParse( + StringPrintf(script, this->GetReferenceJavaScript().c_str()), + visibility); +} + +void DOMElementProxy::EnsureTextMatches(const std::string& expected_text) { + std::string text; + ASSERT_TRUE(GetText(&text)); + ASSERT_EQ(expected_text, text); +} + +void DOMElementProxy::EnsureInnerHTMLMatches(const std::string& expected_html) { + std::string html; + ASSERT_TRUE(GetInnerHTML(&html)); + ASSERT_EQ(expected_html, html); +} + +void DOMElementProxy::EnsureNameMatches(const std::string& expected_name) { + std::string name; + ASSERT_TRUE(GetName(&name)); + ASSERT_EQ(expected_name, name); +} + +void DOMElementProxy::EnsureVisibilityMatches(bool expected_visibility) { + bool visibility; + ASSERT_TRUE(GetVisibility(&visibility)); + ASSERT_EQ(expected_visibility, visibility); +} diff --git a/chrome/test/automation/dom_element_proxy.h b/chrome/test/automation/dom_element_proxy.h new file mode 100644 index 0000000..2057e0c0 --- /dev/null +++ b/chrome/test/automation/dom_element_proxy.h @@ -0,0 +1,126 @@ +// 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_DOM_ELEMENT_PROXY_H_ +#define CHROME_TEST_AUTOMATION_DOM_ELEMENT_PROXY_H_ + +#include <string> +#include <vector> + +#include "base/ref_counted.h" +#include "chrome/test/automation/javascript_execution_controller.h" + +class DOMElementProxy; + +typedef scoped_refptr<DOMElementProxy> DOMElementProxyRef; + +// 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. +class DOMElementProxy : public JavaScriptObjectProxy { + public: + DOMElementProxy(JavaScriptExecutionController* executor, int handle) + : JavaScriptObjectProxy(executor, handle) {} + + // Returns the document for this element, which must be of type frame. + // Returns NULL on failure. + DOMElementProxyRef GetContentDocument(); + + // Finds the frame which matches the list of given names, starting from + // the window that contains this element. Each name in the list is used to + // select the next sub frame. Returns NULL on failure. + // A vector of "2" and "ad" is equivalent to the javascript: + // frame.frames["2"].frames["ad"]. + DOMElementProxyRef GetDocumentFromFrame( + 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. + // Returns true on success. + bool Click(); + + // Adds |text| to this element. Only valid for textareas and textfields. + // Returns true on success. + bool Type(const std::string& text); + + // Sets the input text to |text|. Only valid for textareas and textfields. + // Returns true on success. + bool SetText(const std::string& text); + + // Gets the element's value for its |property|. Returns true on success. + bool GetProperty(const std::string& property, + std::string* out); + + // Gets the element's value for its |attribute|. Returns true on success. + bool GetAttribute(const std::string& attribute, + std::string* out); + + // Retrieves all the text in this element. This includes the value + // of textfields and inputs. Returns true on success. + bool GetText(std::string* text); + + // Retrieves the element's inner HTML. Returns true on success. + bool GetInnerHTML(std::string* html); + + // Retrieves the element's id. Returns true on success. + bool GetId(std::string* id); + + // Retrieves the element's name. Returns true on success. + bool GetName(std::string* name); + + // Retrieves the element's visibility. Returns true on success. + bool GetVisibility(bool* visilibity); + + // 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); + + // Asserts that |expected_html| matches the element's inner html. + void EnsureInnerHTMLMatches(const std::string& expected_html); + + // Asserts that |expected_name| matches the element's name. + void EnsureNameMatches(const std::string& expected_name); + + // Asserts that |expected_visibility| matches the element's visibility. + void EnsureVisibilityMatches(bool expected_visibility); +}; + +#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 new file mode 100644 index 0000000..26d32d0 --- /dev/null +++ b/chrome/test/automation/javascript_execution_controller.cc @@ -0,0 +1,127 @@ +// 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. + +#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" + +using base::GetDoubleQuotedJson; + +// JavaScriptObjectProxy methods +JavaScriptObjectProxy::JavaScriptObjectProxy( + JavaScriptExecutionController* executor, int handle) + : executor_(executor->AsWeakPtr()), handle_(handle) {} + +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()); +} + +// 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; +} + +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 = + "domAutomationController.setAutomationId(0);" + "domAutomation.evaluateJavaScript("; + script.append(GetDoubleQuotedJson(UTF8ToUTF16(original_script))); + script.append(");"); + return ExecuteJavaScriptAndGetJSON(script, json); +} + +bool JavaScriptExecutionController::ParseJSON(const std::string& json, + scoped_ptr<Value>* result) { + JSONStringValueSerializer parse(json); + std::string parsing_error; + scoped_ptr<Value> root_value(parse.Deserialize(&parsing_error)); + + if (!root_value.get()) { + if (parsing_error.length()) + LOG(ERROR) << "Cannot parse JSON response: " << parsing_error; + else + LOG(ERROR) << "JSON response is empty"; + 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; + if (!root_value->IsType(Value::TYPE_LIST)) { + LOG(ERROR) << "JSON response was not in correct format"; + return false; + } + ListValue* list = static_cast<ListValue*>(root_value.get()); + if (!list->GetBoolean(0, &success) || + !list->GetString(1, &evaluation_error) || + !list->Remove(2, &evaluation_result_value)) { + LOG(ERROR) << "JSON response was not in correct format"; + return false; + } + if (!success) { + 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 new file mode 100644 index 0000000..0472eee --- /dev/null +++ b/chrome/test/automation/javascript_execution_controller.h @@ -0,0 +1,146 @@ +// 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_EXECUTION_CONTROLLER_H_ +#define CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_ + +#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" + +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); +}; + +// This class handles the execution of arbitrary JavaScript, preparing it for +// execution, and parsing its result (in JSON). It keeps track of all returned +// JavaScript objects. +class JavaScriptExecutionController + : public base::SupportsWeakPtr<JavaScriptExecutionController> { + public: + JavaScriptExecutionController() {} + virtual ~JavaScriptExecutionController() {} + + // Executes |script| and parse return value. + // A corresponding ConvertResponse(Value* value, T* result) must exist + // for type T. + 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)) + return false; + return ConvertResponse(value.get(), result); + } + + // Executes |script| with no return. + 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); + + // Returns the equivalent JSON for |vector|. + static std::string Serialize(const std::vector<std::string>& vector); + + protected: + // Executes |script| and sets the JSON response |json|. Returns true + // on success. + virtual bool ExecuteJavaScriptAndGetJSON(const std::string& script, + std::string* json) = 0; + + // Called when this controller is tracking its first object. Used by + // reference counted subclasses. + virtual void FirstObjectAdded() {} + + // Called when this controller is no longer tracking any objects. Used by + // reference counted subclasses. + virtual void LastObjectRemoved() {} + + private: + typedef std::map<int, JavaScriptObjectProxy*> HandleToObjectMap; + + friend class JavaScriptObjectProxy; + // Called by JavaScriptObjectProxy on destruct. + void Remove(int handle); + + bool ParseJSON(const std::string& json, scoped_ptr<Value>* result); + + bool ExecuteJavaScript(const std::string& script, std::string* json); + + bool ConvertResponse(Value* value, bool* result); + bool ConvertResponse(Value* value, int* result); + bool ConvertResponse(Value* value, std::string* result); + + template<class JavaScriptObject> + bool ConvertResponse(Value* value, JavaScriptObject** result) { + int handle; + if (!value->GetAsInteger(&handle)) + return false; + + 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; + } + + // Weak pointer to all the object proxies that we create. + HandleToObjectMap handle_to_object_; + + DISALLOW_COPY_AND_ASSIGN(JavaScriptExecutionController); +}; + +#endif // CHROME_TEST_AUTOMATION_JAVASCRIPT_EXECUTION_CONTROLLER_H_ diff --git a/chrome/test/data/dom_automation/events/test.html b/chrome/test/data/dom_automation/events/test.html new file mode 100644 index 0000000..022a3a0 --- /dev/null +++ b/chrome/test/data/dom_automation/events/test.html @@ -0,0 +1,19 @@ +<html> +<script> +function renameLink() { + document.links[0].innerHTML = "clicked"; +} +function changeTextfield() { + document.getElementById("textfield").value = "clicked"; +} +</script> +<body> +<a href="javascript:renameLink();">link</a> +<textarea>textarea</textarea> +<form action=""> + <input id="textfield" type='text' value='textfield'></input> + <input id="button" type='button' value='button' onclick="changeTextfield()"> + </input> +</form> +</body> +</html> diff --git a/chrome/test/data/dom_automation/find_elements/test.html b/chrome/test/data/dom_automation/find_elements/test.html new file mode 100644 index 0000000..858677d --- /dev/null +++ b/chrome/test/data/dom_automation/find_elements/test.html @@ -0,0 +1,36 @@ +<html> +<head> +</head> + +<div class="myclass" name="0">div_text</div> +<div class="myclass" name="1">div_text</div> + +35957 +<span name="span">span_text + <span name="spanspan">span_text + <span name="spanspanspan">span_text + </span> + </span> +</span> + +<table border=1> + <tr style="display:none"> + <td>table_text</td> + </tr> + <tr> + <td style="visibility:collapse"> + table_text + </td> + <td style="visibility:hidden"> + table_text + </td> + <td name="shown"> + table_text + </td> + </tr> +</table> + +<textarea >textarea_text</textarea> +<input type="text" value="input_text"></input> + +</html> diff --git a/chrome/test/data/dom_automation/frames/frame1.html b/chrome/test/data/dom_automation/frames/frame1.html new file mode 100644 index 0000000..4e02aab --- /dev/null +++ b/chrome/test/data/dom_automation/frames/frame1.html @@ -0,0 +1,6 @@ +<html> +<body> +<iframe src="iframe1.html"></iframe> +<div>frame 1</div> +</body> +</html> diff --git a/chrome/test/data/dom_automation/frames/frame2.html b/chrome/test/data/dom_automation/frames/frame2.html new file mode 100644 index 0000000..d1e8bdf --- /dev/null +++ b/chrome/test/data/dom_automation/frames/frame2.html @@ -0,0 +1,6 @@ +<html> +<body> +<iframe src="iframe2.html"></iframe> +<div>frame 2</div> +</body> +</html> diff --git a/chrome/test/data/dom_automation/frames/iframe1.html b/chrome/test/data/dom_automation/frames/iframe1.html new file mode 100644 index 0000000..7fccbfd --- /dev/null +++ b/chrome/test/data/dom_automation/frames/iframe1.html @@ -0,0 +1,5 @@ +<html> +<body> +<div>iframe 1</div> +</body> +</html> diff --git a/chrome/test/data/dom_automation/frames/iframe2.html b/chrome/test/data/dom_automation/frames/iframe2.html new file mode 100644 index 0000000..908303e --- /dev/null +++ b/chrome/test/data/dom_automation/frames/iframe2.html @@ -0,0 +1,5 @@ +<html> +<body> +<div>iframe 2</div> +</body> +</html> diff --git a/chrome/test/data/dom_automation/frames/test.html b/chrome/test/data/dom_automation/frames/test.html new file mode 100644 index 0000000..004c3e9 --- /dev/null +++ b/chrome/test/data/dom_automation/frames/test.html @@ -0,0 +1,12 @@ +<html> +<head> +<script> + +</script> +</head> + +<frameset id="set" cols="25%,75%"> +<frame src="frame1.html"> +<frame src="frame2.html"> +</frameset> +</html> diff --git a/chrome/test/data/dom_automation/string_escape/test.html b/chrome/test/data/dom_automation/string_escape/test.html new file mode 100644 index 0000000..6ffea56 --- /dev/null +++ b/chrome/test/data/dom_automation/string_escape/test.html @@ -0,0 +1,6 @@ +<html> +<head> +<meta http-equiv="content-type" content="charset=UTF-8"> +</head> +<textarea>ÿ</textarea> +</html> diff --git a/chrome/test/ui_test_utils.cc b/chrome/test/ui_test_utils.cc index c93ec1c..f04b02e 100644 --- a/chrome/test/ui_test_utils.cc +++ b/chrome/test/ui_test_utils.cc @@ -10,6 +10,7 @@ #include "base/message_loop.h" #include "base/path_service.h" #include "base/process_util.h" +#include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" @@ -25,6 +26,7 @@ #include "chrome/common/extensions/extension_action.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_service.h" +#include "chrome/test/automation/javascript_execution_controller.h" #if defined(TOOLKIT_VIEWS) #include "views/focus/accelerator_handler.h" #endif @@ -85,7 +87,8 @@ class NavigationNotificationObserver : public NotificationObserver { class DOMOperationObserver : public NotificationObserver { public: - explicit DOMOperationObserver(RenderViewHost* render_view_host) { + explicit DOMOperationObserver(RenderViewHost* render_view_host) + : did_respond_(false) { registrar_.Add(this, NotificationType::DOM_OPERATION_RESPONSE, Source<RenderViewHost>(render_view_host)); ui_test_utils::RunMessageLoop(); @@ -97,14 +100,21 @@ class DOMOperationObserver : public NotificationObserver { DCHECK(type == NotificationType::DOM_OPERATION_RESPONSE); Details<DomOperationNotificationDetails> dom_op_details(details); response_ = dom_op_details->json(); + did_respond_ = true; MessageLoopForUI::current()->Quit(); } + bool GetResponse(std::string* response) { + *response = response_; + return did_respond_; + } + std::string response() const { return response_; } private: NotificationRegistrar registrar_; std::string response_; + bool did_respond_; DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver); }; @@ -337,6 +347,36 @@ class FindInPageNotificationObserver : public NotificationObserver { DISALLOW_COPY_AND_ASSIGN(FindInPageNotificationObserver); }; +class InProcessJavaScriptExecutionController + : public base::RefCounted<InProcessJavaScriptExecutionController>, + public JavaScriptExecutionController { + public: + explicit InProcessJavaScriptExecutionController( + RenderViewHost* render_view_host) + : render_view_host_(render_view_host) {} + + protected: + // Executes |script| and sets the JSON response |json|. + bool ExecuteJavaScriptAndGetJSON(const std::string& script, + std::string* json) { + render_view_host_->ExecuteJavascriptInWebFrame(L"", UTF8ToWide(script)); + DOMOperationObserver dom_op_observer(render_view_host_); + return dom_op_observer.GetResponse(json); + } + + void FirstObjectAdded() { + AddRef(); + } + + void LastObjectRemoved() { + Release(); + } + + private: + // Weak pointer to the associated RenderViewHost. + RenderViewHost* render_view_host_; +}; + } // namespace void RunMessageLoop() { @@ -427,6 +467,15 @@ void NavigateToURLBlockUntilNavigationsComplete(Browser* browser, WaitForNavigations(controller, number_of_navigations); } +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; +} + Value* ExecuteJavaScript(RenderViewHost* render_view_host, const std::wstring& frame_xpath, const std::wstring& original_script) { diff --git a/chrome/test/ui_test_utils.h b/chrome/test/ui_test_utils.h index 68e7103..eca6fe5 100644 --- a/chrome/test/ui_test_utils.h +++ b/chrome/test/ui_test_utils.h @@ -17,6 +17,7 @@ #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_type.h" #include "chrome/common/notification_service.h" +#include "chrome/test/automation/dom_element_proxy.h" class AppModalDialog; class Browser; @@ -86,6 +87,10 @@ void NavigateToURLBlockUntilNavigationsComplete(Browser* browser, const GURL& url, int number_of_navigations); +// Gets the DOMDocument for the active tab in |browser|. +// Returns a NULL reference on failure. +DOMElementProxyRef GetActiveDOMDocument(Browser* browser); + // Executes the passed |script| in the frame pointed to by |frame_xpath| (use // empty string for main frame) and returns the value the evaluation of the // script returned. The caller owns the returned value. |