diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-16 16:34:15 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-16 16:34:15 +0000 |
commit | f18744fd5ec25ee780633e039a9f53d2cce3b49c (patch) | |
tree | 4d81c829dbf6e1710f546277a7907f5c0a928937 /chrome/test/automation | |
parent | 966ddf0958e0e3eb3a145c5a9908bc8662e174be (diff) | |
download | chromium_src-f18744fd5ec25ee780633e039a9f53d2cce3b49c.zip chromium_src-f18744fd5ec25ee780633e039a9f53d2cce3b49c.tar.gz chromium_src-f18744fd5ec25ee780633e039a9f53d2cce3b49c.tar.bz2 |
Add ability to manipulate DOM elements from the automation proxy. Rework the way that javascript is packaged and parsed in the JavascriptExecutionController, and add some waiting methods.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/1632001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@44778 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/automation')
-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 |
8 files changed, 628 insertions, 388 deletions
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_; |