summaryrefslogtreecommitdiffstats
path: root/chrome/test/chromedriver
diff options
context:
space:
mode:
authorkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-23 19:56:46 +0000
committerkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-23 19:56:46 +0000
commita9f94f399a5925ef801b2856bd4ad79f2d823e77 (patch)
treee4d494e02ea773fb93fe4be092cbce1557ee6740 /chrome/test/chromedriver
parent7fbe511b49d31fcc88a7675dbc6b6d8c8013ca68 (diff)
downloadchromium_src-a9f94f399a5925ef801b2856bd4ad79f2d823e77.zip
chromium_src-a9f94f399a5925ef801b2856bd4ad79f2d823e77.tar.gz
chromium_src-a9f94f399a5925ef801b2856bd4ad79f2d823e77.tar.bz2
Initial check-in for new ChromeDriver based on DevTools.
Introduces 3 new targets, chromedriver2_lib, chromedriver2_unittests, and chromedriver2. The latter is a shared library which can be loaded into a test/automation process and used to control chrome. The shared library exposes a function ExecuteCommand, which takes WebDriver JSON commands and returns a WebDriver JSON response. In the future, when necessary, we may create a new chromedriver_server target which uses the chromedriver2_lib and exposes it remotely via sockets/http/whatever. BUG=none Review URL: https://chromiumcodereview.appspot.com/11099075 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@163651 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/chromedriver')
-rw-r--r--chrome/test/chromedriver/OWNERS2
-rw-r--r--chrome/test/chromedriver/chromedriver.cc111
-rw-r--r--chrome/test/chromedriver/chromedriver.h35
-rw-r--r--chrome/test/chromedriver/chromedriver.py39
-rw-r--r--chrome/test/chromedriver/chromedriver_shared_library.cc69
-rw-r--r--chrome/test/chromedriver/chromedriver_unittest.cc101
-rw-r--r--chrome/test/chromedriver/command_executor.h34
-rw-r--r--chrome/test/chromedriver/command_executor_impl.cc80
-rw-r--r--chrome/test/chromedriver/command_executor_impl.h62
-rw-r--r--chrome/test/chromedriver/command_executor_impl_unittest.cc59
-rw-r--r--chrome/test/chromedriver/status.cc47
-rw-r--r--chrome/test/chromedriver/status.h35
-rw-r--r--chrome/test/chromedriver/status_unittest.cc30
-rw-r--r--chrome/test/chromedriver/test.py30
14 files changed, 734 insertions, 0 deletions
diff --git a/chrome/test/chromedriver/OWNERS b/chrome/test/chromedriver/OWNERS
new file mode 100644
index 0000000..dd1e0b4
--- /dev/null
+++ b/chrome/test/chromedriver/OWNERS
@@ -0,0 +1,2 @@
+kkania@chromium.org
+klundberg@chromium.org
diff --git a/chrome/test/chromedriver/chromedriver.cc b/chrome/test/chromedriver/chromedriver.cc
new file mode 100644
index 0000000..8d9b41b
--- /dev/null
+++ b/chrome/test/chromedriver/chromedriver.cc
@@ -0,0 +1,111 @@
+// 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/chromedriver.h"
+
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.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/command_executor_impl.h"
+#include "chrome/test/chromedriver/status.h"
+
+namespace {
+
+// Guards initialization of |g_command_executor|.
+base::LazyInstance<base::Lock> g_lazy_lock = LAZY_INSTANCE_INITIALIZER;
+
+CommandExecutor* g_command_executor = NULL;
+
+CommandExecutor* CreateCommandExecutor() {
+ return new CommandExecutorImpl();
+}
+
+CommandExecutorFactoryFunc g_executor_factory = &CreateCommandExecutor;
+
+void SetResponse(StatusCode status,
+ const base::Value* value,
+ const std::string& session_id,
+ std::string* response) {
+ base::DictionaryValue response_dict;
+ response_dict.SetInteger("status", status);
+ response_dict.Set("value", value->DeepCopy());
+ response_dict.SetString("sessionId", session_id);
+ std::string json;
+ base::JSONWriter::Write(&response_dict, response);
+}
+
+void SetError(const std::string& error_msg,
+ std::string* response) {
+ base::DictionaryValue value;
+ value.SetString("message", error_msg);
+ SetResponse(kUnknownError, &value, "", response);
+}
+
+} // namespace
+
+void SetCommandExecutorFactoryForTesting(CommandExecutorFactoryFunc func) {
+ g_executor_factory = func;
+}
+
+// Synchronously executes the given command. Thread safe.
+// Command must be a JSON object:
+// {
+// "name": <string>,
+// "parameters": <dictionary>,
+// "sessionId": <string>
+// }
+// Response will always be a JSON object:
+// {
+// "status": <integer>,
+// "value": <object>,
+// "sessionId": <string>
+// }
+// If "status" is non-zero, "value" will be an object with a string "message"
+// property which signifies the error message.
+void ExecuteCommand(const std::string& command, std::string* response) {
+ std::string error_msg;
+ scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError(
+ command, 0, NULL, &error_msg));
+ if (!value.get()) {
+ SetError("failed to parse command: " + error_msg, response);
+ return;
+ }
+ base::DictionaryValue* command_dict;
+ if (!value->GetAsDictionary(&command_dict)) {
+ SetError("invalid command (must be dictionary)", response);
+ return;
+ }
+ std::string name;
+ if (!command_dict->GetString("name", &name)) {
+ SetError("invalid command (must contain 'name' of type string)", response);
+ return;
+ }
+ base::DictionaryValue* params;
+ if (!command_dict->GetDictionary("parameters", &params)) {
+ SetError("invalid command (must contain 'parameters' of type dict)",
+ response);
+ return;
+ }
+ std::string session_id;
+ if (!command_dict->GetString("sessionId", &session_id)) {
+ SetError("invalid command (must contain 'sessionId' of type string)",
+ response);
+ return;
+ }
+ StatusCode out_status = kOk;
+ scoped_ptr<base::Value> out_value;
+ std::string out_session_id;
+ {
+ base::AutoLock(g_lazy_lock.Get());
+ if (!g_command_executor)
+ g_command_executor = g_executor_factory();
+ }
+ g_command_executor->ExecuteCommand(
+ name, *params, session_id, &out_status, &out_value, &out_session_id);
+ SetResponse(out_status, out_value.get(), out_session_id, response);
+}
diff --git a/chrome/test/chromedriver/chromedriver.h b/chrome/test/chromedriver/chromedriver.h
new file mode 100644
index 0000000..fbad292
--- /dev/null
+++ b/chrome/test/chromedriver/chromedriver.h
@@ -0,0 +1,35 @@
+// 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_CHROMEDRIVER_H_
+#define CHROME_TEST_CHROMEDRIVER_CHROMEDRIVER_H_
+
+#include <string>
+
+class CommandExecutor;
+
+typedef CommandExecutor* (*CommandExecutorFactoryFunc)();
+
+// Sets the function to use for creating new |CommandExecutor|s. Only
+// for testing purposes.
+void SetCommandExecutorFactoryForTesting(CommandExecutorFactoryFunc func);
+
+// Synchronously executes the given command. Thread safe.
+// Command must be a JSON object:
+// {
+// "name": <string>,
+// "parameters": <dictionary>,
+// "sessionId": <string>
+// }
+// Response will always be a JSON object:
+// {
+// "status": <integer>,
+// "value": <object>,
+// "sessionId": <string>
+// }
+// If "status" is non-zero, "value" will be an object with a string "message"
+// property which signifies the error message.
+void ExecuteCommand(const std::string& command, std::string* response);
+
+#endif // CHROME_TEST_CHROMEDRIVER_CHROMEDRIVER_H_
diff --git a/chrome/test/chromedriver/chromedriver.py b/chrome/test/chromedriver/chromedriver.py
new file mode 100644
index 0000000..02d395a
--- /dev/null
+++ b/chrome/test/chromedriver/chromedriver.py
@@ -0,0 +1,39 @@
+# 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.
+
+import ctypes
+import json
+
+
+class ChromeDriver(object):
+ """Starts and controls a single Chrome instance on this machine."""
+
+ def __init__(self, lib_path):
+ self._lib = ctypes.CDLL(lib_path)
+ self._session_id = self._ExecuteCommand('newSession')
+
+ def _ExecuteCommand(self, name, params={}, session_id=''):
+ cmd = {
+ 'name': name,
+ 'parameters': params,
+ 'sessionId': session_id
+ }
+ cmd_json = json.dumps(cmd)
+ response_data = ctypes.c_char_p()
+ response_size = ctypes.c_uint()
+ self._lib.ExecuteCommand(
+ ctypes.c_char_p(cmd_json),
+ ctypes.c_uint(len(cmd_json)),
+ ctypes.byref(response_data),
+ ctypes.byref(response_size))
+ response_json = ctypes.string_at(response_data, response_size.value)
+ self._lib.Free(response_data)
+ return json.loads(response_json)['value']
+
+ def _ExecuteSessionCommand(self, name, params={}):
+ return self._ExecuteCommand(name, params, self._session_id)
+
+ def Quit(self):
+ """Quits the browser and ends the session."""
+ self._ExecuteSessionCommand('quit')
diff --git a/chrome/test/chromedriver/chromedriver_shared_library.cc b/chrome/test/chromedriver/chromedriver_shared_library.cc
new file mode 100644
index 0000000..73b49ef
--- /dev/null
+++ b/chrome/test/chromedriver/chromedriver_shared_library.cc
@@ -0,0 +1,69 @@
+// 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 <cstring>
+#include <string>
+
+#include "base/at_exit.h"
+#include "build/build_config.h"
+#include "chrome/test/chromedriver/chromedriver.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+#if defined(OS_WIN)
+#define EXPORT __declspec(dllexport)
+#else
+#define EXPORT __attribute__((visibility("default")))
+#endif
+
+namespace {
+
+base::AtExitManager* g_at_exit = NULL;
+
+} // namespace
+
+extern "C" {
+
+// Synchronously executes the given command. Thread safe.
+// Every call dynamically allocates a |response| string, which must be
+// deallocated by calling |Free|. This |response| string is not guaranteed
+// to be null terminated, and may contain null characters within. You
+// should use the given response size to construct the string.
+void EXPORT ExecuteCommand(
+ const char* command,
+ unsigned int command_size,
+ char** response,
+ unsigned int* response_size) {
+ std::string command_str(command, command_size);
+ std::string json;
+ ExecuteCommand(command_str, &json);
+ *response = new char[json.length()];
+ std::memcpy(*response, json.c_str(), json.length());
+ *response_size = json.length();
+}
+
+void EXPORT Free(char* p) {
+ delete [] p;
+}
+
+} // extern "C"
+
+#if defined(OS_WIN)
+BOOL APIENTRY DllMain(HMODULE module, DWORD reason, void* reserved) {
+ if (reason == DLL_PROCESS_ATTACH)
+ g_at_exit = new base::AtExitManager();
+ if (reason == DLL_PROCESS_DETACH)
+ delete g_at_exit;
+ return TRUE;
+}
+#else
+void __attribute__((constructor)) OnLoad(void) {
+ g_at_exit = new base::AtExitManager();
+}
+void __attribute__((destructor)) OnUnload(void) {
+ delete g_at_exit;
+}
+#endif
diff --git a/chrome/test/chromedriver/chromedriver_unittest.cc b/chrome/test/chromedriver/chromedriver_unittest.cc
new file mode 100644
index 0000000..5b3e248
--- /dev/null
+++ b/chrome/test/chromedriver/chromedriver_unittest.cc
@@ -0,0 +1,101 @@
+// 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 "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/json/json_reader.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "base/values.h"
+#include "chrome/test/chromedriver/chromedriver.h"
+#include "chrome/test/chromedriver/command_executor.h"
+#include "chrome/test/chromedriver/status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void ExpectExecuteError(const std::string& command) {
+ std::string response;
+ ExecuteCommand(command, &response);
+ scoped_ptr<base::Value> value(base::JSONReader::Read(response));
+ ASSERT_TRUE(value.get());
+ base::DictionaryValue* dict;
+ ASSERT_TRUE(value->GetAsDictionary(&dict));
+ int status;
+ ASSERT_TRUE(dict->GetInteger("status", &status));
+ EXPECT_EQ(kUnknownError, status);
+}
+
+class ExecutorMock : public CommandExecutor {
+ public:
+ virtual ~ExecutorMock() {}
+
+ 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 {
+ EXPECT_STREQ("name", name.c_str());
+ int param = 0;
+ EXPECT_TRUE(params.GetInteger("param", &param));
+ EXPECT_EQ(100, param);
+ EXPECT_STREQ("id", session_id.c_str());
+ *status = kOk;
+ value->reset(new base::StringValue("stuff"));
+ *out_session_id = "session_id";
+ }
+
+ static void CheckExecuteCommand() {
+ std::string response;
+ ::ExecuteCommand("{\"name\": \"name\", "
+ " \"parameters\": {\"param\": 100}, "
+ " \"sessionId\": \"id\"}",
+ &response);
+ scoped_ptr<base::Value> value(base::JSONReader::Read(response));
+ ASSERT_TRUE(value.get());
+ base::DictionaryValue* dict;
+ ASSERT_TRUE(value->GetAsDictionary(&dict));
+ int status;
+ ASSERT_TRUE(dict->GetInteger("status", &status));
+ std::string value_str;
+ ASSERT_TRUE(dict->GetString("value", &value_str));
+ EXPECT_STREQ("stuff", value_str.c_str());
+ EXPECT_EQ(kOk, status);
+ }
+};
+
+CommandExecutor* CreateExecutorMock() {
+ return new ExecutorMock();
+}
+
+} // namespace
+
+TEST(ChromeDriver, InvalidCommands) {
+ ExpectExecuteError("hi[]");
+ ExpectExecuteError("[]");
+ ExpectExecuteError(
+ "{\"parameters\": {}, \"sessionId\": \"\"}");
+ ExpectExecuteError(
+ "{\"name\": 1, \"parameters\": {}, \"sessionId\": \"\"}");
+ ExpectExecuteError(
+ "{\"name\": \"\", \"sessionId\": \"\"}");
+ ExpectExecuteError(
+ "{\"name\": \"\", \"parameters\": 1, \"sessionId\": \"\"}");
+ ExpectExecuteError(
+ "{\"name\": \"\", \"parameters\": {}}");
+ ExpectExecuteError(
+ "{\"name\": \"\", \"parameters\": {}, \"sessionId\": 1}");
+}
+
+TEST(ChromeDriver, ExecuteCommand) {
+ SetCommandExecutorFactoryForTesting(&CreateExecutorMock);
+ ExecutorMock::CheckExecuteCommand();
+}
diff --git a/chrome/test/chromedriver/command_executor.h b/chrome/test/chromedriver/command_executor.h
new file mode 100644
index 0000000..db0b0f9
--- /dev/null
+++ b/chrome/test/chromedriver/command_executor.h
@@ -0,0 +1,34 @@
+// 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_COMMAND_EXECUTOR_H_
+#define CHROME_TEST_CHROMEDRIVER_COMMAND_EXECUTOR_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "chrome/test/chromedriver/status.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+// Executes WebDriver commands.
+class CommandExecutor {
+ public:
+ virtual ~CommandExecutor() {}
+
+ // Executes a command synchronously. This function must be thread safe.
+ virtual void ExecuteCommand(const std::string& name,
+ const base::DictionaryValue& params,
+ const std::string& session_id,
+ StatusCode* status_code,
+ scoped_ptr<base::Value>* value,
+ std::string* out_session_id) = 0;
+};
+
+#endif // CHROME_TEST_CHROMEDRIVER_COMMAND_EXECUTOR_H_
diff --git a/chrome/test/chromedriver/command_executor_impl.cc b/chrome/test/chromedriver/command_executor_impl.cc
new file mode 100644
index 0000000..422d0ea
--- /dev/null
+++ b/chrome/test/chromedriver/command_executor_impl.cc
@@ -0,0 +1,80 @@
+// 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/command_executor_impl.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "chrome/test/chromedriver/status.h"
+
+namespace {
+
+void ExecuteQuit(
+ const base::DictionaryValue& params,
+ Status* status,
+ scoped_ptr<base::Value>* value) {
+}
+
+} // namespace
+
+CommandExecutorImpl::CommandExecutorImpl()
+ : next_session_id_(1) {
+ session_command_map_.insert(std::make_pair("quit", base::Bind(
+ &ExecuteQuit)));
+}
+
+CommandExecutorImpl::~CommandExecutorImpl() {}
+
+void CommandExecutorImpl::ExecuteCommand(
+ const std::string& name,
+ const base::DictionaryValue& params,
+ const std::string& session_id,
+ StatusCode* status_code,
+ scoped_ptr<base::Value>* value,
+ std::string* out_session_id) {
+ base::AutoLock auto_lock(lock_);
+ Status status(kOk);
+ ExecuteCommandInternal(name, params, session_id, &status, value,
+ out_session_id);
+ *status_code = status.code();
+ if (status.IsError()) {
+ base::DictionaryValue* error = new base::DictionaryValue();
+ error->SetString("message", status.message());
+ value->reset(error);
+ }
+ if (!*value)
+ value->reset(base::Value::CreateNullValue());
+}
+
+void CommandExecutorImpl::ExecuteCommandInternal(
+ const std::string& name,
+ const base::DictionaryValue& params,
+ const std::string& session_id,
+ Status* status,
+ scoped_ptr<base::Value>* value,
+ std::string* out_session_id) {
+ *out_session_id = session_id;
+ if (name == "newSession") {
+ ExecuteNewSession(out_session_id);
+ return;
+ }
+
+ // Although most versions of the C++ std lib supposedly guarantee
+ // thread safety for concurrent reads, better safe than sorry.
+ SessionCommandMap::const_iterator iter = session_command_map_.find(name);
+ if (iter != session_command_map_.end()) {
+ SessionCommandCallback command_callback = iter->second;
+ base::AutoUnlock auto_unlock(lock_);
+ command_callback.Run(params, status, value);
+ return;
+ }
+
+ *status = Status(kUnknownCommand, name);
+}
+
+void CommandExecutorImpl::ExecuteNewSession(std::string* session_id) {
+ *session_id = base::IntToString(next_session_id_++);
+}
diff --git a/chrome/test/chromedriver/command_executor_impl.h b/chrome/test/chromedriver/command_executor_impl.h
new file mode 100644
index 0000000..63cdc22
--- /dev/null
+++ b/chrome/test/chromedriver/command_executor_impl.h
@@ -0,0 +1,62 @@
+// 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_COMMAND_EXECUTOR_IMPL_H_
+#define CHROME_TEST_CHROMEDRIVER_COMMAND_EXECUTOR_IMPL_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/lock.h"
+#include "chrome/test/chromedriver/command_executor.h"
+#include "chrome/test/chromedriver/status.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+class CommandExecutorImpl : public CommandExecutor {
+ public:
+ CommandExecutorImpl();
+ virtual ~CommandExecutorImpl();
+
+ // Overriden from CommandExecutor:
+ virtual void ExecuteCommand(const std::string& name,
+ const base::DictionaryValue& params,
+ const std::string& session_id,
+ StatusCode* status_code,
+ scoped_ptr<base::Value>* value,
+ std::string* out_session_id) OVERRIDE;
+
+ private:
+ typedef base::Callback<void(
+ const base::DictionaryValue& params,
+ Status* status,
+ scoped_ptr<base::Value>* value)>
+ SessionCommandCallback;
+ typedef std::map<std::string, SessionCommandCallback> SessionCommandMap;
+
+ void ExecuteCommandInternal(
+ const std::string& name,
+ const base::DictionaryValue& params,
+ const std::string& session_id,
+ Status* status,
+ scoped_ptr<base::Value>* value,
+ std::string* out_session_id);
+
+ void ExecuteNewSession(std::string* session_id);
+
+ base::Lock lock_;
+ int next_session_id_;
+ SessionCommandMap session_command_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(CommandExecutorImpl);
+};
+
+#endif // CHROME_TEST_CHROMEDRIVER_COMMAND_EXECUTOR_IMPL_H_
diff --git a/chrome/test/chromedriver/command_executor_impl_unittest.cc b/chrome/test/chromedriver/command_executor_impl_unittest.cc
new file mode 100644
index 0000000..0485656
--- /dev/null
+++ b/chrome/test/chromedriver/command_executor_impl_unittest.cc
@@ -0,0 +1,59 @@
+// 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/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "chrome/test/chromedriver/command_executor_impl.h"
+#include "chrome/test/chromedriver/status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(CommandExecutorTest, NewSession) {
+ CommandExecutorImpl executor;
+ base::DictionaryValue empty_dict;
+ StatusCode status;
+ scoped_ptr<base::Value> value;
+ std::string session_id;
+ executor.ExecuteCommand("newSession", empty_dict, "",
+ &status, &value, &session_id);
+ ASSERT_EQ(kOk, status);
+ ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value->IsType(base::Value::TYPE_NULL));
+ ASSERT_STREQ("1", session_id.c_str());
+ executor.ExecuteCommand("newSession", empty_dict, "random",
+ &status, &value, &session_id);
+ ASSERT_STREQ("2", session_id.c_str());
+}
+
+TEST(CommandExecutorTest, UnknownCommand) {
+ CommandExecutorImpl executor;
+ base::DictionaryValue empty_dict;
+ StatusCode status;
+ scoped_ptr<base::Value> value;
+ std::string session_id;
+ executor.ExecuteCommand("noSuchCommand", empty_dict, "session",
+ &status, &value, &session_id);
+ ASSERT_EQ(kUnknownCommand, status);
+ base::DictionaryValue* error;
+ ASSERT_TRUE(value->GetAsDictionary(&error));
+ std::string error_msg;
+ ASSERT_TRUE(error->GetString("message", &error_msg));
+ ASSERT_STREQ("unknown command: noSuchCommand", error_msg.c_str());
+ ASSERT_STREQ("session", session_id.c_str());
+}
+
+TEST(CommandExecutorTest, Quit) {
+ CommandExecutorImpl executor;
+ base::DictionaryValue empty_dict;
+ StatusCode status;
+ scoped_ptr<base::Value> value;
+ std::string session_id;
+ executor.ExecuteCommand("quit", empty_dict, "id",
+ &status, &value, &session_id);
+ ASSERT_EQ(kOk, status);
+ ASSERT_TRUE(value.get());
+ ASSERT_TRUE(value->IsType(base::Value::TYPE_NULL));
+ ASSERT_STREQ("id", session_id.c_str());
+}
diff --git a/chrome/test/chromedriver/status.cc b/chrome/test/chromedriver/status.cc
new file mode 100644
index 0000000..60e09f0
--- /dev/null
+++ b/chrome/test/chromedriver/status.cc
@@ -0,0 +1,47 @@
+// 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/status.h"
+
+namespace {
+
+// Returns the string equivalent of the given |ErrorCode|.
+const char* DefaultMessageForStatusCode(StatusCode code) {
+ switch (code) {
+ case kOk:
+ return "ok";
+ case kUnknownError:
+ return "unknown error";
+ case kUnknownCommand:
+ return "unknown command";
+ default:
+ return "<unknown>";
+ }
+}
+
+} // namespace
+
+Status::Status(StatusCode code)
+ : code_(code), msg_(DefaultMessageForStatusCode(code)) {}
+
+Status::Status(StatusCode code, const std::string& details)
+ : code_(code),
+ msg_(DefaultMessageForStatusCode(code) + std::string(": ") + details) {
+}
+
+bool Status::IsOk() const {
+ return code_ == kOk;
+}
+
+bool Status::IsError() const {
+ return code_ != kOk;
+}
+
+StatusCode Status::code() const {
+ return code_;
+}
+
+const std::string& Status::message() const {
+ return msg_;
+}
diff --git a/chrome/test/chromedriver/status.h b/chrome/test/chromedriver/status.h
new file mode 100644
index 0000000..8f5a75b
--- /dev/null
+++ b/chrome/test/chromedriver/status.h
@@ -0,0 +1,35 @@
+// 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_STATUS_H_
+#define CHROME_TEST_CHROMEDRIVER_STATUS_H_
+
+#include <string>
+
+// WebDriver standard status codes.
+enum StatusCode {
+ kOk = 0,
+ kUnknownCommand = 9,
+ kUnknownError = 13
+};
+
+// Represents a WebDriver status, which may be an error or ok.
+class Status {
+ public:
+ explicit Status(StatusCode code);
+ Status(StatusCode code, const std::string& details);
+
+ bool IsOk() const;
+ bool IsError() const;
+
+ StatusCode code() const;
+
+ const std::string& message() const;
+
+ private:
+ StatusCode code_;
+ std::string msg_;
+};
+
+#endif // CHROME_TEST_CHROMEDRIVER_STATUS_H_
diff --git a/chrome/test/chromedriver/status_unittest.cc b/chrome/test/chromedriver/status_unittest.cc
new file mode 100644
index 0000000..a585afb
--- /dev/null
+++ b/chrome/test/chromedriver/status_unittest.cc
@@ -0,0 +1,30 @@
+// 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/status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(StatusTest, Ok) {
+ Status ok(kOk);
+ ASSERT_TRUE(ok.IsOk());
+ ASSERT_FALSE(ok.IsError());
+ ASSERT_EQ(kOk, ok.code());
+ ASSERT_STREQ("ok", ok.message().c_str());
+}
+
+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());
+}
+
+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());
+}
diff --git a/chrome/test/chromedriver/test.py b/chrome/test/chromedriver/test.py
new file mode 100644
index 0000000..34ea2636
--- /dev/null
+++ b/chrome/test/chromedriver/test.py
@@ -0,0 +1,30 @@
+# 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.
+
+"""End to end tests for ChromeDriver."""
+
+import ctypes
+import os
+import sys
+import unittest
+
+import chromedriver
+
+
+class ChromeDriverTest(unittest.TestCase):
+ """End to end tests for ChromeDriver."""
+
+ def testStartStop(self):
+ driver = chromedriver.ChromeDriver(_CHROMEDRIVER_LIB)
+ driver.Quit()
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ print 'Usage: %s <path_to_chromedriver_so>' % __file__
+ sys.exit(1)
+ global _CHROMEDRIVER_LIB
+ _CHROMEDRIVER_LIB = os.path.abspath(sys.argv[1])
+ all_tests_suite = unittest.defaultTestLoader.loadTestsFromModule(
+ sys.modules[__name__])
+ unittest.TextTestRunner().run(all_tests_suite)