diff options
author | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-27 21:39:25 +0000 |
---|---|---|
committer | kkania@chromium.org <kkania@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-27 21:39:25 +0000 |
commit | 582b55d4a0ee1c0ceea48b1b337b4b71597f9fe0 (patch) | |
tree | 3da47bb3e93368d73e9767af039341dd1db244e6 | |
parent | 91a5ded4aa5088c26916a5eb81d52ff7d1cc1821 (diff) | |
download | chromium_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.gypi | 3 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/create_session.cc | 196 | ||||
-rw-r--r-- | chrome/test/webdriver/commands/session_with_id.cc | 13 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_capabilities_parser.cc | 240 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_capabilities_parser.h | 109 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_capabilities_parser_unittest.cc | 141 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_util.cc | 95 | ||||
-rw-r--r-- | chrome/test/webdriver/webdriver_util.h | 12 |
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); |