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 /chrome/test/automation | |
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
Diffstat (limited to 'chrome/test/automation')
-rw-r--r-- | chrome/test/automation/dom_automation_browsertest.cc | 246 | ||||
-rw-r--r-- | chrome/test/automation/dom_element_proxy.cc | 299 | ||||
-rw-r--r-- | chrome/test/automation/dom_element_proxy.h | 126 | ||||
-rw-r--r-- | chrome/test/automation/javascript_execution_controller.cc | 127 | ||||
-rw-r--r-- | chrome/test/automation/javascript_execution_controller.h | 146 |
5 files changed, 944 insertions, 0 deletions
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_ |