diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-17 17:02:53 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-17 17:02:53 +0000 |
commit | 496e57908e1d53b286a9fb38339a558863b7068c (patch) | |
tree | e25648bbc8c2a4ce1899a0799cff3d8c8c94511a /chrome/test/webdriver | |
parent | 3c51bcfe87bda9ea798986c42b2e38503f4b0de8 (diff) | |
download | chromium_src-496e57908e1d53b286a9fb38339a558863b7068c.zip chromium_src-496e57908e1d53b286a9fb38339a558863b7068c.tar.gz chromium_src-496e57908e1d53b286a9fb38339a558863b7068c.tar.bz2 |
Implement the target locator commands for ChromeDriver.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/6507015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@75279 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/webdriver')
-rw-r--r-- | chrome/test/webdriver/WEBDRIVER_TESTS | 5 | ||||
-rw-r--r-- | chrome/test/webdriver/automation.cc | 209 | ||||
-rw-r--r-- | chrome/test/webdriver/automation.h | 57 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/command.cc | 8 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/command.h | 7 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/create_session.cc | 18 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/session_with_id.cc | 2 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/target_locator_commands.cc | 133 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/target_locator_commands.h | 84 | ||||
-rw-r--r-- | chrome/test/webdriver/server.cc | 25 | ||||
-rw-r--r-- | chrome/test/webdriver/session.cc | 227 | ||||
-rw-r--r-- | chrome/test/webdriver/session.h | 61 | ||||
-rw-r--r-- | chrome/test/webdriver/session_manager.cc | 37 | ||||
-rw-r--r-- | chrome/test/webdriver/session_manager.h | 4 |
14 files changed, 734 insertions, 143 deletions
diff --git a/chrome/test/webdriver/WEBDRIVER_TESTS b/chrome/test/webdriver/WEBDRIVER_TESTS index abbd9fc..f5ec2ac 100644 --- a/chrome/test/webdriver/WEBDRIVER_TESTS +++ b/chrome/test/webdriver/WEBDRIVER_TESTS @@ -51,7 +51,10 @@ '-executing_javascript_test.ExecutingJavaScriptTests.testShouldBeAbleToPassAStringAnAsArgument', '-executing_javascript_test.ExecutingJavaScriptTests.testShouldBeAbleToPassInMoreThanOneArgument', # 'form_handling_tests', -# 'frame_switching_tests', + 'frame_switching_tests', + # Next two frame switching tests are disabled because they depend on toggle. + '-frame_switching_tests.FrameSwitchingTest.testThatWeCanSwitchFrameByName', + '-frame_switching_tests.FrameSwitchingTest.testThatWeCanSwitchToFrameByIndex', # 'implicit_waits_tests', 'page_loading_tests', # testShouldReturnWhenGettingAUrlThatDoesNotResolve causes the test after diff --git a/chrome/test/webdriver/automation.cc b/chrome/test/webdriver/automation.cc index 19236b0..dfaae5a 100644 --- a/chrome/test/webdriver/automation.cc +++ b/chrome/test/webdriver/automation.cc @@ -10,6 +10,7 @@ #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/common/chrome_switches.h" +#include "chrome/test/automation/automation_proxy.h" #include "chrome/test/automation/browser_proxy.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome/test/test_launcher_utils.h" @@ -37,16 +38,6 @@ void Automation::Init(bool* success) { launch_arguments_.AppendSwitchPath(switches::kUserDataDir, profile_dir_.path()); UITestBase::SetUp(); - browser_ = automation()->GetBrowserWindow(0); - if (!browser_.get()) { - Terminate(); - return; - } - tab_ = browser_->GetActiveTab(); - if (!tab_.get()) { - Terminate(); - return; - } *success = true; } @@ -54,21 +45,51 @@ void Automation::Terminate() { QuitBrowser(); } -void Automation::ExecuteScript(const std::string& frame_xpath, +void Automation::ExecuteScript(int tab_id, + const std::string& frame_xpath, const std::string& script, std::string* result, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } std::wstring wide_xpath = UTF8ToWide(frame_xpath); std::wstring wide_script = UTF8ToWide(script); std::wstring wide_result; - *success = tab_->ExecuteAndExtractString( + *success = tab->ExecuteAndExtractString( wide_xpath, wide_script, &wide_result); if (*success) *result = WideToUTF8(wide_result); } -void Automation::SendWebKeyEvent(const WebKeyEvent& key_event, +void Automation::SendWebKeyEvent(int tab_id, + const WebKeyEvent& key_event, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + LOG(ERROR) << "No such tab"; + *success = false; + return; + } + int tab_index = 0; + if (!tab->GetTabIndex(&tab_index)) { + LOG(ERROR) << "Could not get tab index"; + *success = false; + return; + } + scoped_refptr<BrowserProxy> browser = tab->GetParentBrowser(); + if (!browser.get()) { + LOG(ERROR) << "Could not get parent browser of tab"; + *success = false; + return; + } + if (!browser->ActivateTab(tab_index)) { + LOG(ERROR) << "Could not activate tab to send keys"; + *success = false; + return; + } scoped_ptr<DictionaryValue> dict(new DictionaryValue); dict->SetString("command", "SendKeyEventToActiveTab"); dict->SetInteger("type", key_event.type); @@ -81,73 +102,189 @@ void Automation::SendWebKeyEvent(const WebKeyEvent& key_event, std::string request; base::JSONWriter::Write(dict.get(), false, &request); std::string reply; - *success = browser_->SendJSONRequest(request, &reply); + *success = browser->SendJSONRequest(request, &reply); if (!*success) { LOG(ERROR) << "Could not send web key event. Reply: " << reply; } } -void Automation::NavigateToURL(const std::string& url, +void Automation::NavigateToURL(int tab_id, + const std::string& url, bool* success) { - *success = tab_->NavigateToURL(GURL(url)); + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->NavigateToURL(GURL(url)); } -void Automation::GoForward(bool* success) { - *success = tab_->GoForward(); +void Automation::GoForward(int tab_id, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->GoForward(); } -void Automation::GoBack(bool* success) { - *success = tab_->GoBack(); +void Automation::GoBack(int tab_id, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->GoBack(); } -void Automation::Reload(bool* success) { - *success = tab_->Reload(); +void Automation::Reload(int tab_id, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->Reload(); } -void Automation::GetURL(std::string* url, +void Automation::GetURL(int tab_id, + std::string* url, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } GURL gurl; - *success = tab_->GetCurrentURL(&gurl); + *success = tab->GetCurrentURL(&gurl); if (*success) *url = gurl.possibly_invalid_spec(); } -void Automation::GetGURL(GURL* gurl, +void Automation::GetGURL(int tab_id, + GURL* gurl, bool* success) { - *success = tab_->GetCurrentURL(gurl); + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->GetCurrentURL(gurl); } -void Automation::GetTabTitle(std::string* tab_title, +void Automation::GetTabTitle(int tab_id, + std::string* tab_title, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } std::wstring wide_title; - *success = tab_->GetTabTitle(&wide_title); + *success = tab->GetTabTitle(&wide_title); if (*success) *tab_title = WideToUTF8(wide_title); } -void Automation::GetCookies(const GURL& gurl, +void Automation::GetCookies(int tab_id, + const GURL& gurl, std::string* cookies, bool* success) { - *success = tab_->GetCookies(gurl, cookies); + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->GetCookies(gurl, cookies); } -void Automation::GetCookieByName(const GURL& gurl, +void Automation::GetCookieByName(int tab_id, + const GURL& gurl, const std::string& cookie_name, std::string* cookie, bool* success) { - *success = tab_->GetCookieByName(gurl, cookie_name, cookie); + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->GetCookieByName(gurl, cookie_name, cookie); } -void Automation::DeleteCookie(const GURL& gurl, +void Automation::DeleteCookie(int tab_id, + const GURL& gurl, const std::string& cookie_name, bool* success) { - *success = tab_->DeleteCookie(gurl, cookie_name); + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->DeleteCookie(gurl, cookie_name); } -void Automation::SetCookie(const GURL& gurl, +void Automation::SetCookie(int tab_id, + const GURL& gurl, const std::string& cookie, bool* success) { - *success = tab_->SetCookie(gurl, cookie); + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->SetCookie(gurl, cookie); +} + +void Automation::GetTabIds(std::vector<int>* tab_ids, + bool* success) { + *success = false; + int browser_count = 0; + if (!automation()->GetBrowserWindowCount(&browser_count)) { + LOG(ERROR) << "Failed to get browser window count"; + return; + } + TabIdMap tab_id_map; + for (int browser_index = 0; browser_index < browser_count; ++browser_index) { + scoped_refptr<BrowserProxy> browser = + automation()->GetBrowserWindow(browser_index); + if (!browser.get()) + continue; + int tab_count = 0; + if (!browser->GetTabCount(&tab_count)) + continue; + + for (int tab_index = 0; tab_index < tab_count; ++tab_index) { + scoped_refptr<TabProxy> tab = browser->GetTab(tab_index); + if (!tab.get()) + continue; + tab_ids->push_back(tab->handle()); + tab_id_map.insert(std::make_pair(tab->handle(), tab)); + } + } + + tab_id_map_ = tab_id_map; + *success = true; +} + +void Automation::DoesTabExist(int tab_id, bool* does_exist) { + TabProxy* tab = GetTabById(tab_id); + *does_exist = tab && tab->is_valid(); +} + +void Automation::CloseTab(int tab_id, bool* success) { + TabProxy* tab = GetTabById(tab_id); + if (!tab) { + *success = false; + return; + } + *success = tab->Close(true); +} + +TabProxy* Automation::GetTabById(int tab_id) { + TabIdMap::const_iterator iter = tab_id_map_.find(tab_id); + if (iter != tab_id_map_.end()) { + return iter->second.get(); + } + return NULL; } } // namespace webdriver diff --git a/chrome/test/webdriver/automation.h b/chrome/test/webdriver/automation.h index 1d97bc2..34a1cca 100644 --- a/chrome/test/webdriver/automation.h +++ b/chrome/test/webdriver/automation.h @@ -5,7 +5,9 @@ #ifndef CHROME_TEST_WEBDRIVER_AUTOMATION_H_ #define CHROME_TEST_WEBDRIVER_AUTOMATION_H_ +#include <map> #include <string> +#include <vector> #include "base/task.h" #include "base/ref_counted.h" @@ -15,6 +17,7 @@ #include "ui/base/keycodes/keyboard_codes.h" class GURL; +class TabProxy; namespace webdriver { @@ -57,33 +60,53 @@ class Automation : private UITestBase { // Executes the given |script| in the specified frame of the current // tab. |result| will be set to the JSON result. Returns true on success. - void ExecuteScript(const std::string& frame_xpath, + void ExecuteScript(int tab_id, + const std::string& frame_xpath, const std::string& script, std::string* result, bool* success); // Sends a key event to the current browser. Waits until the key has // been processed by the web page. - void SendWebKeyEvent(const WebKeyEvent& key_event, bool* success); - - void NavigateToURL(const std::string& url, bool* success); - void GoForward(bool* success); - void GoBack(bool* success); - void Reload(bool* success); - void GetURL(std::string* url, bool* success); - void GetGURL(GURL* gurl, bool* success); - void GetTabTitle(std::string* tab_title, bool* success); - void GetCookies(const GURL& gurl, std::string* cookies, bool* success); - void GetCookieByName(const GURL& gurl, const std::string& cookie_name, - std::string* cookie, bool* success); - void DeleteCookie(const GURL& gurl, const std::string& cookie_name, + void SendWebKeyEvent(int tab_id, const WebKeyEvent& key_event, bool* success); + + void NavigateToURL(int tab_id, const std::string& url, bool* success); + void GoForward(int tab_id, bool* success); + void GoBack(int tab_id, bool* success); + void Reload(int tab_id, bool* success); + void GetURL(int tab_id, std::string* url, bool* success); + void GetGURL(int tab_id, GURL* gurl, bool* success); + void GetTabTitle(int tab_id, std::string* tab_title, bool* success); + void GetCookies( + int tab_id, const GURL& gurl, std::string* cookies, bool* success); + void GetCookieByName(int tab_id, + const GURL& gurl, + const std::string& cookie_name, + std::string* cookie, + bool* success); + void DeleteCookie(int tab_id, + const GURL& gurl, + const std::string& cookie_name, bool* success); - void SetCookie(const GURL& gurl, const std::string& cookie, bool* success); + void SetCookie( + int tab_id, const GURL& gurl, const std::string& cookie, bool* success); + + // Get persistent IDs for all the tabs currently open. These IDs can be used + // to identify the tab as long as the tab exists. + void GetTabIds(std::vector<int>* tab_ids, bool* success); + + // Check if the given tab exists currently. + void DoesTabExist(int tab_id, bool* does_exist); + + void CloseTab(int tab_id, bool* success); private: - scoped_refptr<BrowserProxy> browser_; - scoped_refptr<TabProxy> tab_; + typedef std::map<int, scoped_refptr<TabProxy> > TabIdMap; + TabProxy* GetTabById(int tab_id); + // Map from tab ID to |TabProxy|. The tab ID is simply the |AutomationHandle| + // for the proxy. + TabIdMap tab_id_map_; ScopedTempDir profile_dir_; DISALLOW_COPY_AND_ASSIGN(Automation); diff --git a/chrome/test/webdriver/commands/command.cc b/chrome/test/webdriver/commands/command.cc index 29ea60f..cb9e670 100644 --- a/chrome/test/webdriver/commands/command.cc +++ b/chrome/test/webdriver/commands/command.cc @@ -40,6 +40,13 @@ bool Command::Init(Response* const response) { return true; } +bool Command::IsNullParameter(const std::string& key) const { + Value* value; + return parameters_.get() && + parameters_->Get(key, &value) && + value->IsType(Value::TYPE_NULL); +} + bool Command::GetStringParameter(const std::string& key, string16* out) const { return parameters_.get() != NULL && parameters_->GetString(key, out); @@ -76,4 +83,3 @@ bool Command::GetListParameter(const std::string& key, } } // namespace webdriver - diff --git a/chrome/test/webdriver/commands/command.h b/chrome/test/webdriver/commands/command.h index 75cc9bc..9d3cd4e 100644 --- a/chrome/test/webdriver/commands/command.h +++ b/chrome/test/webdriver/commands/command.h @@ -54,7 +54,11 @@ class Command { return i < path_segments_.size() ? path_segments_.at(i) : ""; } - // Provides the command parameter with the given |key| as a UTF-16 string. + // Returns true if the command parameter with the given |key| exists and is + // a null value. + bool IsNullParameter(const std::string& key) const; + + // Returns the command parameter with the given |key| as a UTF-16 string. // Returns true on success. bool GetStringParameter(const std::string& key, string16* out) const; @@ -100,4 +104,3 @@ class Command { } // namespace webdriver #endif // CHROME_TEST_WEBDRIVER_COMMANDS_COMMAND_H_ - diff --git a/chrome/test/webdriver/commands/create_session.cc b/chrome/test/webdriver/commands/create_session.cc index 9f30d15..8634444 100644 --- a/chrome/test/webdriver/commands/create_session.cc +++ b/chrome/test/webdriver/commands/create_session.cc @@ -24,28 +24,20 @@ CreateSession::~CreateSession() {} bool CreateSession::DoesPost() { return true; } void CreateSession::ExecutePost(Response* const response) { - SessionManager* session_manager = SessionManager::GetInstance(); - Session* session = session_manager->Create(); - if (!session) { - SET_WEBDRIVER_ERROR(response, - "Failed to create session", - kInternalServerError); - return; - } - - std::string session_id = session->id(); + // Session manages its own liftime, so do not call delete. + Session* session = new Session(); if (!session->Init()) { - session_manager->Delete(session_id); SET_WEBDRIVER_ERROR(response, "Failed to initialize session", kInternalServerError); return; } - VLOG(1) << "Created session " << session_id; + SessionManager* session_manager = SessionManager::GetInstance(); + VLOG(1) << "Created session " << session->id(); std::ostringstream stream; stream << "http://" << session_manager->GetAddress() << "/session/" - << session_id; + << session->id(); response->set_status(kSeeOther); response->set_value(Value::CreateStringValue(stream.str())); } diff --git a/chrome/test/webdriver/commands/session_with_id.cc b/chrome/test/webdriver/commands/session_with_id.cc index ef3cd64..ef7c1da 100644 --- a/chrome/test/webdriver/commands/session_with_id.cc +++ b/chrome/test/webdriver/commands/session_with_id.cc @@ -55,8 +55,8 @@ void SessionWithID::ExecuteGet(Response* const response) { } void SessionWithID::ExecuteDelete(Response* const response) { + // Session manages its own liftime, so do not call delete. session_->Terminate(); - SessionManager::GetInstance()->Delete(session_->id()); response->set_status(kSuccess); } diff --git a/chrome/test/webdriver/commands/target_locator_commands.cc b/chrome/test/webdriver/commands/target_locator_commands.cc new file mode 100644 index 0000000..8808ca4 --- /dev/null +++ b/chrome/test/webdriver/commands/target_locator_commands.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2011 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/webdriver/commands/target_locator_commands.h" + +#include "base/string_number_conversions.h" +#include "base/values.h" +#include "chrome/test/webdriver/commands/response.h" +#include "chrome/test/webdriver/error_codes.h" +#include "chrome/test/webdriver/session.h" + +namespace webdriver { + +WindowHandleCommand::WindowHandleCommand( + const std::vector<std::string>& path_segments, + DictionaryValue* parameters) + : WebDriverCommand(path_segments, parameters) {} + +WindowHandleCommand::~WindowHandleCommand() {} + +bool WindowHandleCommand::DoesGet() { + return true; +} + +void WindowHandleCommand::ExecuteGet(Response* const response) { + response->set_status(kSuccess); + response->set_value(new StringValue( + base::IntToString(session_->current_window_id()))); +} + +WindowHandlesCommand::WindowHandlesCommand( + const std::vector<std::string>& path_segments, + DictionaryValue* parameters) + : WebDriverCommand(path_segments, parameters) {} + +WindowHandlesCommand::~WindowHandlesCommand() {} + +bool WindowHandlesCommand::DoesGet() { + return true; +} + +void WindowHandlesCommand::ExecuteGet(Response* const response) { + std::vector<int> window_ids; + if (!session_->GetWindowIds(&window_ids)) { + SET_WEBDRIVER_ERROR( + response, "Could not get window handles", kInternalServerError); + return; + } + ListValue* id_list = new ListValue(); + for (size_t i = 0; i < window_ids.size(); ++i) + id_list->Append(new StringValue(base::IntToString(window_ids[i]))); + response->set_status(kSuccess); + response->set_value(id_list); +} + +WindowCommand::WindowCommand( + const std::vector<std::string>& path_segments, + DictionaryValue* parameters) + : WebDriverCommand(path_segments, parameters) {} + +WindowCommand::~WindowCommand() {} + +bool WindowCommand::DoesPost() { + return true; +} + +bool WindowCommand::DoesDelete() { + return true; +} + +void WindowCommand::ExecutePost(Response* const response) { + std::string name; + if (!GetStringParameter("name", &name)) { + SET_WEBDRIVER_ERROR( + response, "Missing or invalid 'name' parameter", kBadRequest); + return; + } + + ErrorCode code = session_->SwitchToWindow(name); + if (code != kSuccess) { + SET_WEBDRIVER_ERROR(response, "Could not switch window", code); + return; + } + response->set_status(kSuccess); +} + +void WindowCommand::ExecuteDelete(Response* const response) { + if (!session_->CloseWindow()) { + SET_WEBDRIVER_ERROR( + response, "Could not close window", kInternalServerError); + return; + } + response->set_status(kSuccess); +} + +SwitchFrameCommand::SwitchFrameCommand( + const std::vector<std::string>& path_segments, + DictionaryValue* parameters) + : WebDriverCommand(path_segments, parameters) {} + +SwitchFrameCommand::~SwitchFrameCommand() {} + +bool SwitchFrameCommand::DoesPost() { + return true; +} + +void SwitchFrameCommand::ExecutePost(Response* const response) { + std::string id; + int index = 0; + if (GetStringParameter("id", &id)) { + ErrorCode code = session_->SwitchToFrameWithNameOrId(id); + if (code != kSuccess) { + SET_WEBDRIVER_ERROR(response, "Could not switch to frame", code); + return; + } + } else if (GetIntegerParameter("id", &index)) { + ErrorCode code = session_->SwitchToFrameWithIndex(index); + if (code != kSuccess) { + SET_WEBDRIVER_ERROR(response, "Could not switch to frame", code); + return; + } + } else if (IsNullParameter("id")) { + session_->set_current_frame_xpath(""); + } else { + SET_WEBDRIVER_ERROR( + response, "Missing or invalid 'id' parameter", kBadRequest); + return; + } + response->set_status(kSuccess); +} + +} // namespace webdriver diff --git a/chrome/test/webdriver/commands/target_locator_commands.h b/chrome/test/webdriver/commands/target_locator_commands.h new file mode 100644 index 0000000..d9093cc --- /dev/null +++ b/chrome/test/webdriver/commands/target_locator_commands.h @@ -0,0 +1,84 @@ +// Copyright (c) 2011 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_WEBDRIVER_COMMANDS_TARGET_LOCATOR_COMMANDS_H_ +#define CHROME_TEST_WEBDRIVER_COMMANDS_TARGET_LOCATOR_COMMANDS_H_ + +#include <string> +#include <vector> + +#include "chrome/test/webdriver/commands/webdriver_command.h" + +class DictionaryValue; + +namespace webdriver { + +class Response; + +// Gets the current window handle. +// REST URL: /session/:sessionId/window_handle +class WindowHandleCommand : public WebDriverCommand { + public: + WindowHandleCommand(const std::vector<std::string>& path_segments, + DictionaryValue* parameters); + virtual ~WindowHandleCommand(); + + virtual bool DoesGet(); + virtual void ExecuteGet(Response* const response); + + private: + DISALLOW_COPY_AND_ASSIGN(WindowHandleCommand); +}; + +// Gets a list of all window handles. +// REST URL: /session/:sessionId/window_handles +class WindowHandlesCommand : public WebDriverCommand { + public: + WindowHandlesCommand(const std::vector<std::string>& path_segments, + DictionaryValue* parameters); + virtual ~WindowHandlesCommand(); + + virtual bool DoesGet(); + virtual void ExecuteGet(Response* const response); + + private: + DISALLOW_COPY_AND_ASSIGN(WindowHandlesCommand); +}; + +// Switches to the given window as the default window to execute commands on +// or closes it. +// REST URL: /session/:sessionId/window +class WindowCommand : public WebDriverCommand { + public: + WindowCommand(const std::vector<std::string>& path_segments, + DictionaryValue* parameters); + virtual ~WindowCommand(); + + virtual bool DoesPost(); + virtual bool DoesDelete(); + virtual void ExecutePost(Response* const response); + virtual void ExecuteDelete(Response* const response); + + private: + DISALLOW_COPY_AND_ASSIGN(WindowCommand); +}; + +// Switches to the given frame as the default frame to execute commands in. +// REST URL: /session/:sessionId/frame +class SwitchFrameCommand : public WebDriverCommand { + public: + SwitchFrameCommand(const std::vector<std::string>& path_segments, + DictionaryValue* parameters); + virtual ~SwitchFrameCommand(); + + virtual bool DoesPost(); + virtual void ExecutePost(Response* const response); + + private: + DISALLOW_COPY_AND_ASSIGN(SwitchFrameCommand); +}; + +} // namespace webdriver + +#endif // CHROME_TEST_WEBDRIVER_COMMANDS_TARGET_LOCATOR_COMMANDS_H_ diff --git a/chrome/test/webdriver/server.cc b/chrome/test/webdriver/server.cc index 829f088..274820a 100644 --- a/chrome/test/webdriver/server.cc +++ b/chrome/test/webdriver/server.cc @@ -38,6 +38,7 @@ #include "chrome/test/webdriver/commands/session_with_id.h" #include "chrome/test/webdriver/commands/source_command.h" #include "chrome/test/webdriver/commands/speed_command.h" +#include "chrome/test/webdriver/commands/target_locator_commands.h" #include "chrome/test/webdriver/commands/title_command.h" #include "chrome/test/webdriver/commands/url_command.h" #include "chrome/test/webdriver/commands/webelement_commands.h" @@ -79,16 +80,20 @@ void InitCallbacks(struct mg_context* ctx, base::WaitableEvent* shutdown_event) { mg_set_uri_callback(ctx, "/shutdown", &Shutdown, shutdown_event); - SetCallback<CreateSession>(ctx, "/session"); - SetCallback<BackCommand>(ctx, "/session/*/back"); - SetCallback<ExecuteCommand>(ctx, "/session/*/execute"); - SetCallback<ForwardCommand>(ctx, "/session/*/forward"); - SetCallback<RefreshCommand>(ctx, "/session/*/refresh"); - SetCallback<SourceCommand>(ctx, "/session/*/source"); - SetCallback<TitleCommand>(ctx, "/session/*/title"); - SetCallback<URLCommand>(ctx, "/session/*/url"); - SetCallback<SpeedCommand>(ctx, "/session/*/speed"); - SetCallback<ImplicitWaitCommand>(ctx, "/session/*/timeouts/implicit_wait"); + SetCallback<CreateSession>(ctx, "/session"); + SetCallback<BackCommand>(ctx, "/session/*/back"); + SetCallback<ExecuteCommand>(ctx, "/session/*/execute"); + SetCallback<ForwardCommand>(ctx, "/session/*/forward"); + SetCallback<RefreshCommand>(ctx, "/session/*/refresh"); + SetCallback<SourceCommand>(ctx, "/session/*/source"); + SetCallback<TitleCommand>(ctx, "/session/*/title"); + SetCallback<URLCommand>(ctx, "/session/*/url"); + SetCallback<SpeedCommand>(ctx, "/session/*/speed"); + SetCallback<ImplicitWaitCommand>(ctx, "/session/*/timeouts/implicit_wait"); + SetCallback<WindowHandleCommand>(ctx, "/session/*/window_handle"); + SetCallback<WindowHandlesCommand>(ctx, "/session/*/window_handles"); + SetCallback<WindowCommand>(ctx, "/session/*/window"); + SetCallback<SwitchFrameCommand>(ctx, "/session/*/frame"); // Cookie functions. SetCallback<CookieCommand>(ctx, "/session/*/cookie"); diff --git a/chrome/test/webdriver/session.cc b/chrome/test/webdriver/session.cc index bfbefeb..849e7c6 100644 --- a/chrome/test/webdriver/session.cc +++ b/chrome/test/webdriver/session.cc @@ -12,7 +12,9 @@ #include "base/message_loop_proxy.h" #include "base/process.h" #include "base/process_util.h" +#include "base/scoped_ptr.h" #include "base/stringprintf.h" +#include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" @@ -22,6 +24,7 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/test_launcher_utils.h" +#include "chrome/test/webdriver/session_manager.h" #include "chrome/test/webdriver/utility_functions.h" #include "chrome/test/webdriver/webdriver_key_converter.h" #include "googleurl/src/gurl.h" @@ -29,27 +32,31 @@ namespace webdriver { -Session::Session(const std::string& id) - : thread_(id.c_str()), - id_(id), - window_num_(0), +Session::Session() + : id_(GenerateRandomID()), + thread_(id_.c_str()), implicit_wait_(0), - current_frame_xpath_("") { + current_frame_xpath_(""), + current_window_id_(0) { + SessionManager::GetInstance()->Add(this); } -Session::~Session() {} +Session::~Session() { + SessionManager::GetInstance()->Remove(id_); +} bool Session::Init() { - if (!thread_.Start()) { + bool success = false; + if (thread_.Start()) { + RunSessionTask(NewRunnableMethod( + this, + &Session::InitOnSessionThread, + &success)); + } else { LOG(ERROR) << "Cannot start session thread"; - return false; } - - bool success = false; - RunSessionTask(NewRunnableMethod( - this, - &Session::InitOnSessionThread, - &success)); + if (!success) + delete this; return success; } @@ -57,9 +64,12 @@ void Session::Terminate() { RunSessionTask(NewRunnableMethod( this, &Session::TerminateOnSessionThread)); + delete this; } -ErrorCode Session::ExecuteScript(const std::string& script, +ErrorCode Session::ExecuteScript(int window_id, + const std::string& frame_xpath, + const std::string& script, const ListValue* const args, Value** value) { std::string args_as_json; @@ -80,15 +90,17 @@ ErrorCode Session::ExecuteScript(const std::string& script, VLOG(1) << "Executing script in frame: " << current_frame_xpath_; std::string result; - bool success; + bool success = false; RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::ExecuteScript, - current_frame_xpath_, + window_id, + frame_xpath, jscript, &result, &success)); if (!success) { + LOG(ERROR) << "Automation failed to execute script"; *value = Value::CreateStringValue( "Unknown internal script execution failure"); return kUnknownError; @@ -98,12 +110,14 @@ ErrorCode Session::ExecuteScript(const std::string& script, scoped_ptr<Value> r(base::JSONReader::ReadAndReturnError( result, true, NULL, NULL)); if (!r.get()) { + LOG(ERROR) << "Failed to parse script result"; *value = Value::CreateStringValue( "Internal script execution error: failed to parse script result"); return kUnknownError; } if (r->GetType() != Value::TYPE_DICTIONARY) { + LOG(ERROR) << "Execute script returned non-dictionary type"; std::ostringstream stream; stream << "Internal script execution error: script result must be a " << print_valuetype(Value::TYPE_DICTIONARY) << ", but was " @@ -130,6 +144,13 @@ ErrorCode Session::ExecuteScript(const std::string& script, return static_cast<ErrorCode>(status); } +ErrorCode Session::ExecuteScript(const std::string& script, + const ListValue* const args, + Value** value) { + return ExecuteScript( + current_window_id_, current_frame_xpath_, script, args, value); +} + ErrorCode Session::SendKeys(DictionaryValue* element, const string16& keys) { ListValue args; args.Append(element); @@ -138,8 +159,10 @@ ErrorCode Session::SendKeys(DictionaryValue* element, const string16& keys) { Value* unscoped_result = NULL; ErrorCode code = ExecuteScript(script, &args, &unscoped_result); scoped_ptr<Value> result(unscoped_result); - if (code != kSuccess) + if (code != kSuccess) { + LOG(ERROR) << "Failed to focus element before sending keys"; return code; + } bool success = false; RunSessionTask(NewRunnableMethod( @@ -157,6 +180,7 @@ bool Session::NavigateToURL(const std::string& url) { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::NavigateToURL, + current_window_id_, url, &success)); return success; @@ -167,6 +191,7 @@ bool Session::GoForward() { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::GoForward, + current_window_id_, &success)); return success; } @@ -176,6 +201,7 @@ bool Session::GoBack() { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::GoBack, + current_window_id_, &success)); return success; } @@ -185,6 +211,7 @@ bool Session::Reload() { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::Reload, + current_window_id_, &success)); return success; } @@ -194,6 +221,7 @@ bool Session::GetURL(std::string* url) { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::GetURL, + current_window_id_, url, &success)); return success; @@ -204,6 +232,7 @@ bool Session::GetURL(GURL* gurl) { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::GetGURL, + current_window_id_, gurl, &success)); return success; @@ -214,6 +243,7 @@ bool Session::GetTabTitle(std::string* tab_title) { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::GetTabTitle, + current_window_id_, tab_title, &success)); return success; @@ -224,6 +254,7 @@ bool Session::GetCookies(const GURL& url, std::string* cookies) { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::GetCookies, + current_window_id_, url, cookies, &success)); @@ -237,6 +268,7 @@ bool Session::GetCookieByName(const GURL& url, RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::GetCookieByName, + current_window_id_, url, cookie_name, cookie, @@ -249,6 +281,7 @@ bool Session::DeleteCookie(const GURL& url, const std::string& cookie_name) { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::DeleteCookie, + current_window_id_, url, cookie_name, &success)); @@ -260,12 +293,131 @@ bool Session::SetCookie(const GURL& url, const std::string& cookie) { RunSessionTask(NewRunnableMethod( automation_.get(), &Automation::SetCookie, + current_window_id_, url, cookie, &success)); return success; } +bool Session::GetWindowIds(std::vector<int>* window_ids) { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::GetTabIds, + window_ids, + &success)); + return success; +} + +ErrorCode Session::SwitchToWindow(const std::string& name) { + int switch_to_id = 0; + int name_no = 0; + if (base::StringToInt(name, &name_no)) { + bool does_exist = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::DoesTabExist, + name_no, + &does_exist)); + if (does_exist) + switch_to_id = name_no; + } + + if (!switch_to_id) { + std::vector<int> window_ids; + GetWindowIds(&window_ids); + // See if any of the window names match |name|. + for (size_t i = 0; i < window_ids.size(); ++i) { + ListValue empty_list; + Value* unscoped_name_value; + std::string window_name; + ErrorCode code = ExecuteScript(window_ids[i], + "", + "return window.name;", + &empty_list, + &unscoped_name_value); + scoped_ptr<Value> name_value(unscoped_name_value); + if (code == kSuccess && + name_value->GetAsString(&window_name) && + name == window_name) { + switch_to_id = window_ids[i]; + break; + } + } + } + + if (!switch_to_id) + return kNoSuchWindow; + current_window_id_ = switch_to_id; + current_frame_xpath_ = ""; + return kSuccess; +} + +ErrorCode Session::SwitchToFrameWithNameOrId(const std::string& name_or_id) { + std::string script = + "var arg = arguments[0];" + "var xpath = '(/html/body//iframe|/html/frameset/frame)';" + "var sub = function(s) { return s.replace(/\\$/g, arg); };" + "xpath += sub('[@name=\"$\" or @id=\"$\"]');" + "var frame = document.evaluate(xpath, document, null, " + " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" + "if (!frame) { return null; }" + "xpath = frame.tagName == 'IFRAME' ? '/html/body//iframe'" + " : '/html/frameset/frame';" + "return xpath + sub('[@' + (frame.id == arg ? 'id' : 'name')" + " + '=\"$\"]');"; + ListValue args; + args.Append(new StringValue(name_or_id)); + return SwitchToFrameWithJavaScriptLocatedFrame(script, &args); +} + +ErrorCode Session::SwitchToFrameWithIndex(int index) { + // We cannot simply index into window.frames because we need to know the + // tagName of the frameElement. If child frame N is from another domain, then + // the following will run afoul of the same origin policy: + // window.frames[N].frameElement; + // Instead of indexing window.frames, we use a an XPath expression to index + // into the list of all IFRAME and FRAME elements on the page - if we find + // something, then that XPath expression can be used as the new frame's XPath. + std::string script = + "var index = '[' + (arguments[0] + 1) + ']';" + "var xpath = '(/html/body//iframe|/html/frameset/frame)' + " + " index;" + "console.info('searching for frame by xpath: ' + xpath);" + "var frame = document.evaluate(xpath, document, null, " + "XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;" + "console.info(frame == null ? 'found nothing' : frame);" + "return frame == null ? null : ((frame.tagName == 'IFRAME' ? " + " '/html/body//iframe' : '/html/frameset/frame') + index);"; + ListValue args; + args.Append(Value::CreateIntegerValue(index)); + return SwitchToFrameWithJavaScriptLocatedFrame(script, &args); +} + +bool Session::CloseWindow() { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::CloseTab, + current_window_id_, + &success)); + + if (success) { + std::vector<int> window_ids; + if (!GetWindowIds(&window_ids) || window_ids.empty()) { + // The automation connection will soon be closed, if not already, + // because we supposedly just closed the last window. Terminate the + // session. + // TODO(kkania): This will cause us problems if GetWindowIds fails for a + // reason other than the channel is disconnected. Look into having + // |GetWindowIds| tell us if it just closed the last window. + Terminate(); + } + } + return success; +} + void Session::RunSessionTask(Task* task) { base::WaitableEvent done_event(false, false); thread_.message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod( @@ -286,10 +438,26 @@ void Session::RunSessionTaskOnSessionThread(Task* task, void Session::InitOnSessionThread(bool* success) { automation_.reset(new Automation()); automation_->Init(success); + if (!*success) + return; + + std::vector<int> tab_ids; + automation_->GetTabIds(&tab_ids, success); + if (!*success) { + LOG(ERROR) << "Could not get tab ids"; + return; + } + if (tab_ids.empty()) { + LOG(ERROR) << "No tab ids after initialization"; + *success = false; + } else { + current_window_id_ = tab_ids[0]; + } } void Session::TerminateOnSessionThread() { - automation_->Terminate(); + if (automation_.get()) + automation_->Terminate(); automation_.reset(); } @@ -300,7 +468,8 @@ void Session::SendKeysOnSessionThread(const string16& keys, ConvertKeysToWebKeyEvents(keys, &key_events); for (size_t i = 0; i < key_events.size(); ++i) { bool key_success = false; - automation_->SendWebKeyEvent(key_events[i], &key_success); + automation_->SendWebKeyEvent( + current_window_id_, key_events[i], &key_success); if (!key_success) { LOG(ERROR) << "Failed to send key event. Event details:\n" << "Type: " << key_events[i].type << "\n" @@ -313,4 +482,22 @@ void Session::SendKeysOnSessionThread(const string16& keys, } } +ErrorCode Session::SwitchToFrameWithJavaScriptLocatedFrame( + const std::string& script, + ListValue* args) { + Value* unscoped_result = NULL; + ErrorCode code = ExecuteScript(script, args, &unscoped_result); + scoped_ptr<Value> result(unscoped_result); + if (code != kSuccess) + return code; + std::string xpath; + if (result->GetAsString(&xpath)) { + if (current_frame_xpath_.length()) + current_frame_xpath_ += "\n"; + current_frame_xpath_ += xpath; + return kSuccess; + } + return kNoSuchFrame; +} + } // namespace webdriver diff --git a/chrome/test/webdriver/session.h b/chrome/test/webdriver/session.h index 8e813d4..99c8f71 100644 --- a/chrome/test/webdriver/session.h +++ b/chrome/test/webdriver/session.h @@ -5,6 +5,7 @@ #ifndef CHROME_TEST_WEBDRIVER_SESSION_H_ #define CHROME_TEST_WEBDRIVER_SESSION_H_ +#include <map> #include <string> #include "base/scoped_ptr.h" @@ -25,25 +26,36 @@ namespace webdriver { // Every connection made by WebDriver maps to a session object. // This object creates the chrome instance and keeps track of the // state necessary to control the chrome browser created. +// A session manages its own lifetime. // TODO(phajdan.jr): Abstract UITestBase classes, see: // http://code.google.com/p/chromium/issues/detail?id=56865 class Session { public: - explicit Session(const std::string& id); + // Adds this |Session| to the |SessionManager|. The session manages its own + // lifetime. Do not call delete. + Session(); + // Removes this |Session| from the |SessionManager|. ~Session(); - // Creates a browser. + // Starts the session thread and a new browser. Returns true on + // success. On failure, the session will delete itself and return false. bool Init(); - // Terminates this session and disconnects its automation proxy. After - // invoking this method, the Session can safely be deleted. + // Terminates this session and deletes itself. void Terminate(); - // Executes the given |script| in the context of the frame that is currently - // the focus of this session. The |script| should be in the form of a - // function body (e.g. "return arguments[0]"), where |args| is the list of - // arguments to pass to the function. The caller is responsible for the - // script result |value|. + // Executes the given |script| in the context of the given window and frame. + // The |script| should be in the form of a function body + // (e.g. "return arguments[0]"), where |args| is the list of arguments to + // pass to the function. The caller is responsible for the script result + // |value|. + ErrorCode ExecuteScript(int window_id, + const std::string& frame_xpath, + const std::string& script, + const ListValue* const args, + Value** value); + + // Same as above, but uses the currently targeted window and frame. ErrorCode ExecuteScript(const std::string& script, const ListValue* const args, Value** value); @@ -65,6 +77,25 @@ class Session { bool DeleteCookie(const GURL& url, const std::string& cookie_name); bool SetCookie(const GURL& url, const std::string& cookie); + // Gets all the currently existing window IDs. Returns true on success. + bool GetWindowIds(std::vector<int>* window_ids); + + // Switches the window used by default. |name| is either an ID returned by + // |GetWindowIds| or the name attribute of a DOM window. + ErrorCode SwitchToWindow(const std::string& name); + + // Switches the frame used by default. |name_or_id| is either the name or id + // of a frame element. + ErrorCode SwitchToFrameWithNameOrId(const std::string& name_or_id); + + // Switches the frame used by default. |index| is the zero-based frame index. + ErrorCode SwitchToFrameWithIndex(int index); + + // Closes the current window. Returns true on success. + // Note: The session will be deleted if this closes the last window in the + // session. + bool CloseWindow(); + inline const std::string& id() const { return id_; } inline int implicit_wait() { return implicit_wait_; } @@ -86,6 +117,8 @@ class Session { current_frame_xpath_ = xpath; } + inline int current_window_id() const { return current_window_id_; } + private: void RunSessionTask(Task* task); void RunSessionTaskOnSessionThread( @@ -94,13 +127,14 @@ class Session { void InitOnSessionThread(bool* success); void TerminateOnSessionThread(); void SendKeysOnSessionThread(const string16& keys, bool* success); - - scoped_ptr<Automation> automation_; - base::Thread thread_; + ErrorCode SwitchToFrameWithJavaScriptLocatedFrame( + const std::string& script, + ListValue* args); const std::string id_; - int window_num_; + scoped_ptr<Automation> automation_; + base::Thread thread_; int implicit_wait_; Speed speed_; @@ -112,6 +146,7 @@ class Session { // should break into 2 xpaths // /html/body/table/tbody/tr/td/iframe & /frameset/frame[1]. std::string current_frame_xpath_; + int current_window_id_; DISALLOW_COPY_AND_ASSIGN(Session); }; diff --git a/chrome/test/webdriver/session_manager.cc b/chrome/test/webdriver/session_manager.cc index ed9a113..8c52fa2 100644 --- a/chrome/test/webdriver/session_manager.cc +++ b/chrome/test/webdriver/session_manager.cc @@ -30,20 +30,9 @@ std::string SessionManager::GetAddress() { return hostname + ":" + port_; } -Session* SessionManager::Create() { - std::string id = GenerateRandomID(); - { - base::AutoLock lock(map_lock_); - if (map_.find(id) != map_.end()) { - LOG(ERROR) << "Failed to generate a unique session ID"; - return NULL; - } - } - - Session* session = new Session(id); +void SessionManager::Add(Session* session) { base::AutoLock lock(map_lock_); - map_[id] = session; - return session; + map_[session->id()] = session; } bool SessionManager::Has(const std::string& id) const { @@ -51,23 +40,17 @@ bool SessionManager::Has(const std::string& id) const { return map_.find(id) != map_.end(); } -bool SessionManager::Delete(const std::string& id) { +bool SessionManager::Remove(const std::string& id) { std::map<std::string, Session*>::iterator it; - Session* session; - { - base::AutoLock lock(map_lock_); - it = map_.find(id); - if (it == map_.end()) { - VLOG(1) << "No such session with ID " << id; - return false; - } - session = it->second; - map_.erase(it); + base::AutoLock lock(map_lock_); + it = map_.find(id); + if (it == map_.end()) { + VLOG(1) << "No such session with ID " << id; + return false; } - - VLOG(1) << "Deleting session with ID " << id; - delete session; + session = it->second; + map_.erase(it); return true; } diff --git a/chrome/test/webdriver/session_manager.h b/chrome/test/webdriver/session_manager.h index 1096d83..5742e3a 100644 --- a/chrome/test/webdriver/session_manager.h +++ b/chrome/test/webdriver/session_manager.h @@ -26,8 +26,8 @@ class SessionManager { std::string GetAddress(); - Session* Create(); - bool Delete(const std::string& id); + void Add(Session* session); + bool Remove(const std::string& id); bool Has(const std::string& id) const; Session* GetSession(const std::string& id) const; |