diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-04 20:14:29 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-04 20:14:29 +0000 |
commit | 7f01dde7a68ea76b24aa3c0afd5ca528e2ef9b0c (patch) | |
tree | ef16ccfa623aaffd3d193bfd2e5dd7254ed5f9d0 /chrome/test | |
parent | 8a48f3f925b5f3b15b69ea6d11568b85987ffc47 (diff) | |
download | chromium_src-7f01dde7a68ea76b24aa3c0afd5ca528e2ef9b0c.zip chromium_src-7f01dde7a68ea76b24aa3c0afd5ca528e2ef9b0c.tar.gz chromium_src-7f01dde7a68ea76b24aa3c0afd5ca528e2ef9b0c.tar.bz2 |
[chromedriver] Implement connecting to devtools and loading a page.
BUG=none
Review URL: https://chromiumcodereview.appspot.com/11415205
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@171035 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test')
25 files changed, 610 insertions, 27 deletions
diff --git a/chrome/test/chromedriver/chrome.h b/chrome/test/chromedriver/chrome.h index c3df4c4..a0675d0 100644 --- a/chrome/test/chromedriver/chrome.h +++ b/chrome/test/chromedriver/chrome.h @@ -5,11 +5,15 @@ #ifndef CHROME_TEST_CHROMEDRIVER_CHROME_H_ #define CHROME_TEST_CHROMEDRIVER_CHROME_H_ +#include <string> + class Status; class Chrome { public: virtual ~Chrome() {} + + virtual Status Load(const std::string& url) = 0; virtual Status Quit() = 0; }; diff --git a/chrome/test/chromedriver/chrome_impl.cc b/chrome/test/chromedriver/chrome_impl.cc index 5cac6f3..f074d3c 100644 --- a/chrome/test/chromedriver/chrome_impl.cc +++ b/chrome/test/chromedriver/chrome_impl.cc @@ -4,13 +4,42 @@ #include "chrome/test/chromedriver/chrome_impl.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" #include "base/logging.h" #include "base/process_util.h" +#include "base/stringprintf.h" +#include "base/threading/platform_thread.h" +#include "base/time.h" +#include "base/values.h" +#include "chrome/test/chromedriver/devtools_client.h" +#include "chrome/test/chromedriver/net/net_util.h" +#include "chrome/test/chromedriver/net/url_request_context_getter.h" #include "chrome/test/chromedriver/status.h" +#include "googleurl/src/gurl.h" + +namespace { + +Status FetchPagesInfo(URLRequestContextGetter* context_getter, + int port, + std::list<std::string>* debugger_urls) { + std::string url = base::StringPrintf( + "http://127.0.0.1:%d/json", port); + std::string data; + if (!FetchUrl(GURL(url), context_getter, &data)) + return Status(kChromeNotReachable); + return internal::ParsePagesInfo(data, debugger_urls); +} + +} // namespace ChromeImpl::ChromeImpl(base::ProcessHandle process, - base::ScopedTempDir* user_data_dir) - : process_(process) { + URLRequestContextGetter* context_getter, + base::ScopedTempDir* user_data_dir, + int port) + : process_(process), + context_getter_(context_getter), + port_(port) { if (user_data_dir->IsValid()) { CHECK(user_data_dir_.Set(user_data_dir->Take())); } @@ -20,8 +49,57 @@ ChromeImpl::~ChromeImpl() { base::CloseProcessHandle(process_); } +Status ChromeImpl::Init() { + base::Time deadline = base::Time::Now() + base::TimeDelta::FromSeconds(20); + std::list<std::string> debugger_urls; + while (base::Time::Now() < deadline) { + FetchPagesInfo(context_getter_, port_, &debugger_urls); + if (debugger_urls.empty()) + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + else + break; + } + if (debugger_urls.empty()) + return Status(kUnknownError, "unable to discover open pages"); + client_.reset(new DevToolsClient(context_getter_, debugger_urls.front())); + return Status(kOk); +} + +Status ChromeImpl::Load(const std::string& url) { + base::DictionaryValue params; + params.SetString("url", url); + return client_->SendCommand("Page.navigate", params); +} + Status ChromeImpl::Quit() { if (!base::KillProcess(process_, 0, true)) return Status(kUnknownError, "cannot kill Chrome"); return Status(kOk); } + +namespace internal { + +Status ParsePagesInfo(const std::string& data, + std::list<std::string>* debugger_urls) { + scoped_ptr<base::Value> value(base::JSONReader::Read(data)); + if (!value.get()) + return Status(kUnknownError, "DevTools returned invalid JSON"); + base::ListValue* list; + if (!value->GetAsList(&list)) + return Status(kUnknownError, "DevTools did not return list"); + + std::list<std::string> internal_urls; + for (size_t i = 0; i < list->GetSize(); ++i) { + base::DictionaryValue* info; + if (!list->GetDictionary(i, &info)) + return Status(kUnknownError, "DevTools contains non-dictionary item"); + std::string debugger_url; + if (!info->GetString("webSocketDebuggerUrl", &debugger_url)) + return Status(kUnknownError, "DevTools did not include debugger URL"); + internal_urls.push_back(debugger_url); + } + debugger_urls->swap(internal_urls); + return Status(kOk); +} + +} // namespace internal diff --git a/chrome/test/chromedriver/chrome_impl.h b/chrome/test/chromedriver/chrome_impl.h index 1a6d1c8..5f6056e 100644 --- a/chrome/test/chromedriver/chrome_impl.h +++ b/chrome/test/chromedriver/chrome_impl.h @@ -5,24 +5,47 @@ #ifndef CHROME_TEST_CHROMEDRIVER_CHROME_IMPL_H_ #define CHROME_TEST_CHROMEDRIVER_CHROME_IMPL_H_ +#include <list> +#include <string> + #include "base/compiler_specific.h" #include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/process.h" #include "chrome/test/chromedriver/chrome.h" +class DevToolsClient; class Status; +class URLRequestContextGetter; class ChromeImpl : public Chrome { public: - ChromeImpl(base::ProcessHandle process, base::ScopedTempDir* user_data_dir); + ChromeImpl(base::ProcessHandle process, + URLRequestContextGetter* context_getter, + base::ScopedTempDir* user_data_dir, + int port); virtual ~ChromeImpl(); + Status Init(); + // Overridden from Chrome: + virtual Status Load(const std::string& url) OVERRIDE; virtual Status Quit() OVERRIDE; private: base::ProcessHandle process_; + scoped_refptr<URLRequestContextGetter> context_getter_; base::ScopedTempDir user_data_dir_; + int port_; + scoped_ptr<DevToolsClient> client_; }; +namespace internal { + +Status ParsePagesInfo(const std::string& data, + std::list<std::string>* debugger_urls); + +} // namespace internal + #endif // CHROME_TEST_CHROMEDRIVER_CHROME_IMPL_H_ diff --git a/chrome/test/chromedriver/chrome_impl_unittest.cc b/chrome/test/chromedriver/chrome_impl_unittest.cc new file mode 100644 index 0000000..70f0ea4 --- /dev/null +++ b/chrome/test/chromedriver/chrome_impl_unittest.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2012 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 <list> +#include <string> + +#include "chrome/test/chromedriver/chrome_impl.h" +#include "chrome/test/chromedriver/status.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(ParsePagesInfo, Normal) { + std::list<std::string> urls; + Status status = internal::ParsePagesInfo( + "[{\"webSocketDebuggerUrl\": \"http://debugurl\"}]", + &urls); + ASSERT_TRUE(status.IsOk()); + ASSERT_EQ(1u, urls.size()); + ASSERT_EQ("http://debugurl", urls.front()); +} + +TEST(ParsePagesInfo, Multiple) { + std::list<std::string> urls; + Status status = internal::ParsePagesInfo( + "[{\"webSocketDebuggerUrl\": \"http://debugurl\"}," + " {\"webSocketDebuggerUrl\": \"http://debugurl2\"}]", + &urls); + ASSERT_TRUE(status.IsOk()); + ASSERT_EQ(2u, urls.size()); + ASSERT_EQ("http://debugurl", urls.front()); + ASSERT_EQ("http://debugurl2", urls.back()); +} + +namespace { + +void AssertFails(const std::string& data) { + std::list<std::string> urls; + Status status = internal::ParsePagesInfo(data, &urls); + ASSERT_FALSE(status.IsOk()); + ASSERT_EQ(0u, urls.size()); +} + +} // namespace + +TEST(ParsePagesInfo, InvalidJSON) { + AssertFails("["); +} + +TEST(ParsePagesInfo, NonList) { + AssertFails("{}"); +} + +TEST(ParsePagesInfo, NonDictionary) { + AssertFails("[1]"); +} + +TEST(ParsePagesInfo, NoDebuggerUrl) { + AssertFails("[{\"hi\": 1}]"); +} + +TEST(ParsePagesInfo, InvalidDebuggerUrl) { + AssertFails("[{\"webSocketDebuggerUrl\": 1}]"); +} diff --git a/chrome/test/chromedriver/chrome_launcher_impl.cc b/chrome/test/chromedriver/chrome_launcher_impl.cc index 3e124fb..ee484ab 100644 --- a/chrome/test/chromedriver/chrome_launcher_impl.cc +++ b/chrome/test/chromedriver/chrome_launcher_impl.cc @@ -10,12 +10,15 @@ #include "base/file_path.h" #include "base/process.h" #include "base/process_util.h" +#include "base/string_number_conversions.h" #include "chrome/test/chromedriver/chrome.h" #include "chrome/test/chromedriver/chrome_finder.h" #include "chrome/test/chromedriver/chrome_impl.h" +#include "chrome/test/chromedriver/net/url_request_context_getter.h" #include "chrome/test/chromedriver/status.h" -ChromeLauncherImpl::ChromeLauncherImpl() {} +ChromeLauncherImpl::ChromeLauncherImpl(URLRequestContextGetter* context_getter) + : context_getter_(context_getter) {} ChromeLauncherImpl::~ChromeLauncherImpl() {} @@ -28,7 +31,10 @@ Status ChromeLauncherImpl::Launch( return Status(kUnknownError, "cannot find Chrome binary"); } + int port = 33081; CommandLine command(program); + command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port)); + command.AppendSwitch("no-first-run"); command.AppendSwitch("enable-logging"); command.AppendSwitchASCII("logging-level", "1"); base::ScopedTempDir user_data_dir; @@ -41,7 +47,11 @@ Status ChromeLauncherImpl::Launch( base::ProcessHandle process; if (!base::LaunchProcess(command, options, &process)) return Status(kUnknownError, "chrome failed to start"); - chrome->reset(new ChromeImpl(process, &user_data_dir)); - + scoped_ptr<ChromeImpl> chrome_impl(new ChromeImpl( + process, context_getter_, &user_data_dir, port)); + Status status = chrome_impl->Init(); + if (status.IsError()) + return status; + chrome->reset(chrome_impl.release()); return Status(kOk); } diff --git a/chrome/test/chromedriver/chrome_launcher_impl.h b/chrome/test/chromedriver/chrome_launcher_impl.h index ecd7bd1..542017e3b 100644 --- a/chrome/test/chromedriver/chrome_launcher_impl.h +++ b/chrome/test/chromedriver/chrome_launcher_impl.h @@ -7,16 +7,18 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "chrome/test/chromedriver/chrome_launcher.h" class Chrome; class FilePath; class Status; +class URLRequestContextGetter; class ChromeLauncherImpl : public ChromeLauncher { public: - ChromeLauncherImpl(); + explicit ChromeLauncherImpl(URLRequestContextGetter* context_getter); virtual ~ChromeLauncherImpl(); // Overridden from ChromeLauncher: @@ -24,6 +26,8 @@ class ChromeLauncherImpl : public ChromeLauncher { scoped_ptr<Chrome>* chrome) OVERRIDE; private: + scoped_refptr<URLRequestContextGetter> context_getter_; + DISALLOW_COPY_AND_ASSIGN(ChromeLauncherImpl); }; diff --git a/chrome/test/chromedriver/chromedriver.cc b/chrome/test/chromedriver/chromedriver.cc index 9f66847..70b5746 100644 --- a/chrome/test/chromedriver/chromedriver.cc +++ b/chrome/test/chromedriver/chromedriver.cc @@ -6,14 +6,19 @@ #include "base/json/json_reader.h" #include "base/json/json_writer.h" +#include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" #include "base/values.h" #include "chrome/test/chromedriver/command_executor.h" #include "chrome/test/chromedriver/status.h" namespace { +// Guards |g_executor_initialized|. +base::LazyInstance<base::Lock> g_lazy_lock = LAZY_INSTANCE_INITIALIZER; +bool g_executor_initialized = false; CommandExecutor* g_command_executor = NULL; void SetResponse(StatusCode status, @@ -39,10 +44,19 @@ void SetError(const std::string& error_msg, void Init(scoped_ptr<CommandExecutor> executor) { g_command_executor = executor.release(); + // We do not call CommandExecutor::Init here because you can't do some things + // (e.g., creating threads) during DLL loading on Windows. } void ExecuteCommand(const std::string& command, std::string* response) { CHECK(g_command_executor); + { + base::AutoLock(g_lazy_lock.Get()); + if (!g_executor_initialized) { + g_command_executor->Init(); + g_executor_initialized = true; + } + } std::string error_msg; scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError( command, 0, NULL, &error_msg)); diff --git a/chrome/test/chromedriver/chromedriver.h b/chrome/test/chromedriver/chromedriver.h index 5252da1..3872b003 100644 --- a/chrome/test/chromedriver/chromedriver.h +++ b/chrome/test/chromedriver/chromedriver.h @@ -12,6 +12,7 @@ class CommandExecutor; // Inits the command executor. Must be called before |ExecuteCommand|. +// This may be called during DLL load on Windows. void Init(scoped_ptr<CommandExecutor> executor); // Synchronously executes the given command. Thread safe. diff --git a/chrome/test/chromedriver/chromedriver.py b/chrome/test/chromedriver/chromedriver.py index cbe6d3b..c0bfadd 100644 --- a/chrome/test/chromedriver/chromedriver.py +++ b/chrome/test/chromedriver/chromedriver.py @@ -68,6 +68,9 @@ class ChromeDriver(object): def _ExecuteSessionCommand(self, name, params={}): return self._ExecuteCommand(name, params, self._session_id) + def Load(self, url): + self._ExecuteSessionCommand('get', {'url': url}) + def Quit(self): """Quits the browser and ends the session.""" self._ExecuteSessionCommand('quit') diff --git a/chrome/test/chromedriver/chromedriver_unittest.cc b/chrome/test/chromedriver/chromedriver_unittest.cc index 9c85383..fc8bef6 100644 --- a/chrome/test/chromedriver/chromedriver_unittest.cc +++ b/chrome/test/chromedriver/chromedriver_unittest.cc @@ -38,13 +38,13 @@ class DummyExecutor : public CommandExecutor { public: virtual ~DummyExecutor() {} + virtual void Init() OVERRIDE {} virtual void ExecuteCommand(const std::string& name, const base::DictionaryValue& params, const std::string& session_id, StatusCode* status, scoped_ptr<base::Value>* value, - std::string* out_session_id) OVERRIDE { - } + std::string* out_session_id) OVERRIDE {} }; @@ -80,6 +80,8 @@ class ExecutorMock : public CommandExecutor { EXPECT_TRUE(DidSatisfyExpectations()); } + virtual void Init() OVERRIDE {} + virtual void ExecuteCommand(const std::string& name, const base::DictionaryValue& params, const std::string& session_id, diff --git a/chrome/test/chromedriver/command_executor.h b/chrome/test/chromedriver/command_executor.h index db0b0f9..5f8e5fe 100644 --- a/chrome/test/chromedriver/command_executor.h +++ b/chrome/test/chromedriver/command_executor.h @@ -22,6 +22,8 @@ class CommandExecutor { public: virtual ~CommandExecutor() {} + virtual void Init() = 0; + // Executes a command synchronously. This function must be thread safe. virtual void ExecuteCommand(const std::string& name, const base::DictionaryValue& params, diff --git a/chrome/test/chromedriver/command_executor_impl.cc b/chrome/test/chromedriver/command_executor_impl.cc index 57c3a24..4f5b57a 100644 --- a/chrome/test/chromedriver/command_executor_impl.cc +++ b/chrome/test/chromedriver/command_executor_impl.cc @@ -6,14 +6,30 @@ #include "base/bind.h" #include "base/callback.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" #include "base/values.h" +#include "chrome/test/chromedriver/chrome_launcher_impl.h" #include "chrome/test/chromedriver/commands.h" +#include "chrome/test/chromedriver/net/url_request_context_getter.h" #include "chrome/test/chromedriver/session.h" #include "chrome/test/chromedriver/session_command.h" #include "chrome/test/chromedriver/session_map.h" #include "chrome/test/chromedriver/status.h" -CommandExecutorImpl::CommandExecutorImpl() { +CommandExecutorImpl::CommandExecutorImpl() + : io_thread_("ChromeDriver IO") {} + +CommandExecutorImpl::~CommandExecutorImpl() {} + +void CommandExecutorImpl::Init() { + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + CHECK(io_thread_.StartWithOptions(options)); + context_getter_ = new URLRequestContextGetter( + io_thread_.message_loop_proxy()); + launcher_.reset(new ChromeLauncherImpl(context_getter_)); + + // Session commands. base::Callback<Status( const SessionCommand&, const base::DictionaryValue&, @@ -22,19 +38,19 @@ CommandExecutorImpl::CommandExecutorImpl() { std::string*)> execute_session_command = base::Bind( &ExecuteSessionCommand, &session_map_); + command_map_.Set("get", base::Bind(execute_session_command, + base::Bind(&ExecuteGet))); Command quit_command = base::Bind(execute_session_command, base::Bind(&ExecuteQuit, &session_map_)); command_map_.Set("quit", quit_command); // Non-session commands. command_map_.Set("newSession", - base::Bind(&ExecuteNewSession, &session_map_, &launcher_)); + base::Bind(&ExecuteNewSession, &session_map_, launcher_.get())); command_map_.Set("quitAll", base::Bind(&ExecuteQuitAll, quit_command, &session_map_)); } -CommandExecutorImpl::~CommandExecutorImpl() {} - void CommandExecutorImpl::ExecuteCommand( const std::string& name, const base::DictionaryValue& params, diff --git a/chrome/test/chromedriver/command_executor_impl.h b/chrome/test/chromedriver/command_executor_impl.h index 125b1e3..092809f 100644 --- a/chrome/test/chromedriver/command_executor_impl.h +++ b/chrome/test/chromedriver/command_executor_impl.h @@ -12,7 +12,7 @@ #include "base/compiler_specific.h" #include "base/gtest_prod_util.h" #include "base/memory/scoped_ptr.h" -#include "chrome/test/chromedriver/chrome_launcher_impl.h" +#include "base/threading/thread.h" #include "chrome/test/chromedriver/command.h" #include "chrome/test/chromedriver/command_executor.h" #include "chrome/test/chromedriver/session_map.h" @@ -24,12 +24,16 @@ class DictionaryValue; class Value; } +class ChromeLauncherImpl; +class URLRequestContextGetter; + class CommandExecutorImpl : public CommandExecutor { public: CommandExecutorImpl(); virtual ~CommandExecutorImpl(); // Overridden from CommandExecutor: + virtual void Init() OVERRIDE; virtual void ExecuteCommand(const std::string& name, const base::DictionaryValue& params, const std::string& session_id, @@ -43,8 +47,10 @@ class CommandExecutorImpl : public CommandExecutor { CommandExecutorImplTest, CommandThatDoesntSetValueOrSessionId); FRIEND_TEST_ALL_PREFIXES(CommandExecutorImplTest, CommandThatReturnsError); + base::Thread io_thread_; + scoped_refptr<URLRequestContextGetter> context_getter_; SessionMap session_map_; - ChromeLauncherImpl launcher_; + scoped_ptr<ChromeLauncherImpl> launcher_; SynchronizedMap<std::string, Command> command_map_; DISALLOW_COPY_AND_ASSIGN(CommandExecutorImpl); diff --git a/chrome/test/chromedriver/commands.cc b/chrome/test/chromedriver/commands.cc index 8602aa3..ebf5513 100644 --- a/chrome/test/chromedriver/commands.cc +++ b/chrome/test/chromedriver/commands.cc @@ -78,3 +78,13 @@ Status ExecuteQuit( CHECK(session_map->Remove(session->id)); return session->chrome->Quit(); } + +Status ExecuteGet( + Session* session, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value) { + std::string url; + if (!params.GetString("url", &url)) + return Status(kUnknownError, "'url' must be a string"); + return session->chrome->Load(url); +} diff --git a/chrome/test/chromedriver/commands.h b/chrome/test/chromedriver/commands.h index ac72f1b..3e1a68b 100644 --- a/chrome/test/chromedriver/commands.h +++ b/chrome/test/chromedriver/commands.h @@ -46,4 +46,10 @@ Status ExecuteQuit( const base::DictionaryValue& params, scoped_ptr<base::Value>* value); +// Loads a URL. +Status ExecuteGet( + Session* session, + const base::DictionaryValue& params, + scoped_ptr<base::Value>* value); + #endif // CHROME_TEST_CHROMEDRIVER_COMMANDS_H_ diff --git a/chrome/test/chromedriver/commands_unittest.cc b/chrome/test/chromedriver/commands_unittest.cc index d7612d9..eef7136 100644 --- a/chrome/test/chromedriver/commands_unittest.cc +++ b/chrome/test/chromedriver/commands_unittest.cc @@ -27,6 +27,9 @@ class StubChrome : public Chrome { virtual ~StubChrome() {} // Overridden from Chrome: + virtual Status Load(const std::string& url) OVERRIDE { + return Status(kOk); + } virtual Status Quit() OVERRIDE { return Status(kOk); } @@ -152,7 +155,7 @@ TEST(CommandsTest, Quit) { namespace { -class FailsToQuitChrome : public Chrome { +class FailsToQuitChrome : public StubChrome { public: FailsToQuitChrome() {} virtual ~FailsToQuitChrome() {} diff --git a/chrome/test/chromedriver/devtools_client.cc b/chrome/test/chromedriver/devtools_client.cc new file mode 100644 index 0000000..f78a492 --- /dev/null +++ b/chrome/test/chromedriver/devtools_client.cc @@ -0,0 +1,44 @@ +// Copyright (c) 2012 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/chromedriver/devtools_client.h" + +#include "base/json/json_writer.h" +#include "base/values.h" +#include "chrome/test/chromedriver/net/sync_websocket.h" +#include "chrome/test/chromedriver/net/url_request_context_getter.h" +#include "chrome/test/chromedriver/status.h" +#include "googleurl/src/gurl.h" + +DevToolsClient::DevToolsClient( + URLRequestContextGetter* context_getter, + const std::string& url) + : context_getter_(context_getter), + url_(url), + socket_(new SyncWebSocket(context_getter)), + connected_(false) {} + +DevToolsClient::~DevToolsClient() {} + +Status DevToolsClient::SendCommand( + const std::string& method, + const base::DictionaryValue& params) { + if (!connected_) { + if (!socket_->Connect(GURL(url_))) + return Status(kUnknownError, "unable to connect to renderer"); + connected_ = true; + } + base::DictionaryValue command; + command.SetInteger("id", 1); + command.SetString("method", method); + command.Set("params", params.DeepCopy()); + std::string message; + base::JSONWriter::Write(&command, &message); + if (!socket_->Send(message)) + return Status(kUnknownError, "unable to send message to renderer"); + std::string response; + if (!socket_->ReceiveNextMessage(&response)) + return Status(kUnknownError, "unable to receive message from renderer"); + return Status(kOk); +} diff --git a/chrome/test/chromedriver/devtools_client.h b/chrome/test/chromedriver/devtools_client.h new file mode 100644 index 0000000..218632f --- /dev/null +++ b/chrome/test/chromedriver/devtools_client.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 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_CHROMEDRIVER_DEVTOOLS_CLIENT_H_ +#define CHROME_TEST_CHROMEDRIVER_DEVTOOLS_CLIENT_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class DictionaryValue; +} + +class Status; +class SyncWebSocket; +class URLRequestContextGetter; + +// A DevTools client of a single DevTools debugger. +class DevToolsClient { + public: + DevToolsClient(URLRequestContextGetter* context_getter, + const std::string& url); + ~DevToolsClient(); + + Status SendCommand(const std::string& method, + const base::DictionaryValue& params); + + private: + scoped_refptr<URLRequestContextGetter> context_getter_; + std::string url_; + scoped_ptr<SyncWebSocket> socket_; + bool connected_; + + DISALLOW_COPY_AND_ASSIGN(DevToolsClient); +}; + +#endif // CHROME_TEST_CHROMEDRIVER_DEVTOOLS_CLIENT_H_ diff --git a/chrome/test/chromedriver/net/net_util.cc b/chrome/test/chromedriver/net/net_util.cc new file mode 100644 index 0000000..c6aebc3 --- /dev/null +++ b/chrome/test/chromedriver/net/net_util.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "chrome/test/chromedriver/net/url_request_context_getter.h" +#include "googleurl/src/gurl.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace { + +class SyncUrlFetcher : public net::URLFetcherDelegate { + public: + SyncUrlFetcher() {} + virtual ~SyncUrlFetcher() {} + + bool Fetch(const GURL& url, + URLRequestContextGetter* getter, + std::string* response) { + MessageLoop loop; + scoped_ptr<net::URLFetcher> fetcher_( + net::URLFetcher::Create(url, net::URLFetcher::GET, this)); + fetcher_->SetRequestContext(getter); + response_ = response; + fetcher_->Start(); + loop.Run(); + return success_; + } + + virtual void OnURLFetchComplete(const net::URLFetcher* source) { + success_ = (source->GetResponseCode() == 200); + if (success_) + success_ = source->GetResponseAsString(response_); + MessageLoop::current()->Quit(); + } + + private: + bool success_; + std::string* response_; +}; + +} // namespace + +bool FetchUrl(const GURL& url, + URLRequestContextGetter* getter, + std::string* response) { + return SyncUrlFetcher().Fetch(url, getter, response); +} diff --git a/chrome/test/chromedriver/net/net_util.h b/chrome/test/chromedriver/net/net_util.h new file mode 100644 index 0000000..78ab424 --- /dev/null +++ b/chrome/test/chromedriver/net/net_util.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012 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_CHROMEDRIVER_NET_NET_UTIL_H_ +#define CHROME_TEST_CHROMEDRIVER_NET_NET_UTIL_H_ + +#include <string> + +class GURL; +class URLRequestContextGetter; + +// Synchronously fetches data from a GET HTTP request to the given URL. +// Returns true if response is 200 OK and sets response body to |response|. +bool FetchUrl(const GURL& url, + URLRequestContextGetter* getter, + std::string* response); + +#endif // CHROME_TEST_CHROMEDRIVER_NET_NET_UTIL_H_ diff --git a/chrome/test/chromedriver/net/net_util_unittest.cc b/chrome/test/chromedriver/net/net_util_unittest.cc new file mode 100644 index 0000000..7eee142 --- /dev/null +++ b/chrome/test/chromedriver/net/net_util_unittest.cc @@ -0,0 +1,141 @@ +// Copyright (c) 2012 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 <string> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/single_thread_task_runner.h" +#include "base/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "chrome/test/chromedriver/net/net_util.h" +#include "chrome/test/chromedriver/net/url_request_context_getter.h" +#include "googleurl/src/gurl.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/base/tcp_listen_socket.h" +#include "net/server/http_server.h" +#include "net/server/http_server_request_info.h" +#include "net/url_request/url_request_context_getter.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class FetchUrlTest : public testing::Test, + public net::HttpServer::Delegate { + public: + FetchUrlTest() + : io_thread_("io"), + response_(kSendHello) { + base::Thread::Options options(MessageLoop::TYPE_IO, 0); + CHECK(io_thread_.StartWithOptions(options)); + context_getter_ = new URLRequestContextGetter( + io_thread_.message_loop_proxy()); + base::WaitableEvent event(false, false); + io_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&FetchUrlTest::InitOnIO, + base::Unretained(this), &event)); + event.Wait(); + } + + virtual ~FetchUrlTest() { + base::WaitableEvent event(false, false); + io_thread_.message_loop_proxy()->PostTask( + FROM_HERE, + base::Bind(&FetchUrlTest::DestroyServerOnIO, + base::Unretained(this), &event)); + event.Wait(); + } + + void InitOnIO(base::WaitableEvent* event) { + net::TCPListenSocketFactory factory("127.0.0.1", 0); + server_ = new net::HttpServer(factory, this); + net::IPEndPoint address; + CHECK_EQ(net::OK, server_->GetLocalAddress(&address)); + server_url_ = GURL( + base::StringPrintf("http://127.0.0.1:%d", address.port())); + event->Signal(); + } + + void DestroyServerOnIO(base::WaitableEvent* event) { + server_ = NULL; + event->Signal(); + } + + // Overridden from net::HttpServer::Delegate: + virtual void OnHttpRequest(int connection_id, + const net::HttpServerRequestInfo& info) OVERRIDE { + switch (response_) { + case kSendHello: + server_->Send200(connection_id, "hello", "text/plain"); + break; + case kSend404: + server_->Send404(connection_id); + break; + case kClose: + // net::HttpServer doesn't allow us to close connection during callback. + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&net::HttpServer::Close, server_, connection_id)); + break; + default: + break; + } + } + + virtual void OnWebSocketRequest( + int connection_id, + const net::HttpServerRequestInfo& info) OVERRIDE {} + virtual void OnWebSocketMessage(int connection_id, + const std::string& data) OVERRIDE {} + virtual void OnClose(int connection_id) OVERRIDE {} + + protected: + enum ServerResponse { + kSendHello = 0, + kSend404, + kClose, + }; + + base::Thread io_thread_; + ServerResponse response_; + scoped_refptr<net::HttpServer> server_; + scoped_refptr<URLRequestContextGetter> context_getter_; + GURL server_url_; +}; + +} // namespace + +TEST_F(FetchUrlTest, Http200) { + std::string response("stuff"); + ASSERT_TRUE(FetchUrl(server_url_, context_getter_, &response)); + ASSERT_STREQ("hello", response.c_str()); +} + +TEST_F(FetchUrlTest, HttpNon200) { + response_ = kSend404; + std::string response("stuff"); + ASSERT_FALSE(FetchUrl(server_url_, context_getter_, &response)); + ASSERT_STREQ("stuff", response.c_str()); +} + +TEST_F(FetchUrlTest, ConnectionClose) { + response_ = kClose; + std::string response("stuff"); + ASSERT_FALSE(FetchUrl(server_url_, context_getter_, &response)); + ASSERT_STREQ("stuff", response.c_str()); +} + +TEST_F(FetchUrlTest, NoServer) { + std::string response("stuff"); + GURL bogus_url("http://localhost:33333"); + ASSERT_FALSE(FetchUrl(bogus_url, context_getter_, &response)); + ASSERT_STREQ("stuff", response.c_str()); +} diff --git a/chrome/test/chromedriver/status.cc b/chrome/test/chromedriver/status.cc index bd50279..89dc7e9 100644 --- a/chrome/test/chromedriver/status.cc +++ b/chrome/test/chromedriver/status.cc @@ -19,6 +19,8 @@ const char* DefaultMessageForStatusCode(StatusCode code) { return "session not created exception"; case kNoSuchSession: return "no such session"; + case kChromeNotReachable: + return "chrome not reachable"; default: return "<unknown>"; } @@ -34,6 +36,21 @@ Status::Status(StatusCode code, const std::string& details) msg_(DefaultMessageForStatusCode(code) + std::string(": ") + details) { } +Status::Status(StatusCode code, const Status& cause) + : code_(code), + msg_(DefaultMessageForStatusCode(code) + std::string("\nfrom ") + + cause.message()) {} + +Status::Status(StatusCode code, + const std::string& details, + const Status& cause) + : code_(code), + msg_(DefaultMessageForStatusCode(code) + std::string(": ") + details + + "\nfrom " + cause.message()) { +} + +Status::~Status() {} + bool Status::IsOk() const { return code_ == kOk; } diff --git a/chrome/test/chromedriver/status.h b/chrome/test/chromedriver/status.h index a52d6e4..7ecb808 100644 --- a/chrome/test/chromedriver/status.h +++ b/chrome/test/chromedriver/status.h @@ -13,7 +13,9 @@ enum StatusCode { kUnknownCommand = 9, kUnknownError = 13, kSessionNotCreatedException = 33, - kNoSuchSession = 100 + // Chrome-specific status codes. + kNoSuchSession = 100, + kChromeNotReachable, }; // Represents a WebDriver status, which may be an error or ok. @@ -21,6 +23,9 @@ class Status { public: explicit Status(StatusCode code); Status(StatusCode code, const std::string& details); + Status(StatusCode code, const Status& cause); + Status(StatusCode code, const std::string& details, const Status& cause); + ~Status(); bool IsOk() const; bool IsError() const; diff --git a/chrome/test/chromedriver/status_unittest.cc b/chrome/test/chromedriver/status_unittest.cc index a585afb..9946c07 100644 --- a/chrome/test/chromedriver/status_unittest.cc +++ b/chrome/test/chromedriver/status_unittest.cc @@ -14,17 +14,33 @@ TEST(StatusTest, Ok) { } TEST(StatusTest, Error) { - Status ok(kUnknownCommand); - ASSERT_FALSE(ok.IsOk()); - ASSERT_TRUE(ok.IsError()); - ASSERT_EQ(kUnknownCommand, ok.code()); - ASSERT_STREQ("unknown command", ok.message().c_str()); + Status error(kUnknownCommand); + ASSERT_FALSE(error.IsOk()); + ASSERT_TRUE(error.IsError()); + ASSERT_EQ(kUnknownCommand, error.code()); + ASSERT_STREQ("unknown command", error.message().c_str()); } TEST(StatusTest, ErrorWithDetails) { - Status ok(kUnknownError, "something happened"); - ASSERT_FALSE(ok.IsOk()); - ASSERT_TRUE(ok.IsError()); - ASSERT_EQ(kUnknownError, ok.code()); - ASSERT_STREQ("unknown error: something happened", ok.message().c_str()); + Status error(kUnknownError, "something happened"); + ASSERT_FALSE(error.IsOk()); + ASSERT_TRUE(error.IsError()); + ASSERT_EQ(kUnknownError, error.code()); + ASSERT_STREQ("unknown error: something happened", error.message().c_str()); +} + +TEST(StatusTest, ErrorWithCause) { + Status error( + kUnknownCommand, "quit", + Status( + kUnknownError, "something happened", + Status(kSessionNotCreatedException))); + ASSERT_FALSE(error.IsOk()); + ASSERT_TRUE(error.IsError()); + ASSERT_EQ(kUnknownCommand, error.code()); + ASSERT_STREQ( + "unknown command: quit\n" + "from unknown error: something happened\n" + "from session not created exception", + error.message().c_str()); } diff --git a/chrome/test/chromedriver/test.py b/chrome/test/chromedriver/test.py index 6f17e2a..de37fc5 100644 --- a/chrome/test/chromedriver/test.py +++ b/chrome/test/chromedriver/test.py @@ -19,6 +19,12 @@ class ChromeDriverTest(unittest.TestCase): driver = chromedriver.ChromeDriver(_CHROMEDRIVER_LIB, _CHROME_BINARY) driver.Quit() + def testLoadUrl(self): + driver = chromedriver.ChromeDriver(_CHROMEDRIVER_LIB) + driver.Load('http://www.google.com') + driver.Quit() + + if __name__ == '__main__': if len(sys.argv) != 2 and len(sys.argv) != 3: print ('Usage: %s <path_to_chromedriver_so> [path_to_chrome_binary]' % |