summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-27 21:39:25 +0000
committerkkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-27 21:39:25 +0000
commit582b55d4a0ee1c0ceea48b1b337b4b71597f9fe0 (patch)
tree3da47bb3e93368d73e9767af039341dd1db244e6
parent91a5ded4aa5088c26916a5eb81d52ff7d1cc1821 (diff)
downloadchromium_src-582b55d4a0ee1c0ceea48b1b337b4b71597f9fe0.zip
chromium_src-582b55d4a0ee1c0ceea48b1b337b4b71597f9fe0.tar.gz
chromium_src-582b55d4a0ee1c0ceea48b1b337b4b71597f9fe0.tar.bz2
Enhance and refactor ChromeDriver's capability handling. Log warning for
non-recognized capabilities, return errors on incorrect input types, and allow new format where all options are placed under a chromeOptions dictionary. The latter is needed to simplify handling capabilities on the WebDriver client side. See http://codereview.appspot.com/5307047/. BUG=82895,94721 TEST=none Review URL: http://codereview.chromium.org/8341044 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@107629 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/chrome_tests.gypi3
-rw-r--r--chrome/test/webdriver/commands/create_session.cc196
-rw-r--r--chrome/test/webdriver/commands/session_with_id.cc13
-rw-r--r--chrome/test/webdriver/webdriver_capabilities_parser.cc240
-rw-r--r--chrome/test/webdriver/webdriver_capabilities_parser.h109
-rw-r--r--chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc141
-rw-r--r--chrome/test/webdriver/webdriver_util.cc95
-rw-r--r--chrome/test/webdriver/webdriver_util.h12
8 files changed, 632 insertions, 177 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 6ef71d9..cc30bd3 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -934,6 +934,8 @@
'test/webdriver/webdriver_automation.h',
'test/webdriver/webdriver_basic_types.cc',
'test/webdriver/webdriver_basic_types.h',
+ 'test/webdriver/webdriver_capabilities_parser.cc',
+ 'test/webdriver/webdriver_capabilities_parser.h',
'test/webdriver/webdriver_dispatch.cc',
'test/webdriver/webdriver_dispatch.h',
'test/webdriver/webdriver_element_id.cc',
@@ -1035,6 +1037,7 @@
'test/webdriver/frame_path_unittest.cc',
'test/webdriver/http_response_unittest.cc',
'test/webdriver/keycode_text_conversion_unittest.cc',
+ 'test/webdriver/webdriver_capabilities_parser_unittest.cc',
'test/webdriver/webdriver_dispatch_unittest.cc',
'test/webdriver/webdriver_key_converter_unittest.cc',
'test/webdriver/webdriver_test_util.cc',
diff --git a/chrome/test/webdriver/commands/create_session.cc b/chrome/test/webdriver/commands/create_session.cc
index 84bdd5d..fd16c0b 100644
--- a/chrome/test/webdriver/commands/create_session.cc
+++ b/chrome/test/webdriver/commands/create_session.cc
@@ -4,59 +4,21 @@
#include "chrome/test/webdriver/commands/create_session.h"
-#include <sstream>
#include <string>
-#include "base/base64.h"
#include "base/command_line.h"
#include "base/file_path.h"
-#include "base/file_util.h"
#include "base/logging.h"
#include "base/scoped_temp_dir.h"
-#include "base/string_number_conversions.h"
-#include "base/stringprintf.h"
#include "base/values.h"
-#include "chrome/app/chrome_command_ids.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/common/chrome_switches.h"
-#include "chrome/common/zip.h"
#include "chrome/test/webdriver/commands/response.h"
+#include "chrome/test/webdriver/webdriver_capabilities_parser.h"
#include "chrome/test/webdriver/webdriver_error.h"
#include "chrome/test/webdriver/webdriver_session.h"
#include "chrome/test/webdriver/webdriver_session_manager.h"
namespace webdriver {
-namespace {
-
-bool WriteBase64DataToFile(const FilePath& filename,
- const std::string& base64data,
- std::string* error_msg) {
- std::string data;
- if (!base::Base64Decode(base64data, &data)) {
- *error_msg = "Invalid base64 encoded data.";
- return false;
- }
- if (!file_util::WriteFile(filename, data.c_str(), data.length())) {
- *error_msg = "Could not write data to file.";
- return false;
- }
- return true;
-}
-
-Error* GetBooleanCapability(
- const base::DictionaryValue* dict, const std::string& key, bool* option) {
- Value* value = NULL;
- if (dict->GetWithoutPathExpansion(key, &value)) {
- if (!value->GetAsBoolean(option)) {
- return new Error(kUnknownError, key + " must be a boolean");
- }
- }
- return NULL;
-}
-
-} // namespace
-
CreateSession::CreateSession(const std::vector<std::string>& path_segments,
const DictionaryValue* const parameters)
: Command(path_segments, parameters) {}
@@ -66,146 +28,42 @@ CreateSession::~CreateSession() {}
bool CreateSession::DoesPost() { return true; }
void CreateSession::ExecutePost(Response* const response) {
- DictionaryValue *capabilities = NULL;
- if (!GetDictionaryParameter("desiredCapabilities", &capabilities)) {
+ DictionaryValue* dict;
+ if (!GetDictionaryParameter("desiredCapabilities", &dict)) {
response->SetError(new Error(
kBadRequest, "Missing or invalid 'desiredCapabilities'"));
return;
}
-
- Automation::BrowserOptions browser_options;
- FilePath::StringType path;
- if (capabilities->GetStringWithoutPathExpansion("chrome.binary", &path))
- browser_options.command = CommandLine(FilePath(path));
-
- ListValue* switches = NULL;
- const char* kCustomSwitchesKey = "chrome.switches";
- if (capabilities->GetListWithoutPathExpansion(kCustomSwitchesKey,
- &switches)) {
- for (size_t i = 0; i < switches->GetSize(); ++i) {
- std::string switch_string;
- if (!switches->GetString(i, &switch_string)) {
- response->SetError(new Error(
- kBadRequest, "Custom switch is not a string"));
- return;
- }
- size_t separator_index = switch_string.find("=");
- if (separator_index != std::string::npos) {
- CommandLine::StringType switch_string_native;
- if (!switches->GetString(i, &switch_string_native)) {
- response->SetError(new Error(
- kBadRequest, "Custom switch is not a string"));
- return;
- }
- browser_options.command.AppendSwitchNative(
- switch_string.substr(0, separator_index),
- switch_string_native.substr(separator_index + 1));
- } else {
- browser_options.command.AppendSwitch(switch_string);
- }
- }
- } else if (capabilities->HasKey(kCustomSwitchesKey)) {
- response->SetError(new Error(
- kBadRequest, "Custom switches must be a list"));
- return;
- }
-
- Value* verbose_value;
- if (capabilities->GetWithoutPathExpansion("chrome.verbose", &verbose_value)) {
- bool verbose = false;
- if (verbose_value->GetAsBoolean(&verbose)) {
- // Since logging is shared among sessions, if any session requests verbose
- // logging, verbose logging will be enabled for all sessions. It is not
- // possible to turn it off.
- if (verbose)
- logging::SetMinLogLevel(logging::LOG_INFO);
- } else {
- response->SetError(new Error(
- kBadRequest, "verbose must be a boolean true or false"));
- return;
- }
- }
-
- capabilities->GetStringWithoutPathExpansion(
- "chrome.channel", &browser_options.channel_id);
-
- ScopedTempDir temp_profile_dir;
- std::string base64_profile;
- if (capabilities->GetStringWithoutPathExpansion("chrome.profile",
- &base64_profile)) {
- if (!temp_profile_dir.CreateUniqueTempDir()) {
- response->SetError(new Error(
- kBadRequest, "Could not create temporary profile directory."));
- return;
- }
- FilePath temp_profile_zip(
- temp_profile_dir.path().AppendASCII("profile.zip"));
- std::string message;
- if (!WriteBase64DataToFile(temp_profile_zip, base64_profile, &message)) {
- response->SetError(new Error(kBadRequest, message));
- return;
- }
-
- browser_options.user_data_dir =
- temp_profile_dir.path().AppendASCII("user_data_dir");
- if (!zip::Unzip(temp_profile_zip, browser_options.user_data_dir)) {
- response->SetError(new Error(
- kBadRequest, "Could not unarchive provided user profile"));
- return;
- }
- }
-
- const char* kExtensions = "chrome.extensions";
- ScopedTempDir extensions_dir;
- ListValue* extensions_list = NULL;
- std::vector<FilePath> extensions;
- if (capabilities->GetListWithoutPathExpansion(kExtensions,
- &extensions_list)) {
- if (!extensions_dir.CreateUniqueTempDir()) {
- response->SetError(new Error(
- kBadRequest, "Could create temporary extensions directory."));
- return;
- }
- for (size_t i = 0; i < extensions_list->GetSize(); ++i) {
- std::string base64_extension;
- if (!extensions_list->GetString(i, &base64_extension)) {
- response->SetError(new Error(
- kBadRequest, "Extension must be a base64 encoded string."));
- return;
- }
- FilePath extension_file(
- extensions_dir.path().AppendASCII("extension" +
- base::IntToString(i) + ".crx"));
- std::string message;
- if (!WriteBase64DataToFile(extension_file, base64_extension, &message)) {
- response->SetError(new Error(kBadRequest, message));
- return;
- }
- extensions.push_back(extension_file);
- }
- } else if (capabilities->HasKey(kExtensions)) {
+ ScopedTempDir temp_dir;
+ if (!temp_dir.CreateUniqueTempDir()) {
response->SetError(new Error(
- kBadRequest, "Extensions must be a list of base64 encoded strings"));
+ kUnknownError, "Unable to create temp directory for unpacking"));
return;
}
-
- Session::Options session_options;
- Error* error = NULL;
- error = GetBooleanCapability(capabilities, "chrome.nativeEvents",
- &session_options.use_native_events);
- if (!error) {
- error = GetBooleanCapability(capabilities, "chrome.loadAsync",
- &session_options.load_async);
- }
- if (!error) {
- error = GetBooleanCapability(capabilities, "chrome.detach",
- &browser_options.detach_process);
- }
+ Capabilities caps;
+ CapabilitiesParser parser(dict, temp_dir.path(), &caps);
+ Error* error = parser.Parse();
if (error) {
response->SetError(error);
return;
}
+ // Since logging is shared among sessions, if any session requests verbose
+ // logging, verbose logging will be enabled for all sessions. It is not
+ // possible to turn it off.
+ if (caps.verbose)
+ logging::SetMinLogLevel(logging::LOG_INFO);
+
+ Session::Options session_options;
+ session_options.load_async = caps.load_async;
+ session_options.use_native_events = caps.native_events;
+
+ Automation::BrowserOptions browser_options;
+ browser_options.command = caps.command;
+ browser_options.channel_id = caps.channel;
+ browser_options.detach_process = caps.detach;
+ browser_options.user_data_dir = caps.profile;
+
// Session manages its own liftime, so do not call delete.
Session* session = new Session(session_options);
error = session->Init(browser_options);
@@ -215,8 +73,8 @@ void CreateSession::ExecutePost(Response* const response) {
}
// Install extensions.
- for (size_t i = 0; i < extensions.size(); ++i) {
- Error* error = session->InstallExtension(extensions[i]);
+ for (size_t i = 0; i < caps.extensions.size(); ++i) {
+ Error* error = session->InstallExtension(caps.extensions[i]);
if (error) {
response->SetError(error);
return;
diff --git a/chrome/test/webdriver/commands/session_with_id.cc b/chrome/test/webdriver/commands/session_with_id.cc
index b3155e2..5a95814 100644
--- a/chrome/test/webdriver/commands/session_with_id.cc
+++ b/chrome/test/webdriver/commands/session_with_id.cc
@@ -49,9 +49,20 @@ void SessionWithID::ExecuteGet(Response* const response) {
temp_value->SetString("platform", "unknown");
#endif
- temp_value->SetBoolean("cssSelectorsEnabled", true);
temp_value->SetBoolean("javascriptEnabled", true);
temp_value->SetBoolean("takesScreenshot", true);
+ temp_value->SetBoolean("handlesAlerts", true);
+ temp_value->SetBoolean("databaseEnabled", false);
+ temp_value->SetBoolean("locationContextEnabled", false);
+ temp_value->SetBoolean("applicationCacheEnabled", false);
+ temp_value->SetBoolean("browserConnectionEnabled", false);
+ temp_value->SetBoolean("cssSelectorsEnabled", true);
+ temp_value->SetBoolean("webStorageEnabled", false);
+ temp_value->SetBoolean("rotatable", false);
+ temp_value->SetBoolean("acceptSslCerts", false);
+ // Even when ChromeDriver does not OS-events, the input simulation produces
+ // the same effect for most purposes (except IME).
+ temp_value->SetBoolean("nativeEvents", true);
// Custom non-standard session info.
temp_value->SetWithoutPathExpansion(
diff --git a/chrome/test/webdriver/webdriver_capabilities_parser.cc b/chrome/test/webdriver/webdriver_capabilities_parser.cc
new file mode 100644
index 0000000..fabdd4a
--- /dev/null
+++ b/chrome/test/webdriver/webdriver_capabilities_parser.cc
@@ -0,0 +1,240 @@
+// 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/webdriver_capabilities_parser.h"
+
+#include "base/base64.h"
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "chrome/common/zip.h"
+#include "chrome/test/webdriver/webdriver_error.h"
+#include "chrome/test/webdriver/webdriver_util.h"
+
+using base::Value;
+
+namespace webdriver {
+
+namespace {
+
+Error* CreateBadInputError(const std::string& name,
+ Value::Type type,
+ const Value* option) {
+ return new Error(kBadRequest, base::StringPrintf(
+ "%s must be of type %s, not %s. Received: %s",
+ name.c_str(), GetJsonTypeName(type),
+ GetJsonTypeName(option->GetType()),
+ JsonStringifyForDisplay(option).c_str()));
+}
+
+} // namespace
+
+Capabilities::Capabilities()
+ : command(CommandLine::NO_PROGRAM),
+ detach(false),
+ load_async(false),
+ native_events(false),
+ verbose(false) { }
+
+Capabilities::~Capabilities() { }
+
+CapabilitiesParser::CapabilitiesParser(
+ const base::DictionaryValue* capabilities_dict,
+ const FilePath& root_path,
+ Capabilities* capabilities)
+ : dict_(capabilities_dict),
+ root_(root_path),
+ caps_(capabilities) {
+}
+
+CapabilitiesParser::~CapabilitiesParser() { }
+
+Error* CapabilitiesParser::Parse() {
+ const char kOptionsKey[] = "chromeOptions";
+ const base::DictionaryValue* options = dict_;
+ bool legacy_options = true;
+ Value* options_value;
+ if (dict_->Get(kOptionsKey, &options_value)) {
+ legacy_options = false;
+ if (options_value->IsType(Value::TYPE_DICTIONARY)) {
+ options = static_cast<DictionaryValue*>(options_value);
+ } else {
+ return CreateBadInputError(
+ kOptionsKey, Value::TYPE_DICTIONARY, options_value);
+ }
+ }
+
+ typedef Error* (CapabilitiesParser::*Parser)(const Value*);
+ std::map<std::string, Parser> parser_map;
+ if (legacy_options) {
+ parser_map["chrome.binary"] = &CapabilitiesParser::ParseBinary;
+ parser_map["chrome.channel"] = &CapabilitiesParser::ParseChannel;
+ parser_map["chrome.detach"] = &CapabilitiesParser::ParseDetach;
+ parser_map["chrome.extensions"] = &CapabilitiesParser::ParseExtensions;
+ parser_map["chrome.loadAsync"] = &CapabilitiesParser::ParseLoadAsync;
+ parser_map["chrome.nativeEvents"] = &CapabilitiesParser::ParseNativeEvents;
+ parser_map["chrome.profile"] = &CapabilitiesParser::ParseProfile;
+ parser_map["chrome.switches"] = &CapabilitiesParser::ParseArgs;
+ parser_map["chrome.verbose"] = &CapabilitiesParser::ParseVerbose;
+ } else {
+ parser_map["args"] = &CapabilitiesParser::ParseArgs;
+ parser_map["binary"] = &CapabilitiesParser::ParseBinary;
+ parser_map["channel"] = &CapabilitiesParser::ParseChannel;
+ parser_map["detach"] = &CapabilitiesParser::ParseDetach;
+ parser_map["extensions"] = &CapabilitiesParser::ParseExtensions;
+ parser_map["loadAsync"] = &CapabilitiesParser::ParseLoadAsync;
+ parser_map["nativeEvents"] = &CapabilitiesParser::ParseNativeEvents;
+ parser_map["profile"] = &CapabilitiesParser::ParseProfile;
+ parser_map["verbose"] = &CapabilitiesParser::ParseVerbose;
+ }
+
+ base::DictionaryValue::key_iterator key_iter = options->begin_keys();
+ for (; key_iter != options->end_keys(); ++key_iter) {
+ if (parser_map.find(*key_iter) == parser_map.end()) {
+ LOG(WARNING) << "Ignoring unrecognized capability: " << *key_iter;
+ continue;
+ }
+ Value* option = NULL;
+ options->GetWithoutPathExpansion(*key_iter, &option);
+ Error* error = (this->*parser_map[*key_iter])(option);
+ if (error) {
+ error->AddDetails(base::StringPrintf(
+ "Error occurred while processing capability '%s'",
+ (*key_iter).c_str()));
+ return error;
+ }
+ }
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseArgs(const Value* option) {
+ const base::ListValue* args;
+ if (!option->GetAsList(&args))
+ return CreateBadInputError("arguments", Value::TYPE_LIST, option);
+ for (size_t i = 0; i < args->GetSize(); ++i) {
+ std::string arg_string;
+ if (!args->GetString(i, &arg_string))
+ return CreateBadInputError("argument", Value::TYPE_STRING, option);
+ size_t separator_index = arg_string.find("=");
+ if (separator_index != std::string::npos) {
+ CommandLine::StringType arg_string_native;
+ if (!args->GetString(i, &arg_string_native))
+ return CreateBadInputError("argument", Value::TYPE_STRING, option);
+ caps_->command.AppendSwitchNative(
+ arg_string.substr(0, separator_index),
+ arg_string_native.substr(separator_index + 1));
+ } else {
+ caps_->command.AppendSwitch(arg_string);
+ }
+ }
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseBinary(const Value* option) {
+ FilePath::StringType path;
+ if (!option->GetAsString(&path)) {
+ return CreateBadInputError("binary path", Value::TYPE_STRING, option);
+ }
+ caps_->command.SetProgram(FilePath(path));
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseChannel(const Value* option) {
+ if (!option->GetAsString(&caps_->channel))
+ return CreateBadInputError("channel", Value::TYPE_STRING, option);
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseDetach(const Value* option) {
+ if (!option->GetAsBoolean(&caps_->detach))
+ return CreateBadInputError("detach", Value::TYPE_BOOLEAN, option);
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseExtensions(const Value* option) {
+ const base::ListValue* extensions;
+ if (!option->GetAsList(&extensions))
+ return CreateBadInputError("extensions", Value::TYPE_LIST, option);
+ for (size_t i = 0; i < extensions->GetSize(); ++i) {
+ std::string extension_base64;
+ if (!extensions->GetString(i, &extension_base64)) {
+ return new Error(kBadRequest,
+ "Each extension must be a base64 encoded string");
+ }
+ FilePath extension = root_.AppendASCII(
+ base::StringPrintf("extension%" PRIuS ".crx", i));
+ std::string error_msg;
+ if (!DecodeAndWriteFile(extension, extension_base64, false /* unzip */,
+ &error_msg)) {
+ return new Error(
+ kUnknownError,
+ "Error occurred while parsing extension: " + error_msg);
+ }
+ caps_->extensions.push_back(extension);
+ }
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseLoadAsync(const Value* option) {
+ if (!option->GetAsBoolean(&caps_->load_async))
+ return CreateBadInputError("loadAsync", Value::TYPE_BOOLEAN, option);
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseNativeEvents(const Value* option) {
+ if (!option->GetAsBoolean(&caps_->native_events))
+ return CreateBadInputError("nativeEvents", Value::TYPE_BOOLEAN, option);
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseProfile(const Value* option) {
+ std::string profile_base64;
+ if (!option->GetAsString(&profile_base64))
+ return CreateBadInputError("profile", Value::TYPE_STRING, option);
+ std::string error_msg;
+ caps_->profile = root_.AppendASCII("profile");
+ if (!DecodeAndWriteFile(caps_->profile, profile_base64, true /* unzip */,
+ &error_msg))
+ return new Error(kUnknownError, "unable to unpack profile: " + error_msg);
+ return NULL;
+}
+
+Error* CapabilitiesParser::ParseVerbose(const Value* option) {
+ if (!option->GetAsBoolean(&caps_->verbose))
+ return CreateBadInputError("verbose", Value::TYPE_BOOLEAN, option);
+ return NULL;
+}
+
+bool CapabilitiesParser::DecodeAndWriteFile(
+ const FilePath& path,
+ const std::string& base64,
+ bool unzip,
+ std::string* error_msg) {
+ std::string data;
+ if (!base::Base64Decode(base64, &data)) {
+ *error_msg = "Could not decode base64 data";
+ return false;
+ }
+ if (unzip) {
+ FilePath temp_file(root_.AppendASCII(GenerateRandomID()));
+ if (!file_util::WriteFile(temp_file, data.c_str(), data.length())) {
+ *error_msg = "Could not write file";
+ return false;
+ }
+ if (!zip::Unzip(temp_file, path)) {
+ *error_msg = "Could not unzip archive";
+ return false;
+ }
+ } else {
+ if (!file_util::WriteFile(path, data.c_str(), data.length())) {
+ *error_msg = "Could not write file";
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace webdriver
diff --git a/chrome/test/webdriver/webdriver_capabilities_parser.h b/chrome/test/webdriver/webdriver_capabilities_parser.h
new file mode 100644
index 0000000..8f9320d
--- /dev/null
+++ b/chrome/test/webdriver/webdriver_capabilities_parser.h
@@ -0,0 +1,109 @@
+// 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_WEBDRIVER_CAPABILITIES_PARSER_H_
+#define CHROME_TEST_WEBDRIVER_WEBDRIVER_CAPABILITIES_PARSER_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/file_path.h"
+
+namespace base {
+class DictionaryValue;
+class Value;
+}
+
+namespace webdriver {
+
+class Error;
+
+// Contains all the capabilities that a user may request when starting a
+// new session.
+struct Capabilities {
+ Capabilities();
+ ~Capabilities();
+
+ // Command line to use for starting Chrome.
+ CommandLine command;
+
+ // Channel ID to use for connecting to an already running Chrome.
+ std::string channel;
+
+ // Whether the lifetime of the started Chrome browser process should be
+ // bound to ChromeDriver's process. If true, Chrome will not quit if
+ // ChromeDriver dies.
+ bool detach;
+
+ // List of paths for extensions to install on startup.
+ std::vector<FilePath> extensions;
+
+ // Whether Chrome should not block when loading.
+ bool load_async;
+
+ // Whether Chrome should simulate input events using OS APIs instead of
+ // WebKit APIs.
+ bool native_events;
+
+ // Path to a custom profile to use.
+ FilePath profile;
+
+ // Whether ChromeDriver should log verbosely.
+ bool verbose;
+};
+
+// Parses the given capabilities dictionary to produce a |Capabilities|
+// instance.
+// See webdriver's desired capabilities for more info.
+class CapabilitiesParser {
+ public:
+ // Create a new parser. |capabilities_dict| is the dictionary for all
+ // of the user's desired capabilities. |root_path| is the root directory
+ // to use for writing any necessary files to disk. This function will not
+ // create it or delete it. All files written to disk will be placed in
+ // this directory.
+ CapabilitiesParser(const base::DictionaryValue* capabilities_dict,
+ const FilePath& root_path,
+ Capabilities* capabilities);
+ ~CapabilitiesParser();
+
+ // Parses the capabilities. May return an error.
+ Error* Parse();
+
+ private:
+ Error* ParseArgs(const base::Value* option);
+ Error* ParseBinary(const base::Value* option);
+ Error* ParseChannel(const base::Value* option);
+ Error* ParseDetach(const base::Value* option);
+ Error* ParseExtensions(const base::Value* option);
+ Error* ParseLoadAsync(const base::Value* option);
+ Error* ParseNativeEvents(const base::Value* option);
+ Error* ParseProfile(const base::Value* option);
+ Error* ParseVerbose(const base::Value* option);
+ // Decodes the given base64-encoded string, optionally unzips it, and
+ // writes the result to |path|.
+ // On error, false will be returned and |error_msg| will be set.
+ bool DecodeAndWriteFile(const FilePath& path,
+ const std::string& base64,
+ bool unzip,
+ std::string* error_msg);
+
+ // The capabilities dictionary to parse.
+ const base::DictionaryValue* dict_;
+
+ // The root directory under which to write all files.
+ const FilePath root_;
+
+ // A pointer to the capabilities to modify while parsing.
+ Capabilities* caps_;
+
+ DISALLOW_COPY_AND_ASSIGN(CapabilitiesParser);
+};
+
+} // namespace webdriver
+
+#endif // CHROME_TEST_WEBDRIVER_WEBDRIVER_CAPABILITIES_PARSER_H_
diff --git a/chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc b/chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc
new file mode 100644
index 0000000..f3123c3
--- /dev/null
+++ b/chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc
@@ -0,0 +1,141 @@
+// 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 "base/base64.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/scoped_temp_dir.h"
+#include "base/values.h"
+#include "chrome/common/zip.h"
+#include "chrome/test/webdriver/webdriver_capabilities_parser.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
+
+namespace webdriver {
+
+TEST(CapabilitiesParser, NoCaps) {
+ Capabilities caps;
+ DictionaryValue dict;
+ CapabilitiesParser parser(&dict, FilePath(), &caps);
+ ASSERT_FALSE(parser.Parse());
+}
+
+TEST(CapabilitiesParser, SimpleCaps) {
+ DictionaryValue dict;
+ DictionaryValue* options = new DictionaryValue();
+ dict.Set("chromeOptions", options);
+
+ options->SetString("binary", "binary");
+ options->SetString("channel", "channel");
+ options->SetBoolean("detach", true);
+ options->SetBoolean("loadAsync", true);
+ options->SetBoolean("nativeEvents", true);
+ options->SetBoolean("verbose", true);
+
+ Capabilities caps;
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ CapabilitiesParser parser(&dict, temp_dir.path(), &caps);
+ ASSERT_FALSE(parser.Parse());
+ EXPECT_EQ(FILE_PATH_LITERAL("binary"), caps.command.GetProgram().value());
+ EXPECT_STREQ("channel", caps.channel.c_str());
+ EXPECT_TRUE(caps.detach);
+ EXPECT_TRUE(caps.load_async);
+ EXPECT_TRUE(caps.native_events);
+ EXPECT_TRUE(caps.verbose);
+}
+
+TEST(CapabilitiesParser, Args) {
+ DictionaryValue dict;
+ DictionaryValue* options = new DictionaryValue();
+ dict.Set("chromeOptions", options);
+
+ ListValue* args = new ListValue();
+ args->Append(Value::CreateStringValue("arg1"));
+ args->Append(Value::CreateStringValue("arg2=val"));
+ args->Append(Value::CreateStringValue("arg3='a space'"));
+ options->Set("args", args);
+
+ Capabilities caps;
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ CapabilitiesParser parser(&dict, temp_dir.path(), &caps);
+ ASSERT_FALSE(parser.Parse());
+ EXPECT_TRUE(caps.command.HasSwitch("arg1"));
+ EXPECT_STREQ("val", caps.command.GetSwitchValueASCII("arg2").c_str());
+ EXPECT_STREQ("'a space'", caps.command.GetSwitchValueASCII("arg3").c_str());
+}
+
+TEST(CapabilitiesParser, Extensions) {
+ DictionaryValue dict;
+ DictionaryValue* options = new DictionaryValue();
+ dict.Set("chromeOptions", options);
+
+ ListValue* extensions = new ListValue();
+ extensions->Append(Value::CreateStringValue("TWFu"));
+ extensions->Append(Value::CreateStringValue("TWFuTWFu"));
+ options->Set("extensions", extensions);
+
+ Capabilities caps;
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ CapabilitiesParser parser(&dict, temp_dir.path(), &caps);
+ ASSERT_FALSE(parser.Parse());
+ ASSERT_EQ(2u, caps.extensions.size());
+ std::string contents;
+ ASSERT_TRUE(file_util::ReadFileToString(caps.extensions[0], &contents));
+ EXPECT_STREQ("Man", contents.c_str());
+ contents.clear();
+ ASSERT_TRUE(file_util::ReadFileToString(caps.extensions[1], &contents));
+ EXPECT_STREQ("ManMan", contents.c_str());
+}
+
+TEST(CapabilitiesParser, Profile) {
+ DictionaryValue dict;
+ DictionaryValue* options = new DictionaryValue();
+ dict.Set("chromeOptions", options);
+
+ ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ FilePath folder = temp_dir.path().AppendASCII("folder");
+ ASSERT_TRUE(file_util::CreateDirectory(folder));
+ ASSERT_EQ(4, file_util::WriteFile(
+ folder.AppendASCII("data"), "data", 4));
+ FilePath zip = temp_dir.path().AppendASCII("data.zip");
+ ASSERT_TRUE(zip::Zip(folder, zip, false /* include_hidden_files */));
+ std::string contents;
+ ASSERT_TRUE(file_util::ReadFileToString(zip, &contents));
+ std::string base64;
+ ASSERT_TRUE(base::Base64Encode(contents, &base64));
+ options->SetString("profile", base64);
+
+ Capabilities caps;
+ CapabilitiesParser parser(&dict, temp_dir.path(), &caps);
+ ASSERT_FALSE(parser.Parse());
+ std::string new_contents;
+ ASSERT_TRUE(file_util::ReadFileToString(
+ caps.profile.AppendASCII("data"), &new_contents));
+ EXPECT_STREQ("data", new_contents.c_str());
+}
+
+TEST(CapabilitiesParser, UnknownCap) {
+ Capabilities caps;
+ DictionaryValue dict;
+ dict.SetString("chromeOptions.nosuchcap", "none");
+ CapabilitiesParser parser(&dict, FilePath(), &caps);
+ ASSERT_FALSE(parser.Parse());
+}
+
+TEST(CapabilitiesParser, BadInput) {
+ Capabilities caps;
+ DictionaryValue dict;
+ dict.SetString("chromeOptions.verbose", "false");
+ CapabilitiesParser parser(&dict, FilePath(), &caps);
+ ASSERT_TRUE(parser.Parse());
+}
+
+} // namespace webdriver
diff --git a/chrome/test/webdriver/webdriver_util.cc b/chrome/test/webdriver/webdriver_util.cc
index 45e080f..5440d5a 100644
--- a/chrome/test/webdriver/webdriver_util.cc
+++ b/chrome/test/webdriver/webdriver_util.cc
@@ -7,9 +7,13 @@
#include "base/basictypes.h"
#include "base/format_macros.h"
#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
#include "base/rand_util.h"
#include "base/stringprintf.h"
-#include "base/values.h"
+
+using base::DictionaryValue;
+using base::ListValue;
+using base::Value;
namespace webdriver {
@@ -27,6 +31,91 @@ std::string JsonStringify(const Value* value) {
return json;
}
+namespace {
+
+// Truncates the given string to 40 characters, adding an ellipsis if
+// truncation was necessary.
+void TruncateString(std::string* data) {
+ if (data->length() > 40) {
+ data->resize(40);
+ data->replace(37, 3, "...");
+ }
+}
+
+// Truncates all strings contained in the given value.
+void TruncateContainedStrings(Value* value) {
+ ListValue* list;
+ if (value->IsType(Value::TYPE_DICTIONARY)) {
+ DictionaryValue* dict = static_cast<DictionaryValue*>(value);
+ DictionaryValue::key_iterator key = dict->begin_keys();
+ for (; key != dict->end_keys(); ++key) {
+ Value* child;
+ if (!dict->GetWithoutPathExpansion(*key, &child))
+ continue;
+ std::string data;
+ if (child->GetAsString(&data)) {
+ TruncateString(&data);
+ dict->SetWithoutPathExpansion(*key, Value::CreateStringValue(data));
+ } else {
+ TruncateContainedStrings(child);
+ }
+ }
+ } else if (value->GetAsList(&list)) {
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ Value* child;
+ if (!list->Get(i, &child))
+ continue;
+ std::string data;
+ if (child->GetAsString(&data)) {
+ TruncateString(&data);
+ list->Set(i, Value::CreateStringValue(data));
+ } else {
+ TruncateContainedStrings(child);
+ }
+ }
+ }
+}
+
+} // namespace
+
+std::string JsonStringifyForDisplay(const Value* value) {
+ scoped_ptr<Value> copy;
+ if (value->IsType(Value::TYPE_STRING)) {
+ std::string data;
+ value->GetAsString(&data);
+ TruncateString(&data);
+ copy.reset(Value::CreateStringValue(data));
+ } else {
+ copy.reset(value->DeepCopy());
+ TruncateContainedStrings(copy.get());
+ }
+ std::string json;
+ base::JSONWriter::Write(copy.get(), true /* pretty_print */, &json);
+ return json;
+}
+
+const char* GetJsonTypeName(Value::Type type) {
+ switch (type) {
+ case Value::TYPE_NULL:
+ return "null";
+ case Value::TYPE_BOOLEAN:
+ return "boolean";
+ case Value::TYPE_INTEGER:
+ return "integer";
+ case Value::TYPE_DOUBLE:
+ return "double";
+ case Value::TYPE_STRING:
+ return "string";
+ case Value::TYPE_BINARY:
+ return "binary";
+ case Value::TYPE_DICTIONARY:
+ return "dictionary";
+ case Value::TYPE_LIST:
+ return "list";
+ }
+ return "unknown";
+}
+
ValueParser::ValueParser() { }
ValueParser::~ValueParser() { }
@@ -34,11 +123,11 @@ ValueParser::~ValueParser() { }
} // namespace webdriver
bool ValueConversionTraits<webdriver::SkipParsing>::SetFromValue(
- const base::Value* value, const webdriver::SkipParsing* t) {
+ const Value* value, const webdriver::SkipParsing* t) {
return true;
}
bool ValueConversionTraits<webdriver::SkipParsing>::CanConvert(
- const base::Value* value) {
+ const Value* value) {
return true;
}
diff --git a/chrome/test/webdriver/webdriver_util.h b/chrome/test/webdriver/webdriver_util.h
index e36bdb7..c512ba5 100644
--- a/chrome/test/webdriver/webdriver_util.h
+++ b/chrome/test/webdriver/webdriver_util.h
@@ -11,14 +11,11 @@
#include "base/basictypes.h"
#include "base/compiler_specific.h"
+#include "base/values.h"
#include "chrome/test/automation/value_conversion_traits.h"
class FilePath;
-namespace base {
-class Value;
-}
-
namespace webdriver {
// Generates a random, 32-character hexidecimal ID.
@@ -27,6 +24,13 @@ std::string GenerateRandomID();
// Returns the equivalent JSON string for the given value.
std::string JsonStringify(const base::Value* value);
+// Returns the JSON string for the given value, with the exception that
+// long strings are shortened for easier display.
+std::string JsonStringifyForDisplay(const base::Value* value);
+
+// Returns the string representation of the given type, for display purposes.
+const char* GetJsonTypeName(base::Value::Type type);
+
#if defined(OS_MACOSX)
// Gets the paths to the user and local application directory.
void GetApplicationDirs(std::vector<FilePath>* app_dirs);