diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-03 21:49:03 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-02-03 21:49:03 +0000 |
commit | 2a28e0b2f08036c00598eadfd5bf6bb7fbe0d339 (patch) | |
tree | 79d96f98e81ea8942c876b52a7f78baa0a5a179d | |
parent | 9e1bdd3f9785ed8a84a137c98b781ddaec41b080 (diff) | |
download | chromium_src-2a28e0b2f08036c00598eadfd5bf6bb7fbe0d339.zip chromium_src-2a28e0b2f08036c00598eadfd5bf6bb7fbe0d339.tar.gz chromium_src-2a28e0b2f08036c00598eadfd5bf6bb7fbe0d339.tar.bz2 |
Break Session off into a separate Automation class, which is just focused on controlling Chrome. Have Session create its own thread which automation messages are executed on.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/6306013
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@73670 0039d316-1c4b-4281-b951-d872f2087c98
21 files changed, 501 insertions, 405 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 2e34646..0d22801 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -710,6 +710,8 @@ '../third_party/mongoose/mongoose.h', '../third_party/mongoose/mongoose.c', '../third_party/webdriver/atoms.h', + 'test/webdriver/automation.h', + 'test/webdriver/automation.cc', 'test/webdriver/dispatch.h', 'test/webdriver/dispatch.cc', 'test/webdriver/error_codes.h', @@ -747,6 +749,8 @@ 'test/webdriver/commands/url_command.cc', 'test/webdriver/commands/webdriver_command.h', 'test/webdriver/commands/webdriver_command.cc', + 'test/webdriver/commands/webelement_command.h', + 'test/webdriver/commands/webelement_command.cc', ], 'conditions': [ ['OS=="linux"', { diff --git a/chrome/test/webdriver/automation.cc b/chrome/test/webdriver/automation.cc new file mode 100644 index 0000000..e0cd968 --- /dev/null +++ b/chrome/test/webdriver/automation.cc @@ -0,0 +1,95 @@ +// 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/automation.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/test_launcher_utils.h" +#include "googleurl/src/gurl.h" + +namespace webdriver { + +void Automation::Init(bool* success) { + *success = false; + + // Create a temp directory for the new profile. + if (!profile_dir_.CreateUniqueTempDir()) { + LOG(ERROR) << "Could not make a temp profile directory"; + return; + } + // TODO(kkania): See if these are still needed. + test_launcher_utils::PrepareBrowserCommandLineForTests(&launch_arguments_); + launch_arguments_.AppendSwitch(switches::kDomAutomationController); + launch_arguments_.AppendSwitch(switches::kFullMemoryCrashReport); + + 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; +} + +void Automation::Terminate() { + QuitBrowser(); +} + +void Automation::ExecuteScript(const std::string& frame_xpath, + const std::string& script, + std::string* result, + bool* success) { + std::wstring wide_xpath = UTF8ToWide(frame_xpath); + std::wstring wide_script = UTF8ToWide(script); + std::wstring wide_result; + *success = tab_->ExecuteAndExtractString( + wide_xpath, wide_script, &wide_result); + if (*success) + *result = WideToUTF8(wide_result); +} + +void Automation::NavigateToURL(const std::string& url, + bool* success) { + *success = tab_->NavigateToURL(GURL(url)); +} + +void Automation::GoForward(bool* success) { + *success = tab_->GoForward(); +} + +void Automation::GoBack(bool* success) { + *success = tab_->GoBack(); +} + +void Automation::Reload(bool* success) { + *success = tab_->Reload(); +} + +void Automation::GetURL(std::string* url, + bool* success) { + GURL gurl; + *success = tab_->GetCurrentURL(&gurl); + if (*success) + *url = gurl.possibly_invalid_spec(); +} + +void Automation::GetTabTitle(std::string* tab_title, + bool* success) { + std::wstring wide_title; + *success = tab_->GetTabTitle(&wide_title); + if (*success) + *tab_title = WideToUTF8(wide_title); +} + +} // namespace webdriver diff --git a/chrome/test/webdriver/automation.h b/chrome/test/webdriver/automation.h new file mode 100644 index 0000000..4bf8758 --- /dev/null +++ b/chrome/test/webdriver/automation.h @@ -0,0 +1,61 @@ +// 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_AUTOMATION_H_ +#define CHROME_TEST_WEBDRIVER_AUTOMATION_H_ + +#include <string> + +#include "base/task.h" +#include "base/ref_counted.h" +#include "base/scoped_temp_dir.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/ui/ui_test.h" + +namespace webdriver { + +// Creates and controls the Chrome instance. +// This class should be created and accessed on a single thread. +// TODO(phajdan.jr): Abstract UITestBase classes, see: +// http://code.google.com/p/chromium/issues/detail?id=56865 +class Automation : private UITestBase { + public: + Automation() {} + + // Creates a browser. + void Init(bool* success); + + // Terminates this session and disconnects its automation proxy. After + // invoking this method, the Automation can safely be deleted. + void Terminate(); + + // 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, + const std::string& script, + std::string* result, + 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 GetTabTitle(std::string* tab_title, bool* success); + + private: + scoped_refptr<BrowserProxy> browser_; + scoped_refptr<TabProxy> tab_; + + ScopedTempDir profile_dir_; + + DISALLOW_COPY_AND_ASSIGN(Automation); +}; + +} // namespace webdriver + +DISABLE_RUNNABLE_METHOD_REFCOUNT(webdriver::Automation); + +#endif // CHROME_TEST_WEBDRIVER_AUTOMATION_H_ diff --git a/chrome/test/webdriver/chromedriver_launcher.py b/chrome/test/webdriver/chromedriver_launcher.py index 4c91a35..1777525 100644 --- a/chrome/test/webdriver/chromedriver_launcher.py +++ b/chrome/test/webdriver/chromedriver_launcher.py @@ -12,10 +12,15 @@ For ChromeDriver documentation, refer to: import logging import os import platform -import signal import subprocess import sys -import threading + +if sys.version_info < (2,6): + # Subprocess.Popen.kill is not available prior to 2.6. + if platform.system() == 'Windows': + import win32api + else: + import signal class ChromeDriverLauncher: @@ -34,10 +39,6 @@ class ChromeDriverLauncher: self._port = port if self._exe_path is None: self._exe_path = ChromeDriverLauncher.LocateExe() - if self._exe_path is None: - raise RuntimeError('ChromeDriver exe could not be found in its default ' - 'location. Searched in following directories: ' + - ', '.join(self.DefaultExeLocations())) if self._root_path is None: self._root_path = '.' if self._port is None: @@ -47,20 +48,26 @@ class ChromeDriverLauncher: if not os.path.exists(self._exe_path): raise RuntimeError('ChromeDriver exe not found at: ' + self._exe_path) - - os.environ['PATH'] = os.path.dirname(self._exe_path) + os.environ['PATH'] self.Start() @staticmethod - def DefaultExeLocations(): - """Returns the paths that are used to find the ChromeDriver executable. + def LocateExe(): + """Attempts to locate the ChromeDriver executable. + + This searches the current directory, then checks the appropriate build + locations according to platform. Returns: - a list of directories that would be searched for the executable + absolute path to the ChromeDriver executable, or None if not found """ + exe_name = 'chromedriver' + if platform.system() == 'Windows': + exe_name += '.exe' + if os.path.exists(exe_name): + return os.path.abspath(exe_name) + script_dir = os.path.dirname(__file__) - chrome_src = os.path.abspath(os.path.join( - script_dir, os.pardir, os.pardir, os.pardir)) + chrome_src = os.path.join(script_dir, os.pardir, os.pardir, os.pardir) bin_dirs = { 'linux2': [ os.path.join(chrome_src, 'out', 'Debug'), os.path.join(chrome_src, 'sconsbuild', 'Debug'), @@ -73,23 +80,7 @@ class ChromeDriverLauncher: os.path.join(chrome_src, 'chrome', 'Release'), os.path.join(chrome_src, 'build', 'Release')], } - return [os.getcwd()] + bin_dirs.get(sys.platform, []) - - @staticmethod - def LocateExe(): - """Attempts to locate the ChromeDriver executable. - - This searches the current directory, then checks the appropriate build - locations according to platform. - - Returns: - absolute path to the ChromeDriver executable, or None if not found - """ - exe_name = 'chromedriver' - if platform.system() == 'Windows': - exe_name += '.exe' - - for dir in ChromeDriverLauncher.DefaultExeLocations(): + for dir in bin_dirs.get(sys.platform, []): path = os.path.join(dir, exe_name) if os.path.exists(path): return os.path.abspath(path) @@ -99,64 +90,33 @@ class ChromeDriverLauncher: """Starts a new ChromeDriver process. Kills a previous one if it is still running. - - Raises: - RuntimeError if ChromeDriver does not start """ - def _WaitForLaunchResult(stdout, started_event, launch_result): - """Reads from the stdout of ChromeDriver and parses the launch result. - - Args: - stdout: handle to ChromeDriver's standard output - started_event: condition variable to notify when the launch result - has been parsed - launch_result: dictionary to add the result of this launch to - """ - status_line = stdout.readline() - started_event.acquire() - launch_result['success'] = status_line.startswith('Started') - launch_result['status_line'] = status_line - started_event.notify() - started_event.release() - if self._process is not None: self.Kill() - proc = subprocess.Popen([self._exe_path, '--port=%d' % self._port, - '--root=%s' % self._root_path], - stdout=subprocess.PIPE) + '--root="%s"' % self._root_path]) if proc is None: raise RuntimeError('ChromeDriver cannot be started') + logging.info('Started chromedriver at port %s' % self._port) self._process = proc - # Wait for ChromeDriver to be initialized before returning. - launch_result = {} - started_event = threading.Condition() - started_event.acquire() - spawn_thread = threading.Thread( - target=_WaitForLaunchResult, - args=(proc.stdout, started_event, launch_result)) - spawn_thread.start() - started_event.wait(20) - timed_out = 'success' not in launch_result - started_event.release() - if timed_out: - raise RuntimeError('ChromeDriver did not respond') - elif not launch_result['success']: - raise RuntimeError('ChromeDriver failed to launch: ' + - launch_result['status_line']) - logging.info('ChromeDriver running on port %s' % self._port) - def Kill(self): """Kills a currently running ChromeDriver process, if it is running.""" if self._process is None: return - pid = self._process.pid - if platform.system() == 'Windows': - subprocess.call(['taskkill.exe', '/T', '/F', '/PID', str(pid)]) + if sys.version_info < (2,6): + # From http://stackoverflow.com/questions/1064335 + if platform.system() == 'Windows': + PROCESS_TERMINATE = 1 + handle = win32api.OpenProcess(PROCESS_TERMINATE, False, + self._process.pid) + win32api.TerminateProcess(handle, -1) + win32api.CloseHandle(handle) + else: + os.kill(self._process.pid, signal.SIGKILL) else: - os.kill(pid, signal.SIGTERM) + self._process.kill() self._process = None def __del__(self): diff --git a/chrome/test/webdriver/commands/create_session.cc b/chrome/test/webdriver/commands/create_session.cc index 3272a43..dfb1838 100644 --- a/chrome/test/webdriver/commands/create_session.cc +++ b/chrome/test/webdriver/commands/create_session.cc @@ -10,16 +10,27 @@ #include "base/values.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/common/chrome_constants.h" +#include "chrome/test/webdriver/session.h" +#include "chrome/test/webdriver/session_manager.h" namespace webdriver { void CreateSession::ExecutePost(Response* const response) { SessionManager* session_manager = SessionManager::GetInstance(); - std::string session_id; + Session* session = session_manager->Create(); + if (!session) { + SET_WEBDRIVER_ERROR(response, + "Failed to create session", + kInternalServerError); + return; + } - if (!session_manager->Create(&session_id)) { - response->set_status(kUnknownError); - response->set_value(Value::CreateStringValue("Failed to create session")); + std::string session_id = session->id(); + if (!session->Init()) { + session_manager->Delete(session_id); + SET_WEBDRIVER_ERROR(response, + "Failed to initialize session", + kInternalServerError); return; } diff --git a/chrome/test/webdriver/commands/navigate_commands.cc b/chrome/test/webdriver/commands/navigate_commands.cc index 1e02bce..ab6b16c 100644 --- a/chrome/test/webdriver/commands/navigate_commands.cc +++ b/chrome/test/webdriver/commands/navigate_commands.cc @@ -7,7 +7,7 @@ namespace webdriver { void ForwardCommand::ExecutePost(Response* const response) { - if (!tab_->GoForward()) { + if (!session_->GoForward()) { SET_WEBDRIVER_ERROR(response, "GoForward failed", kInternalServerError); return; } @@ -17,7 +17,7 @@ void ForwardCommand::ExecutePost(Response* const response) { } void BackCommand::ExecutePost(Response* const response) { - if (!tab_->GoBack()) { + if (!session_->GoBack()) { SET_WEBDRIVER_ERROR(response, "GoBack failed", kInternalServerError); return; } @@ -27,7 +27,7 @@ void BackCommand::ExecutePost(Response* const response) { } void RefreshCommand::ExecutePost(Response* const response) { - if (!tab_->Reload()) { + if (!session_->Reload()) { SET_WEBDRIVER_ERROR(response, "Reload failed", kInternalServerError); return; } @@ -37,4 +37,3 @@ void RefreshCommand::ExecutePost(Response* const response) { } } // namespace webdriver - diff --git a/chrome/test/webdriver/commands/response.h b/chrome/test/webdriver/commands/response.h index 645ded2..3b39302 100644 --- a/chrome/test/webdriver/commands/response.h +++ b/chrome/test/webdriver/commands/response.h @@ -9,6 +9,7 @@ #include <string> #include "base/basictypes.h" +#include "base/json/json_writer.h" #include "base/logging.h" #include "base/values.h" #include "chrome/test/webdriver/error_codes.h" diff --git a/chrome/test/webdriver/commands/source_command.cc b/chrome/test/webdriver/commands/source_command.cc index 54cb8b1..0192d4f 100644 --- a/chrome/test/webdriver/commands/source_command.cc +++ b/chrome/test/webdriver/commands/source_command.cc @@ -18,11 +18,10 @@ const wchar_t* const kSource[] = { void SourceCommand::ExecuteGet(Response* const response) { const std::wstring jscript = build_atom(kSource, sizeof kSource); - // Get the source code for the current frame only. - std::wstring xpath = session_->current_frame_xpath(); - std::wstring result = L""; + Value* result = NULL; - if (!tab_->ExecuteAndExtractString(xpath, jscript, &result)) { + scoped_ptr<ListValue> list(new ListValue()); + if (!session_->ExecuteScript(jscript, list.get(), &result)) { LOG(ERROR) << "Could not execute JavaScript to find source. JavaScript" << " used was:\n" << kSource; LOG(ERROR) << "ExecuteAndExtractString's results was: " @@ -31,10 +30,8 @@ void SourceCommand::ExecuteGet(Response* const response) { kInternalServerError); return; } - - response->set_value(new StringValue(WideToUTF16(result))); + response->set_value(result); response->set_status(kSuccess); } } // namespace webdriver - diff --git a/chrome/test/webdriver/commands/title_command.cc b/chrome/test/webdriver/commands/title_command.cc index 544aaa6..1213895 100644 --- a/chrome/test/webdriver/commands/title_command.cc +++ b/chrome/test/webdriver/commands/title_command.cc @@ -4,21 +4,19 @@ #include <string> -#include "base/utf_string_conversions.h" #include "chrome/test/webdriver/commands/title_command.h" namespace webdriver { void TitleCommand::ExecuteGet(Response* const response) { - std::wstring title; - if (!tab_->GetTabTitle(&title)) { + std::string title; + if (!session_->GetTabTitle(&title)) { SET_WEBDRIVER_ERROR(response, "GetTabTitle failed", kInternalServerError); return; } - response->set_value(new StringValue(WideToUTF16(title))); + response->set_value(new StringValue(title)); response->set_status(kSuccess); } } // namespace webdriver - diff --git a/chrome/test/webdriver/commands/url_command.cc b/chrome/test/webdriver/commands/url_command.cc index 4b716a8..87c5127 100644 --- a/chrome/test/webdriver/commands/url_command.cc +++ b/chrome/test/webdriver/commands/url_command.cc @@ -5,18 +5,17 @@ #include <string> #include "chrome/test/webdriver/commands/url_command.h" -#include "googleurl/src/gurl.h" namespace webdriver { void URLCommand::ExecuteGet(Response* const response) { - GURL url; - if (!tab_->GetCurrentURL(&url)) { + std::string url; + if (!session_->GetURL(&url)) { SET_WEBDRIVER_ERROR(response, "GetCurrentURL failed", kInternalServerError); return; } - response->set_value(new StringValue(url.spec())); + response->set_value(new StringValue(url)); response->set_status(kSuccess); } @@ -28,8 +27,7 @@ void URLCommand::ExecutePost(Response* const response) { return; } // TODO(jmikhail): sniff for meta-redirects. - GURL rgurl(url); - if (!tab_->NavigateToURL(rgurl)) { + if (!session_->NavigateToURL(url)) { SET_WEBDRIVER_ERROR(response, "NavigateToURL failed", kInternalServerError); return; @@ -41,4 +39,3 @@ void URLCommand::ExecutePost(Response* const response) { } } // namespace webdriver - diff --git a/chrome/test/webdriver/commands/webdriver_command.cc b/chrome/test/webdriver/commands/webdriver_command.cc index 276fa65..f1931c0 100644 --- a/chrome/test/webdriver/commands/webdriver_command.cc +++ b/chrome/test/webdriver/commands/webdriver_command.cc @@ -59,17 +59,6 @@ bool WebDriverCommand::Init(Response* const response) { } response->SetField("sessionId", Value::CreateStringValue(session_id)); - return !RequiresValidTab() || VerifyTabIsValid(response); -} - -bool WebDriverCommand::VerifyTabIsValid(Response* response) { - tab_ = session_->ActiveTab(); - if (!tab_.get()) { - response->set_value(Value::CreateStringValue( - "Lost session window handle; did you close the window?")); - response->set_status(kNoSuchWindow); - return false; - } return true; } diff --git a/chrome/test/webdriver/commands/webdriver_command.h b/chrome/test/webdriver/commands/webdriver_command.h index 52500cf..4446b05 100644 --- a/chrome/test/webdriver/commands/webdriver_command.h +++ b/chrome/test/webdriver/commands/webdriver_command.h @@ -43,17 +43,7 @@ class WebDriverCommand : public Command { // failure. DictionaryValue* GetElementIdAsDictionaryValue(const std::string& element_id); - // Returns whether this command requires a valid TabProxy upon - // initialization. - virtual bool RequiresValidTab() { return true; } - - // Returns whether this command has a valid TabProxy. Returns true on - // success. Otherwise, returns false and populates the |resposne| with the - // necessary information to return to the client. - bool VerifyTabIsValid(Response* response); - Session* session_; - scoped_refptr<TabProxy> tab_; DISALLOW_COPY_AND_ASSIGN(WebDriverCommand); }; @@ -61,4 +51,3 @@ class WebDriverCommand : public Command { } // namespace webdriver #endif // CHROME_TEST_WEBDRIVER_COMMANDS_WEBDRIVER_COMMAND_H_ - diff --git a/chrome/test/webdriver/commands/webelement_command.cc b/chrome/test/webdriver/commands/webelement_command.cc index 9486c7b..f40fd6a 100644 --- a/chrome/test/webdriver/commands/webelement_command.cc +++ b/chrome/test/webdriver/commands/webelement_command.cc @@ -10,7 +10,7 @@ namespace webdriver { -bool WebElementCommand::Init(Response* response) { +bool WebElementCommand::Init(Response* const response) { if (WebDriverCommand::Init(response)) { SET_WEBDRIVER_ERROR(response, "Failure on Init for web element command", kInternalServerError); @@ -83,4 +83,3 @@ bool WebElementCommand::GetElementSize(int* width, int* height) { } } // namespace webdriver - diff --git a/chrome/test/webdriver/commands/webelement_command.h b/chrome/test/webdriver/commands/webelement_command.h index a093b05..79e3a05 100644 --- a/chrome/test/webdriver/commands/webelement_command.h +++ b/chrome/test/webdriver/commands/webelement_command.h @@ -22,7 +22,7 @@ class WebElementCommand : public WebDriverCommand { path_segments_(path_segments) {} virtual ~WebElementCommand() {} - virtual bool Init(Response* response); + virtual bool Init(Response* const response); protected: bool GetElementLocation(bool in_view, int* x, int* y); @@ -40,4 +40,3 @@ class WebElementCommand : public WebDriverCommand { } // namespace webdriver #endif // CHROME_TEST_WEBDRIVER_COMMANDS_WEBELEMENT_COMMAND_H_ - diff --git a/chrome/test/webdriver/dispatch.cc b/chrome/test/webdriver/dispatch.cc index a24b43e..aa83a2b 100644 --- a/chrome/test/webdriver/dispatch.cc +++ b/chrome/test/webdriver/dispatch.cc @@ -1,16 +1,27 @@ // Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + #include "chrome/test/webdriver/dispatch.h" #include <sstream> #include <string> - -#include "base/json/json_writer.h" +#include <vector> + +#include "base/logging.h" +#include "base/message_loop_proxy.h" +#include "base/string_split.h" +#include "base/string_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" #include "chrome/test/webdriver/commands/command.h" +#include "chrome/test/webdriver/session_manager.h" +#include "chrome/test/webdriver/utility_functions.h" namespace webdriver { +namespace { + // The standard HTTP Status codes are implemented below. Chrome uses // OK, See Other, Not Found, Method Not Allowed, and Internal Error. // Internal Error, HTTP 500, is used as a catch all for any issue @@ -126,35 +137,27 @@ void SendHttpInternalError(struct mg_connection* const connection, mg_printf(connection, "%s", out.str().c_str()); } -// For every HTTP request from the mongoode server DispatchCommand will -// inspect the HTTP method call requested and execute the proper function -// mapped from the class Command. -void DispatchCommand(Command* const command, const std::string& method, +void DispatchCommand(Command* const command, + const std::string& method, Response* response) { - if (command->DoesPost() && method == "POST") { - if (command->Init(response)) - command->ExecutePost(response); - } else if (command->DoesGet() && method == "GET") { - if (command->Init(response)) - command->ExecuteGet(response); - } else if (command->DoesDelete() && method == "DELETE") { - if (command->Init(response)) - command->ExecuteDelete(response); + if (!command->Init(response)) + return; + + if (method == "POST") { + command->ExecutePost(response); + } else if (method == "GET") { + command->ExecuteGet(response); + } else if (method == "DELETE") { + command->ExecuteDelete(response); } else { - ListValue* methods = new ListValue; - if (command->DoesPost()) - methods->Append(Value::CreateStringValue("POST")); - if (command->DoesGet()) { - methods->Append(Value::CreateStringValue("GET")); - methods->Append(Value::CreateStringValue("HEAD")); - } - if (command->DoesDelete()) - methods->Append(Value::CreateStringValue("DELETE")); - response->set_status(kMethodNotAllowed); - response->set_value(methods); // Assumes ownership. + NOTREACHED(); } } +} // namespace + +namespace internal { + void SendResponse(struct mg_connection* const connection, const struct mg_request_info* const request_info, const Response& response) { @@ -188,5 +191,62 @@ void SendResponse(struct mg_connection* const connection, } } -} // namespace webdriver +bool ParseRequestInfo(const struct mg_request_info* const request_info, + std::string* method, + std::vector<std::string>* path_segments, + DictionaryValue** parameters, + Response* const response) { + *method = request_info->request_method; + if (*method == "HEAD") + *method = "GET"; + else if (*method == "PUT") + *method = "POST"; + + std::string uri(request_info->uri); + base::SplitString(uri, '/', path_segments); + + if (*method == "POST" && request_info->post_data_len > 0) { + VLOG(1) << "...parsing request body"; + std::string json(request_info->post_data, request_info->post_data_len); + std::string error; + if (!ParseJSONDictionary(json, parameters, &error)) { + response->set_value(Value::CreateStringValue( + "Failed to parse command data: " + error + "\n Data: " + json)); + response->set_status(kBadRequest); + return false; + } + } + VLOG(1) << "Parsed " << method << " " << uri + << std::string(request_info->post_data, request_info->post_data_len); + return true; +} + +void DispatchHelper(Command* command_ptr, + const std::string& method, + Response* response) { + CHECK(method == "GET" || method == "POST" || method == "DELETE"); + scoped_ptr<Command> command(command_ptr); + + if ((method == "GET" && !command->DoesGet()) || + (method == "POST" && !command->DoesPost()) || + (method == "DELETE" && !command->DoesDelete())) { + ListValue* methods = new ListValue; + if (command->DoesPost()) + methods->Append(Value::CreateStringValue("POST")); + if (command->DoesGet()) { + methods->Append(Value::CreateStringValue("GET")); + methods->Append(Value::CreateStringValue("HEAD")); + } + if (command->DoesDelete()) + methods->Append(Value::CreateStringValue("DELETE")); + response->set_status(kMethodNotAllowed); + response->set_value(methods); + return; + } + DispatchCommand(command.get(), method, response); +} + +} // namespace internal + +} // namespace webdriver diff --git a/chrome/test/webdriver/dispatch.h b/chrome/test/webdriver/dispatch.h index 5add729..817dd7c 100644 --- a/chrome/test/webdriver/dispatch.h +++ b/chrome/test/webdriver/dispatch.h @@ -5,23 +5,20 @@ #ifndef CHROME_TEST_WEBDRIVER_DISPATCH_H_ #define CHROME_TEST_WEBDRIVER_DISPATCH_H_ -#include <sstream> #include <string> #include <vector> -#include "base/logging.h" -#include "base/scoped_ptr.h" -#include "base/string_split.h" -#include "base/string_util.h" -#include "chrome/test/webdriver/utility_functions.h" -#include "chrome/test/webdriver/commands/command.h" - +#include "chrome/test/webdriver/commands/response.h" #include "third_party/mongoose/mongoose.h" +class DictionaryValue; + namespace webdriver { class Command; +namespace internal { + // Sends a |response| to a WebDriver command back to the client. // |connection| is the communication pipe to the HTTP server and // |request_info| contains any data sent by the user. @@ -29,13 +26,21 @@ void SendResponse(struct mg_connection* const connection, const struct mg_request_info* const request_info, const Response& response); +// Parses the request info and returns whether parsing was successful. If not, +// |response| has been modified with the error. +bool ParseRequestInfo(const struct mg_request_info* const request_info, + std::string* method, + std::vector<std::string>* path_segments, + DictionaryValue** parameters, + Response* const response); -// Serves as a link to the mongoose server to find if the user request -// is an HTTP POST, GET, or DELETE and then executes the proper function -// calls for the class that inherts from Command. An HTTP CREATE is not -// handled and is reserved only for the establishment of a session. -void DispatchCommand(Command* const command, const std::string& method, - Response* response); +// Allows the bulk of the implementation of |Dispatch| to be moved out of this +// header file. Takes ownership of |command|. +void DispatchHelper(Command* const command, + const std::string& method, + Response* const response); + +} // namespace internal // Template function for dispatching commands sent to the WebDriver REST // service. |CommandType| must be a subtype of |webdriver::Command|. @@ -43,37 +48,23 @@ template<typename CommandType> void Dispatch(struct mg_connection* connection, const struct mg_request_info* request_info, void* user_data) { - Response response; - - std::string method(request_info->request_method); - + std::string method; std::vector<std::string> path_segments; - std::string uri(request_info->uri); - base::SplitString(uri, '/', &path_segments); - DictionaryValue* parameters = NULL; - if ((method == "POST" || method == "PUT") && - request_info->post_data_len > 0) { - VLOG(1) << "...parsing request body"; - std::string json(request_info->post_data, request_info->post_data_len); - std::string error; - if (!ParseJSONDictionary(json, ¶meters, &error)) { - response.set_value(Value::CreateStringValue( - "Failed to parse command data: " + error + "\n Data: " + json)); - response.set_status(kBadRequest); - SendResponse(connection, request_info, response); - return; - } + Response response; + if (internal::ParseRequestInfo(request_info, + &method, + &path_segments, + ¶meters, + &response)) { + internal::DispatchHelper( + new CommandType(path_segments, parameters), + method, + &response); } - - VLOG(1) << "Dispatching " << method << " " << uri - << std::string(request_info->post_data, request_info->post_data_len); - scoped_ptr<CommandType> ptr(new CommandType(path_segments, parameters)); - DispatchCommand(ptr.get(), method, &response); - SendResponse(connection, request_info, response); + internal::SendResponse(connection, request_info, response); } } // namespace webdriver #endif // CHROME_TEST_WEBDRIVER_DISPATCH_H_ - diff --git a/chrome/test/webdriver/server.cc b/chrome/test/webdriver/server.cc index edf0098..6409524 100644 --- a/chrome/test/webdriver/server.cc +++ b/chrome/test/webdriver/server.cc @@ -55,7 +55,6 @@ signal_handler(int sig_num) { } namespace webdriver { - template <typename CommandType> void SetCallback(struct mg_context* ctx, const char* pattern) { mg_set_uri_callback(ctx, pattern, &Dispatch<CommandType>, NULL); @@ -116,6 +115,7 @@ int main(int argc, char *argv[]) { port = cmd_line.GetSwitchValueASCII(std::string("port")); } + VLOG(1) << "Using port: " << port; webdriver::SessionManager* session = webdriver::SessionManager::GetInstance(); session->SetIPAddress(port); @@ -127,7 +127,7 @@ int main(int argc, char *argv[]) { webdriver::InitCallbacks(ctx); - std::cout << "Started: port=" << port << std::endl; + std::cout << "Starting server on port: " << port << std::endl; // The default behavior is to run this service forever. while (true) base::PlatformThread::Sleep(3600); @@ -138,3 +138,4 @@ int main(int argc, char *argv[]) { mg_stop(ctx); return (EXIT_SUCCESS); } + diff --git a/chrome/test/webdriver/session.cc b/chrome/test/webdriver/session.cc index fadc41e..96fa461 100644 --- a/chrome/test/webdriver/session.cc +++ b/chrome/test/webdriver/session.cc @@ -4,178 +4,55 @@ #include "chrome/test/webdriver/session_manager.h" -#ifdef OS_POSIX -#include <dirent.h> -#include <unistd.h> -#include <sys/stat.h> -#endif -#ifdef OS_WIN -#include <windows.h> -#include <shellapi.h> -#endif - -#include <stdlib.h> -#ifdef OS_POSIX -#include <algorithm> -#endif #include <vector> #include "base/command_line.h" #include "base/file_util.h" #include "base/logging.h" +#include "base/message_loop_proxy.h" #include "base/process.h" #include "base/process_util.h" #include "base/string_util.h" -#include "base/test/test_timeouts.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" +#include "base/test/test_timeouts.h" #include "base/utf_string_conversions.h" - #include "chrome/app/chrome_command_ids.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/test_launcher_utils.h" #include "chrome/test/webdriver/utility_functions.h" - #include "third_party/webdriver/atoms.h" namespace webdriver { -#ifdef OS_WIN -namespace { -std::string GetTempPath() { - DWORD result = ::GetTempPath(0, L""); - if (result == 0) - LOG(ERROR) << "Could not get system temp path"; - - std::vector<TCHAR> tempPath(result + 1); - result = ::GetTempPath(static_cast<DWORD>(tempPath.size()), &tempPath[0]); - if ((result == 0) || (result >= tempPath.size())) { - LOG(ERROR) << "Could not get system temp path"; - NOTREACHED(); - } - return std::string(tempPath.begin(), - tempPath.begin() + static_cast<std::size_t>(result)); -} -} // namespace -#endif - Session::Session(const std::string& id) - : UITestBase(), id_(id), window_num_(0), implicit_wait_(0), + : thread_(id.c_str()), + id_(id), + window_num_(0), + implicit_wait_(0), current_frame_xpath_(L"") { } bool Session::Init() { - // Create a temp directory for the new profile. - if (!CreateTemporaryProfileDirectory()) { - LOG(ERROR) << "Could not make a temp profile directory, " - << tmp_profile_dir() - << "\nNeed to quit, the issue must be fixed"; - exit(-1); - } - - SetupCommandLine(); - LaunchBrowserAndServer(); - return LoadProxies(); -} - -scoped_refptr<TabProxy> Session::ActiveTab() { - int tab_index; - if (!tab_->GetTabIndex(&tab_index) || - !browser_->ActivateTab(tab_index)) { - LOG(ERROR) << "Failed to session tab"; - return NULL; - } - return tab_; -} - -bool Session::LoadProxies() { - AutomationProxy* proxy = automation(); - scoped_refptr<BrowserProxy> browser = proxy->GetBrowserWindow(0); - if (!browser.get()) { - LOG(WARNING) << "Failed to get browser window."; + if (!thread_.Start()) { + LOG(ERROR) << "Cannot start session thread"; return false; } - - scoped_refptr<TabProxy> tab = browser->GetActiveTab(); - if (!tab.get()) { - LOG(ERROR) << "Could not load tab"; - return false; - } - - SetBrowserAndTab(0, browser, tab); - return true; -} - -void Session::SetupCommandLine() { - test_launcher_utils::PrepareBrowserCommandLineForTests(&launch_arguments_); - launch_arguments_.AppendSwitch(switches::kDomAutomationController); - launch_arguments_.AppendSwitch(switches::kFullMemoryCrashReport); - - launch_arguments_.AppendSwitchASCII(switches::kUserDataDir, - tmp_profile_dir()); -} - -void Session::SetBrowserAndTab(const int window_num, - const scoped_refptr<BrowserProxy>& browser, - const scoped_refptr<TabProxy>& tab) { - window_num_ = window_num; - browser_ = browser; - tab_ = tab; - current_frame_xpath_ = L""; - - int tab_num; - LOG_IF(WARNING, !tab->GetTabIndex(&tab_num) || !browser->ActivateTab(tab_num)) - << "Failed to activate tab"; -} - -bool Session::CreateTemporaryProfileDirectory() { - memset(tmp_profile_dir_, 0, sizeof tmp_profile_dir_); -#ifdef OS_POSIX - strncat(tmp_profile_dir_, "/tmp/webdriverXXXXXX", sizeof tmp_profile_dir_); - if (mkdtemp(tmp_profile_dir_) == NULL) { - LOG(ERROR) << "mkdtemp failed"; - return false; - } -#elif OS_WIN - DWORD ret; - ProfileDir temp_dir; - - ret = GetTempPathA(sizeof temp_dir, temp_dir); - if (ret == 0 || ret > sizeof temp_dir) { - LOG(ERROR) << "Could not find the temp directory"; - return false; - } - - ret = GetTempFileNameA(temp_dir, // Directory for tmp files. - "webdriver", // Temp file name prefix. - static_cast<int>(time(NULL)) % 65535 + 1, - tmp_profile_dir_); // Buffer for name. - - if (ret ==0) { - LOG(ERROR) << "Could not generate temp directory name"; - return false; - } - - if (!CreateDirectoryA(tmp_profile_dir_, NULL)) { - DWORD dw = GetLastError(); - LOG(ERROR) << "Error code: " << dw; - return false; - } -#endif - VLOG(1) << "Using temporary profile directory: " << tmp_profile_dir_; - return true; + automation_.reset(new Automation()); + + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::Init, + &success)); + return success; } void Session::Terminate() { - QuitBrowser(); -#ifdef OS_POSIX - FilePath del_dir = FilePath(tmp_profile_dir()); -#elif OS_WIN - FilePath del_dir = FilePath(ASCIIToWide(tmp_profile_dir())); -#endif - if (file_util::PathExists(del_dir) && !file_util::Delete(del_dir, true)) - LOG(ERROR) << "Could not clean up temp directory: " << tmp_profile_dir(); + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::Terminate)); } ErrorCode Session::ExecuteScript(const std::wstring& script, @@ -199,12 +76,21 @@ ErrorCode Session::ExecuteScript(const std::wstring& script, VLOG(1) << "Executing script in frame: " << current_frame_xpath_; std::wstring result; - scoped_refptr<TabProxy> tab = ActiveTab(); - if (!tab->ExecuteAndExtractString(current_frame_xpath_, jscript, &result)) { + bool success; + std::string result_utf8; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::ExecuteScript, + WideToUTF8(current_frame_xpath_), + WideToUTF8(jscript), + &result_utf8, + &success)); + if (!success) { *value = Value::CreateStringValue( "Unknown internal script execution failure"); return kUnknownError; } + result = UTF8ToWide(result_utf8); VLOG(1) << "...script result: " << result; std::string temp = WideToASCII(result); @@ -243,4 +129,78 @@ ErrorCode Session::ExecuteScript(const std::wstring& script, return static_cast<ErrorCode>(status); } +bool Session::NavigateToURL(const std::string& url) { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::NavigateToURL, + url, + &success)); + return success; +} + +bool Session::GoForward() { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::GoForward, + &success)); + return success; +} + +bool Session::GoBack() { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::GoBack, + &success)); + return success; +} + +bool Session::Reload() { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::Reload, + &success)); + return success; +} + +bool Session::GetURL(std::string* url) { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::GetURL, + url, + &success)); + return success; +} + +bool Session::GetTabTitle(std::string* tab_title) { + bool success = false; + RunSessionTask(NewRunnableMethod( + automation_.get(), + &Automation::GetTabTitle, + tab_title, + &success)); + return success; +} + +void Session::RunSessionTask(Task* task) { + base::WaitableEvent done_event(false, false); + thread_.message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod( + this, + &Session::RunSessionTaskOnSessionThread, + task, + &done_event)); + done_event.Wait(); +} + +void Session::RunSessionTaskOnSessionThread(Task* task, + base::WaitableEvent* done_event) { + task->Run(); + delete task; + done_event->Signal(); +} + } // namespace webdriver diff --git a/chrome/test/webdriver/session.h b/chrome/test/webdriver/session.h index 5fea84c..d93db24 100644 --- a/chrome/test/webdriver/session.h +++ b/chrome/test/webdriver/session.h @@ -8,12 +8,7 @@ #include <string> #include "base/scoped_ptr.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/automation/window_proxy.h" -#include "chrome/test/ui/ui_test.h" +#include "chrome/test/webdriver/automation.h" #include "chrome/test/webdriver/error_codes.h" namespace webdriver { @@ -23,29 +18,17 @@ namespace webdriver { // state necessary to control the chrome browser created. // TODO(phajdan.jr): Abstract UITestBase classes, see: // http://code.google.com/p/chromium/issues/detail?id=56865 -class Session : private UITestBase { +class Session { public: -#if defined(OS_POSIX) - typedef char ProfileDir[L_tmpnam + 1]; // +1 for \0 -#elif defined(OS_WIN) - typedef char ProfileDir[MAX_PATH + 1]; // +1 for \0 -#endif - explicit Session(const std::string& id); + // Creates a browser. bool Init(); // Terminates this session and disconnects its automation proxy. After // invoking this method, the Session can safely be deleted. void Terminate(); - // Finds the active tab that webdriver commands should go to. - scoped_refptr<TabProxy> ActiveTab(); - - void SetBrowserAndTab(const int window_num, - const scoped_refptr<BrowserProxy>& browser, - const scoped_refptr<TabProxy>& tab); - // 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 @@ -55,6 +38,19 @@ class Session : private UITestBase { const ListValue* const args, Value** value); + + bool NavigateToURL(const std::string& url); + bool GoForward(); + bool GoBack(); + bool Reload(); + bool GetURL(std::string* url); + bool GetTabTitle(std::string* tab_title); + void RunSessionTask(Task* task); + void RunSessionTaskOnSessionThread( + Task* task, + base::WaitableEvent* done_event); + + inline const std::string& id() const { return id_; } inline int implicit_wait() { return implicit_wait_; } @@ -68,10 +64,6 @@ class Session : private UITestBase { speed_ = speed; } - inline const char* tmp_profile_dir() { - return tmp_profile_dir_; - }; - inline const std::wstring& current_frame_xpath() const { return current_frame_xpath_; } @@ -81,19 +73,15 @@ class Session : private UITestBase { } private: - bool CreateTemporaryProfileDirectory(); - bool LoadProxies(); - void SetupCommandLine(); + scoped_ptr<Automation> automation_; + base::Thread thread_; const std::string id_; int window_num_; - scoped_refptr<BrowserProxy> browser_; - scoped_refptr<TabProxy> tab_; int implicit_wait_; Speed speed_; - ProfileDir tmp_profile_dir_; // The XPath to the frame within this session's active tab which all // commands should be directed to. XPath strings can represent a frame deep @@ -108,5 +96,6 @@ class Session : private UITestBase { } // namespace webdriver -#endif // CHROME_TEST_WEBDRIVER_SESSION_H_ +DISABLE_RUNNABLE_METHOD_REFCOUNT(webdriver::Session); +#endif // CHROME_TEST_WEBDRIVER_SESSION_H_ diff --git a/chrome/test/webdriver/session_manager.cc b/chrome/test/webdriver/session_manager.cc index 7b1981b..2d3a8bc 100644 --- a/chrome/test/webdriver/session_manager.cc +++ b/chrome/test/webdriver/session_manager.cc @@ -10,7 +10,7 @@ #include "base/process_util.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" -#include "base/test/test_timeouts.h" +#include "base/threading/thread.h" #include "chrome/common/chrome_constants.h" #if defined(OS_POSIX) @@ -120,59 +120,55 @@ std::string SessionManager::GenerateSessionID() { #else id += text[rand() % (sizeof text - 1)]; #endif - id += count_; // Append the global count to generate a unique id. } session_generation_.Release(); return id; } -bool SessionManager::Create(std::string* id) { - MessageLoop loop; - TestTimeouts::Initialize(); - - *id = GenerateSessionID(); - if (map_.find(*id) != map_.end()) { - LOG(ERROR) << "Failed to generate a unique session ID"; - return false; - } - - // Start chrome, if it doesn't startup quit. - const int ap_timeout = TestTimeouts::command_execution_timeout_ms(); - VLOG(1) << "Waiting for a max of " << ap_timeout << " ms to start the chrome " - "browser"; - - scoped_ptr<Session> session(new Session(*id)); - - if (!session->Init()) { - LOG(ERROR) << "Could not establish a valid connection to the browser"; - return false; +Session* SessionManager::Create() { + std::string id = GenerateSessionID(); + { + base::AutoLock lock(map_lock_); + if (map_.find(id) != map_.end()) { + LOG(ERROR) << "Failed to generate a unique session ID"; + return false; + } } - map_[*id] = session.release(); - return true; + Session* session = new Session(id); + base::AutoLock lock(map_lock_); + map_[id] = session; + return session; } bool SessionManager::Has(const std::string& id) const { + base::AutoLock lock(map_lock_); return map_.find(id) != map_.end(); } bool SessionManager::Delete(const std::string& id) { std::map<std::string, Session*>::iterator it; - VLOG(1) << "Deleting session with ID " << id; - it = map_.find(id); - if (it == map_.end()) { - VLOG(1) << "No such session with ID " << id; - return false; + 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); } - it->second->Terminate(); - map_.erase(it); + VLOG(1) << "Deleting session with ID " << id; + delete session; return true; } Session* SessionManager::GetSession(const std::string& id) const { std::map<std::string, Session*>::const_iterator it; + base::AutoLock lock(map_lock_); it = map_.find(id); if (it == map_.end()) { VLOG(1) << "No such session with ID " << id; diff --git a/chrome/test/webdriver/session_manager.h b/chrome/test/webdriver/session_manager.h index 0c5c5bfd..c4f8729 100644 --- a/chrome/test/webdriver/session_manager.h +++ b/chrome/test/webdriver/session_manager.h @@ -9,7 +9,7 @@ #include <string> #include "base/singleton.h" - +#include "base/synchronization/lock.h" #include "chrome/test/webdriver/session.h" namespace webdriver { @@ -27,7 +27,7 @@ class SessionManager { std::string GetIPAddress(); bool SetIPAddress(const std::string& port); - bool Create(std::string* id); + Session* Create(); bool Delete(const std::string& id); bool Has(const std::string& id) const; @@ -40,6 +40,7 @@ class SessionManager { std::string IPLookup(const std::string& nic); std::map<std::string, Session*> map_; + mutable base::Lock map_lock_; base::Lock session_generation_; // Record the address and port for the HTTP 303 See other redirect. // We save the IP and Port of the machine chromedriver is running on since @@ -55,4 +56,3 @@ class SessionManager { } // namespace webdriver #endif // CHROME_TEST_WEBDRIVER_SESSION_MANAGER_H_ - |