Commit 3f5bda7d authored by chrisgao@chromium.org's avatar chrisgao@chromium.org

[chromedriver] Implement proxy capability.

Added proxy as switches as arguments passed to command line.
BUG=chromedriver:287
NOTRY=true

Review URL: https://chromiumcodereview.appspot.com/13185004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@192836 0039d316-1c4b-4281-b951-d872f2087c98
parent 7f5605c1
......@@ -914,6 +914,8 @@
'../third_party/webdriver/atoms.h',
'test/chromedriver/basic_types.cc',
'test/chromedriver/basic_types.h',
'test/chromedriver/capabilities.cc',
'test/chromedriver/capabilities.h',
'test/chromedriver/chrome_launcher.cc',
'test/chromedriver/chrome_launcher.h',
'test/chromedriver/chromedriver.cc',
......@@ -1016,6 +1018,7 @@
'../ui/ui.gyp:ui',
],
'sources': [
'test/chromedriver/capabilities_unittest.cc',
'test/chromedriver/chrome/chrome_finder_unittest.cc',
'test/chromedriver/chrome/devtools_client_impl_unittest.cc',
'test/chromedriver/chrome/devtools_http_client_unittest.cc',
......
// Copyright (c) 2013 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/capabilities.h"
#include <map>
#include "base/bind.h"
#include "base/callback.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/values.h"
#include "chrome/test/chromedriver/chrome/status.h"
namespace {
typedef base::Callback<Status(const base::Value&, Capabilities*)> Parser;
Status ParseChromeBinary(
const base::Value& option,
Capabilities* capabilities) {
base::FilePath::StringType path_str;
if (!option.GetAsString(&path_str))
return Status(kUnknownError, "'binary' must be a string");
base::FilePath chrome_exe(path_str);
capabilities->command.SetProgram(chrome_exe);
return Status(kOk);
}
Status ParseLogPath(const base::Value& option, Capabilities* capabilities) {
if (!option.GetAsString(&capabilities->log_path))
return Status(kUnknownError, "'logPath' must be a string");
return Status(kOk);
}
Status ParseArgs(const base::Value& option, Capabilities* capabilities) {
const base::ListValue* args_list = NULL;
if (!option.GetAsList(&args_list))
return Status(kUnknownError, "'args' must be a list");
for (size_t i = 0; i < args_list->GetSize(); ++i) {
std::string arg_string;
if (!args_list->GetString(i, &arg_string))
return Status(kUnknownError, "each argument must be a string");
size_t separator_index = arg_string.find("=");
if (separator_index != std::string::npos) {
CommandLine::StringType arg_string_native;
if (!args_list->GetString(i, &arg_string_native))
return Status(kUnknownError, "each argument must be a string");
capabilities->command.AppendSwitchNative(
arg_string.substr(0, separator_index),
arg_string_native.substr(separator_index + 1));
} else {
capabilities->command.AppendSwitch(arg_string);
}
}
return Status(kOk);
}
Status ParsePrefs(const base::Value& option, Capabilities* capabilities) {
const base::DictionaryValue* prefs = NULL;
if (!option.GetAsDictionary(&prefs))
return Status(kUnknownError, "'prefs' must be a dictionary");
capabilities->prefs.reset(prefs->DeepCopy());
return Status(kOk);
}
Status ParseLocalState(const base::Value& option, Capabilities* capabilities) {
const base::DictionaryValue* local_state = NULL;
if (!option.GetAsDictionary(&local_state))
return Status(kUnknownError, "'localState' must be a dictionary");
capabilities->local_state.reset(local_state->DeepCopy());
return Status(kOk);
}
Status ParseExtensions(const base::Value& option, Capabilities* capabilities) {
const base::ListValue* extensions = NULL;
if (!option.GetAsList(&extensions))
return Status(kUnknownError, "'extensions' must be a list");
for (size_t i = 0; i < extensions->GetSize(); ++i) {
std::string extension;
if (!extensions->GetString(i, &extension)) {
return Status(kUnknownError,
"each extension must be a base64 encoded string");
}
capabilities->extensions.push_back(extension);
}
return Status(kOk);
}
Status ParseProxy(const base::Value& option, Capabilities* capabilities) {
const base::DictionaryValue* proxy_dict;
if (!option.GetAsDictionary(&proxy_dict))
return Status(kUnknownError, "'proxy' must be a dictionary");
std::string proxy_type;
if (!proxy_dict->GetString("proxyType", &proxy_type))
return Status(kUnknownError, "'proxyType' must be a string");
proxy_type = StringToLowerASCII(proxy_type);
if (proxy_type == "direct") {
capabilities->command.AppendSwitch("no-proxy-server");
} else if (proxy_type == "system") {
// Chrome default.
} else if (proxy_type == "pac") {
CommandLine::StringType proxy_pac_url;
if (!proxy_dict->GetString("proxyAutoconfigUrl", &proxy_pac_url))
return Status(kUnknownError, "'proxyAutoconfigUrl' must be a string");
capabilities->command.AppendSwitchNative("proxy-pac-url", proxy_pac_url);
} else if (proxy_type == "autodetect") {
capabilities->command.AppendSwitch("proxy-auto-detect");
} else if (proxy_type == "manual") {
const char* proxy_servers_options[][2] = {
{"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}};
std::string proxy_servers;
for (size_t i = 0; i < arraysize(proxy_servers_options); ++i) {
if (!proxy_dict->HasKey(proxy_servers_options[i][0]))
continue;
std::string value;
if (!proxy_dict->GetString(proxy_servers_options[i][0], &value)) {
return Status(
kUnknownError,
base::StringPrintf("'%s' must be a string",
proxy_servers_options[i][0]));
}
// Converts into Chrome proxy scheme.
// Example: "http=localhost:9000;ftp=localhost:8000".
if (!proxy_servers.empty())
proxy_servers += ";";
proxy_servers += base::StringPrintf(
"%s=%s", proxy_servers_options[i][1], value.c_str());
}
std::string proxy_bypass_list;
if (proxy_dict->HasKey("noProxy")) {
if (!proxy_dict->GetString("noProxy", &proxy_bypass_list))
return Status(kUnknownError, "'noProxy' must be a string");
}
if (proxy_servers.empty() && proxy_bypass_list.empty()) {
return Status(kUnknownError,
"proxyType is 'manual' but no manual "
"proxy capabilities were found");
}
if (!proxy_servers.empty())
capabilities->command.AppendSwitchASCII("proxy-server", proxy_servers);
if (!proxy_bypass_list.empty()) {
capabilities->command.AppendSwitchASCII("proxy-bypass-list",
proxy_bypass_list);
}
} else {
return Status(kUnknownError, "unrecognized proxy type:" + proxy_type);
}
return Status(kOk);
}
Status ParseDesktopChromeOption(
const base::Value& capability,
Capabilities* capabilities) {
const base::DictionaryValue* chrome_options = NULL;
if (!capability.GetAsDictionary(&chrome_options))
return Status(kUnknownError, "'chromeOptions' must be a dictionary");
std::map<std::string, Parser> parser_map;
parser_map["binary"] = base::Bind(&ParseChromeBinary);
parser_map["logPath"] = base::Bind(&ParseLogPath);
parser_map["args"] = base::Bind(&ParseArgs);
parser_map["prefs"] = base::Bind(&ParsePrefs);
parser_map["localState"] = base::Bind(&ParseLocalState);
parser_map["extensions"] = base::Bind(&ParseExtensions);
for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd();
it.Advance()) {
if (parser_map.find(it.key()) == parser_map.end()) {
return Status(kUnknownError,
"unrecognized chrome option: " + it.key());
}
Status status = parser_map[it.key()].Run(it.value(), capabilities);
if (status.IsError())
return status;
}
return Status(kOk);
}
Status ParseAndroidChromeCapabilities(const base::DictionaryValue& desired_caps,
Capabilities* capabilities) {
const base::Value* chrome_options = NULL;
if (desired_caps.Get("chromeOptions", &chrome_options)) {
const base::DictionaryValue* chrome_options_dict = NULL;
if (!chrome_options->GetAsDictionary(&chrome_options_dict))
return Status(kUnknownError, "'chromeOptions' must be a dictionary");
const base::Value* android_package_value;
if (chrome_options_dict->Get("android_package", &android_package_value)) {
if (!android_package_value->GetAsString(&capabilities->android_package) ||
capabilities->android_package.empty()) {
return Status(kUnknownError,
"'android_package' must be a non-empty string");
}
}
}
return Status(kOk);
}
} // namespace
Capabilities::Capabilities() : command(CommandLine::NO_PROGRAM) {}
Capabilities::~Capabilities() {}
bool Capabilities::IsAndroid() const {
return !android_package.empty();
}
Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
Status status = ParseAndroidChromeCapabilities(desired_caps, this);
if (status.IsError())
return status;
if (IsAndroid())
return Status(kOk);
std::map<std::string, Parser> parser_map;
parser_map["proxy"] = base::Bind(&ParseProxy);
parser_map["chromeOptions"] = base::Bind(&ParseDesktopChromeOption);
for (std::map<std::string, Parser>::iterator it = parser_map.begin();
it != parser_map.end(); ++it) {
const base::Value* capability = NULL;
if (desired_caps.Get(it->first, &capability)) {
status = it->second.Run(*capability, this);
if (status.IsError())
return status;
}
}
return Status(kOk);
}
// Copyright (c) 2013 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_CAPABILITIES_H_
#define CHROME_TEST_CHROMEDRIVER_CAPABILITIES_H_
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
namespace base {
class DictionaryValue;
}
class Status;
struct Capabilities {
Capabilities();
~Capabilities();
// Return true if android package is specified.
bool IsAndroid() const;
Status Parse(const base::DictionaryValue& desired_caps);
std::string android_package;
std::string log_path;
CommandLine command;
scoped_ptr<base::DictionaryValue> prefs;
scoped_ptr<base::DictionaryValue> local_state;
std::vector<std::string> extensions;
};
#endif // CHROME_TEST_CHROMEDRIVER_CAPABILITIES_H_
// Copyright (c) 2013 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/capabilities.h"
#include "base/values.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(ParseCapabilities, WithAndroidPackage) {
Capabilities capabilities;
base::DictionaryValue caps;
caps.SetString("chromeOptions.android_package", "abc");
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(capabilities.IsAndroid());
ASSERT_EQ("abc", capabilities.android_package);
}
TEST(ParseCapabilities, EmptyAndroidPackage) {
Capabilities capabilities;
base::DictionaryValue caps;
caps.SetString("chromeOptions.android_package", "");
Status status = capabilities.Parse(caps);
ASSERT_FALSE(status.IsOk());
}
TEST(ParseCapabilities, IllegalAndroidPackage) {
Capabilities capabilities;
base::DictionaryValue caps;
caps.SetInteger("chromeOptions.android_package", 123);
Status status = capabilities.Parse(caps);
ASSERT_FALSE(status.IsOk());
}
TEST(ParseCapabilities, LogPath) {
Capabilities capabilities;
base::DictionaryValue caps;
caps.SetString("chromeOptions.logPath", "path/to/logfile");
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_STREQ("path/to/logfile", capabilities.log_path.c_str());
}
TEST(ParseCapabilities, NoArgs) {
Capabilities capabilities;
base::ListValue args;
ASSERT_TRUE(args.empty());
base::DictionaryValue caps;
caps.Set("chromeOptions.args", args.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(capabilities.command.GetSwitches().empty());
}
TEST(ParseCapabilities, SingleArgWithoutValue) {
Capabilities capabilities;
base::ListValue args;
args.AppendString("enable-nacl");
ASSERT_EQ(1u, args.GetSize());
base::DictionaryValue caps;
caps.Set("chromeOptions.args", args.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(1u, capabilities.command.GetSwitches().size());
ASSERT_TRUE(capabilities.command.HasSwitch("enable-nacl"));
}
TEST(ParseCapabilities, SingleArgWithValue) {
Capabilities capabilities;
base::ListValue args;
args.AppendString("load-extension=/test/extension");
ASSERT_EQ(1u, args.GetSize());
base::DictionaryValue caps;
caps.Set("chromeOptions.args", args.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(1u, capabilities.command.GetSwitches().size());
ASSERT_TRUE(capabilities.command.HasSwitch("load-extension"));
ASSERT_STREQ(
"/test/extension",
capabilities.command.GetSwitchValueASCII("load-extension").c_str());
}
TEST(ParseCapabilities, MultipleArgs) {
Capabilities capabilities;
base::ListValue args;
args.AppendString("arg1");
args.AppendString("arg2=val");
args.AppendString("arg3='a space'");
ASSERT_EQ(3u, args.GetSize());
base::DictionaryValue caps;
caps.Set("chromeOptions.args", args.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(3u, capabilities.command.GetSwitches().size());
ASSERT_TRUE(capabilities.command.HasSwitch("arg1"));
ASSERT_TRUE(capabilities.command.HasSwitch("arg2"));
ASSERT_STREQ("val", capabilities.command.GetSwitchValueASCII("arg2").c_str());
ASSERT_TRUE(capabilities.command.HasSwitch("arg3"));
ASSERT_STREQ("'a space'",
capabilities.command.GetSwitchValueASCII("arg3").c_str());
}
TEST(ParseCapabilities, Prefs) {
Capabilities capabilities;
base::DictionaryValue prefs;
prefs.SetString("key1", "value1");
prefs.SetString("key2.k", "value2");
base::DictionaryValue caps;
caps.Set("chromeOptions.prefs", prefs.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(capabilities.prefs->Equals(&prefs));
}
TEST(ParseCapabilities, LocalState) {
Capabilities capabilities;
base::DictionaryValue local_state;
local_state.SetString("s1", "v1");
local_state.SetString("s2.s", "v2");
base::DictionaryValue caps;
caps.Set("chromeOptions.localState", local_state.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(capabilities.local_state->Equals(&local_state));
}
TEST(ParseCapabilities, Extensions) {
Capabilities capabilities;
base::ListValue extensions;
extensions.AppendString("ext1");
extensions.AppendString("ext2");
base::DictionaryValue caps;
caps.Set("chromeOptions.extensions", extensions.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(2u, capabilities.extensions.size());
ASSERT_EQ("ext1", capabilities.extensions[0]);
ASSERT_EQ("ext2", capabilities.extensions[1]);
}
TEST(ParseCapabilities, UnrecognizedProxyType) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "unknown proxy type");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_FALSE(status.IsOk());
}
TEST(ParseCapabilities, IllegalProxyType) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetInteger("proxyType", 123);
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_FALSE(status.IsOk());
}
TEST(ParseCapabilities, DirectProxy) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "DIRECT");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(1u, capabilities.command.GetSwitches().size());
ASSERT_TRUE(capabilities.command.HasSwitch("no-proxy-server"));
}
TEST(ParseCapabilities, SystemProxy) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "system");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(capabilities.command.GetSwitches().empty());
}
TEST(ParseCapabilities, PacProxy) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "PAC");
proxy.SetString("proxyAutoconfigUrl", "test.wpad");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(1u, capabilities.command.GetSwitches().size());
ASSERT_STREQ(
"test.wpad",
capabilities.command.GetSwitchValueASCII("proxy-pac-url").c_str());
}
TEST(ParseCapabilities, MissingProxyAutoconfigUrl) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "PAC");
proxy.SetString("httpProxy", "http://localhost:8001");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_FALSE(status.IsOk());
}
TEST(ParseCapabilities, AutodetectProxy) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "autodetect");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(1u, capabilities.command.GetSwitches().size());
ASSERT_TRUE(capabilities.command.HasSwitch("proxy-auto-detect"));
}
TEST(ParseCapabilities, ManualProxy) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "manual");
proxy.SetString("ftpProxy", "localhost:9001");
proxy.SetString("httpProxy", "localhost:8001");
proxy.SetString("sslProxy", "localhost:10001");
proxy.SetString("noProxy", "google.com, youtube.com");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(2u, capabilities.command.GetSwitches().size());
ASSERT_STREQ(
"ftp=localhost:9001;http=localhost:8001;https=localhost:10001",
capabilities.command.GetSwitchValueASCII("proxy-server").c_str());
ASSERT_STREQ(
"google.com, youtube.com",
capabilities.command.GetSwitchValueASCII("proxy-bypass-list").c_str());
}
TEST(ParseCapabilities, MissingSettingForManualProxy) {
Capabilities capabilities;
base::DictionaryValue proxy;
proxy.SetString("proxyType", "manual");
base::DictionaryValue caps;
caps.Set("proxy", proxy.DeepCopy());
Status status = capabilities.Parse(caps);
ASSERT_FALSE(status.IsOk());
}
......@@ -56,40 +56,36 @@ Status UnpackAutomationExtension(const base::FilePath& temp_dir,
}
Status PrepareCommandLine(int port,
const base::FilePath& exe,
const base::ListValue* args,
const base::ListValue* extensions,
const base::DictionaryValue* prefs,
const base::DictionaryValue* local_state,
const Capabilities& capabilities,
CommandLine* prepared_command,
base::ScopedTempDir* user_data_dir,
base::ScopedTempDir* extension_dir) {
base::FilePath program = exe;
CommandLine command = capabilities.command;
base::FilePath program = command.GetProgram();
if (program.empty()) {
if (!FindChrome(&program))
return Status(kUnknownError, "cannot find Chrome binary");
command.SetProgram(program);
} else if (!file_util::PathExists(program)) {
return Status(kUnknownError,
base::StringPrintf("no chrome binary at %" PRFilePath,
program.value().c_str()));
}
LOG(INFO) << "Using chrome from " << program.value();
CommandLine command(program);
command.AppendSwitchASCII("remote-debugging-port", base::IntToString(port));
command.AppendSwitch("no-first-run");
command.AppendSwitch("enable-logging");
command.AppendSwitchASCII("logging-level", "1");
command.AppendArg("data:text/html;charset=utf-8,");
if (args) {
Status status = internal::ProcessCommandLineArgs(args, &command);
if (status.IsError())
return status;
}
if (!command.HasSwitch("user-data-dir")) {
if (!user_data_dir->CreateUniqueTempDir())
return Status(kUnknownError, "cannot create temp dir for user data dir");
command.AppendSwitchPath("user-data-dir", user_data_dir->path());
Status status = internal::PrepareUserDataDir(
user_data_dir->path(), prefs, local_state);
user_data_dir->path(), capabilities.prefs.get(),
capabilities.local_state.get());
if (status.IsError())
return status;
}
......@@ -99,7 +95,7 @@ Status PrepareCommandLine(int port,
"cannot create temp dir for unpacking extensions");
}
Status status = internal::ProcessExtensions(
extensions, extension_dir->path(), true, &command);
capabilities.extensions, extension_dir->path(), true, &command);
if (status.IsError())
return status;
......@@ -181,30 +177,24 @@ Status WaitForDevToolsAndCheckVersion(
return Status(kUnknownError, "unable to discover open pages");
}
} // namespace
Status LaunchDesktopChrome(URLRequestContextGetter* context_getter,
int port,
const SyncWebSocketFactory& socket_factory,
const base::FilePath& exe,
const base::ListValue* args,
const base::ListValue* extensions,
const base::DictionaryValue* prefs,
const base::DictionaryValue* local_state,
const std::string& log_path,
const Capabilities& capabilities,
scoped_ptr<Chrome>* chrome) {
CommandLine command(CommandLine::NO_PROGRAM);
base::ScopedTempDir user_data_dir;
base::ScopedTempDir extension_dir;
PrepareCommandLine(port, exe, args, extensions, prefs, local_state,
PrepareCommandLine(port, capabilities,
&command, &user_data_dir, &extension_dir);
base::LaunchOptions options;
#if !defined(OS_WIN)
base::EnvironmentVector environ;
if (!log_path.empty()) {
environ.push_back(base::EnvironmentVector::value_type("CHROME_LOG_FILE",
log_path));
if (!capabilities.log_path.empty()) {
environ.push_back(
base::EnvironmentVector::value_type("CHROME_LOG_FILE",
capabilities.log_path));
options.environ = &environ;
}
#endif
......@@ -263,13 +253,13 @@ Status LaunchDesktopChrome(URLRequestContextGetter* context_getter,
Status LaunchAndroidChrome(URLRequestContextGetter* context_getter,
int port,
const SyncWebSocketFactory& socket_factory,
const std::string& package_name,
const Capabilities& capabilities,
scoped_ptr<Chrome>* chrome) {
// TODO(frankf): Figure out how this should be installed to
// make this work for all platforms.
base::FilePath adb_commands(FILE_PATH_LITERAL("adb_commands.py"));
CommandLine command(adb_commands);
command.AppendSwitchASCII("package", package_name);
command.AppendSwitchASCII("package", capabilities.android_package);
command.AppendSwitch("launch");
command.AppendSwitchASCII("port", base::IntToString(port));
......@@ -297,46 +287,38 @@ Status LaunchAndroidChrome(URLRequestContextGetter* context_getter,
return Status(kOk);
}
namespace internal {
} // namespace
Status ProcessCommandLineArgs(const base::ListValue* args,
CommandLine* command) {
for (size_t i = 0; i < args->GetSize(); ++i) {
std::string arg_string;
if (!args->GetString(i, &arg_string))
return Status(kUnknownError, "invalid chrome command line argument");
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 Status(kUnknownError, "invalid chrome command line argument");
command->AppendSwitchNative(
arg_string.substr(0, separator_index),
arg_string_native.substr(separator_index + 1));
} else {
command->AppendSwitch(arg_string);
}
Status LaunchChrome(URLRequestContextGetter* context_getter,
int port,
const SyncWebSocketFactory& socket_factory,
const Capabilities& capabilities,
scoped_ptr<Chrome>* chrome) {
if (capabilities.IsAndroid()) {
return LaunchAndroidChrome(
context_getter, port, socket_factory, capabilities, chrome);
} else {
return LaunchDesktopChrome(
context_getter, port, socket_factory, capabilities, chrome);
}
return Status(kOk);
}
Status ProcessExtensions(const base::ListValue* extensions,
namespace internal {
Status ProcessExtensions(const std::vector<std::string>& extensions,
const base::FilePath& temp_dir,
bool include_automation_extension,
CommandLine* command) {
std::vector<base::FilePath::StringType> extension_paths;
for (size_t i = 0; i < (extensions ? extensions->GetSize() : 0); ++i) {
size_t count = 0;
for (std::vector<std::string>::const_iterator it = extensions.begin();
it != extensions.end(); ++it) {
std::string extension_base64;
if (!extensions->GetString(i, &extension_base64)) {
return Status(kUnknownError,
"each extension must be a base64 encoded string");
}
// Decodes extension string.
// Some WebDriver client base64 encoders follow RFC 1521, which require that
// 'encoded lines be no more than 76 characters long'. Just remove any
// newlines.
RemoveChars(extension_base64, "\n", &extension_base64);
RemoveChars(*it, "\n", &extension_base64);
std::string decoded_extension;
if (!base::Base64Decode(extension_base64, &decoded_extension))
return Status(kUnknownError, "failed to base64 decode extension");
......@@ -349,12 +331,14 @@ Status ProcessExtensions(const base::ListValue* extensions,
base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
int size = static_cast<int>(decoded_extension.length());
if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size)
!= size)
!= size) {
return Status(kUnknownError, "failed to write extension file");
}
// Unzips the temporary .crx file.
count++;
base::FilePath extension_dir = temp_dir.AppendASCII(
base::StringPrintf("extension%" PRIuS, i));
base::StringPrintf("extension%" PRIuS, count));
if (!zip::Unzip(extension_crx, extension_dir))
return Status(kUnknownError, "failed to unzip the extension CRX file");
extension_paths.push_back(extension_dir.value());
......
......@@ -6,9 +6,11 @@
#define CHROME_TEST_CHROMEDRIVER_CHROME_LAUNCHER_H_
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/test/chromedriver/capabilities.h"
#include "chrome/test/chromedriver/net/sync_websocket_factory.h"
class CommandLine;
......@@ -16,34 +18,20 @@ class CommandLine;
namespace base {
class DictionaryValue;
class FilePath;
class ListValue;
}
class Chrome;
class Status;
class URLRequestContextGetter;
Status LaunchDesktopChrome(URLRequestContextGetter* context_getter,
int port,
const SyncWebSocketFactory& socket_factory,
const base::FilePath& exe,
const base::ListValue* args,
const base::ListValue* extensions,
const base::DictionaryValue* prefs,
const base::DictionaryValue* local_state,
const std::string& log_path,
scoped_ptr<Chrome>* chrome);
Status LaunchAndroidChrome(URLRequestContextGetter* context_getter,
int port,
const SyncWebSocketFactory& socket_factory,
const std::string& package_name,
scoped_ptr<Chrome>* chrome);
Status LaunchChrome(URLRequestContextGetter* context_getter,
int port,
const SyncWebSocketFactory& socket_factory,
const Capabilities& capabilities,
scoped_ptr<Chrome>* chrome);
namespace internal {
Status ProcessCommandLineArgs(const base::ListValue* args,
CommandLine* command);
Status ProcessExtensions(const base::ListValue* extensions,
Status ProcessExtensions(const std::vector<std::string>& extensions,
const base::FilePath& temp_dir,
bool include_automation_extension,
CommandLine* command);
......
......@@ -15,69 +15,11 @@
#include "chrome/test/chromedriver/chrome_launcher.h"
#include "testing/gtest/include/gtest/gtest.h"
TEST(ProcessCommandLineArgs, NoArgs) {
CommandLine command(CommandLine::NO_PROGRAM);
base::ListValue switches;
ASSERT_TRUE(switches.empty());
Status status = internal::ProcessCommandLineArgs(&switches, &command);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(command.GetSwitches().empty());
}
TEST(ProcessCommandLineArgs, SingleArgWithoutValue) {
CommandLine command(CommandLine::NO_PROGRAM);
base::ListValue switches;
switches.AppendString("enable-nacl");
ASSERT_EQ(1u, switches.GetSize());
Status status = internal::ProcessCommandLineArgs(&switches, &command);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(1u, command.GetSwitches().size());
ASSERT_TRUE(command.HasSwitch("enable-nacl"));
}
TEST(ProcessCommandLineArgs, SingleArgWithValue) {
CommandLine command(CommandLine::NO_PROGRAM);
base::ListValue switches;
switches.AppendString("load-extension=/test/extension");
ASSERT_EQ(1u, switches.GetSize());
Status status = internal::ProcessCommandLineArgs(&switches, &command);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(1u, command.GetSwitches().size());
ASSERT_TRUE(command.HasSwitch("load-extension"));
ASSERT_EQ("/test/extension", command.GetSwitchValueASCII("load-extension"));
}
TEST(ProcessCommandLineArgs, MultipleArgs) {
CommandLine command(CommandLine::NO_PROGRAM);
base::ListValue switches;
switches.AppendString("disable-sync");
switches.AppendString("user-data-dir=/test/user/data");
ASSERT_EQ(2u, switches.GetSize());
Status status = internal::ProcessCommandLineArgs(&switches, &command);
ASSERT_TRUE(status.IsOk());
ASSERT_EQ(2u, command.GetSwitches().size());
ASSERT_TRUE(command.HasSwitch("disable-sync"));
ASSERT_TRUE(command.HasSwitch("user-data-dir"));
ASSERT_EQ("/test/user/data", command.GetSwitchValueASCII("user-data-dir"));
}
TEST(ProcessExtensions, AutomationExtension) {
CommandLine command(CommandLine::NO_PROGRAM);
base::ListValue extensions;
base::FilePath extension_dir;
Status status = internal::ProcessExtensions(&extensions, extension_dir,
true, &command);
ASSERT_TRUE(status.IsOk()) << status.message();
ASSERT_TRUE(command.HasSwitch("load-extension"));
base::FilePath temp_ext_path = command.GetSwitchValuePath("load-extension");
ASSERT_TRUE(file_util::PathExists(temp_ext_path));
}
TEST(ProcessExtensions, NoExtension) {
CommandLine command(CommandLine::NO_PROGRAM);
base::ListValue extensions;
std::vector<std::string> extensions;
base::FilePath extension_dir;
Status status = internal::ProcessExtensions(&extensions, extension_dir,
Status status = internal::ProcessExtensions(extensions, extension_dir,
false, &command);
ASSERT_TRUE(status.IsOk());
ASSERT_FALSE(command.HasSwitch("load-extension"));
......@@ -91,16 +33,16 @@ TEST(ProcessExtensions, SingleExtension) {
std::string crx_contents;
ASSERT_TRUE(file_util::ReadFileToString(crx_file_path, &crx_contents));
base::ListValue extensions;
std::vector<std::string> extensions;
std::string crx_encoded;
ASSERT_TRUE(base::Base64Encode(crx_contents, &crx_encoded));
extensions.AppendString(crx_encoded);
extensions.push_back(crx_encoded);
base::ScopedTempDir extension_dir;
ASSERT_TRUE(extension_dir.CreateUniqueTempDir());
CommandLine command(CommandLine::NO_PROGRAM);
Status status = internal::ProcessExtensions(&extensions, extension_dir.path(),
Status status = internal::ProcessExtensions(extensions, extension_dir.path(),
false, &command);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(command.HasSwitch("load-extension"));
......@@ -120,18 +62,18 @@ TEST(ProcessExtensions, MultipleExtensions) {
ASSERT_TRUE(file_util::ReadFileToString(test_crx_1, &crx_1_contents));
ASSERT_TRUE(file_util::ReadFileToString(test_crx_2, &crx_2_contents));
base::ListValue extensions;
std::vector<std::string> extensions;
std::string crx_1_encoded, crx_2_encoded;
ASSERT_TRUE(base::Base64Encode(crx_1_contents, &crx_1_encoded));
ASSERT_TRUE(base::Base64Encode(crx_2_contents, &crx_2_encoded));
extensions.AppendString(crx_1_encoded);
extensions.AppendString(crx_2_encoded);
extensions.push_back(crx_1_encoded);
extensions.push_back(crx_2_encoded);
base::ScopedTempDir extension_dir;
ASSERT_TRUE(extension_dir.CreateUniqueTempDir());
CommandLine command(CommandLine::NO_PROGRAM);
Status status = internal::ProcessExtensions(&extensions, extension_dir.path(),
Status status = internal::ProcessExtensions(extensions, extension_dir.path(),
false, &command);
ASSERT_TRUE(status.IsOk());
ASSERT_TRUE(command.HasSwitch("load-extension"));
......
......@@ -4,11 +4,10 @@
#include "chrome/test/chromedriver/commands.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/stringprintf.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "chrome/test/chromedriver/capabilities.h"
#include "chrome/test/chromedriver/chrome/chrome.h"
#include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
#include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
......@@ -58,75 +57,19 @@ Status ExecuteNewSession(
if (!params.GetDictionary("desiredCapabilities", &desired_caps))
return Status(kUnknownError, "cannot find dict 'desiredCapabilities'");
scoped_ptr<Chrome> chrome;
std::string android_package;
if (desired_caps->GetString("chromeOptions.android_package",
&android_package)) {
Status status = LaunchAndroidChrome(
context_getter, port, socket_factory, android_package, &chrome);
if (status.IsError())
return status;
} else {
base::FilePath::StringType path_str;
base::FilePath chrome_exe;
if (desired_caps->GetString("chromeOptions.binary", &path_str)) {
chrome_exe = base::FilePath(path_str);
if (!file_util::PathExists(chrome_exe)) {
std::string message = base::StringPrintf(
"no chrome binary at %" PRFilePath,
path_str.c_str());
return Status(kUnknownError, message);
}
}
const base::Value* args = NULL;
const base::ListValue* args_list = NULL;
if (desired_caps->Get("chromeOptions.args", &args) &&
!args->GetAsList(&args_list)) {
return Status(kUnknownError,
"command line arguments for chrome must be a list");
}
const base::Value* prefs = NULL;
const base::DictionaryValue* prefs_dict = NULL;
if (desired_caps->Get("chromeOptions.prefs", &prefs) &&
!prefs->GetAsDictionary(&prefs_dict)) {
return Status(kUnknownError, "'prefs' must be a dictionary");
}
const base::Value* local_state = NULL;
const base::DictionaryValue* local_state_dict = NULL;
if (desired_caps->Get("chromeOptions.localState", &local_state) &&
!prefs->GetAsDictionary(&prefs_dict)) {
return Status(kUnknownError, "'localState' must be a dictionary");
}
Capabilities capabilities;
Status status = capabilities.Parse(*desired_caps);
if (status.IsError())
return status;
const base::Value* extensions = NULL;
const base::ListValue* extensions_list = NULL;
if (desired_caps->Get("chromeOptions.extensions", &extensions)
&& !extensions->GetAsList(&extensions_list)) {
return Status(kUnknownError,
"chrome extensions must be a list");
}
const base::Value* log_path = NULL;
std::string chrome_log_path;
if (desired_caps->Get("chromeOptions.logPath", &log_path) &&
!log_path->GetAsString(&chrome_log_path)) {
return Status(kUnknownError,
"chrome log path must be a string");
}
Status status = LaunchDesktopChrome(
context_getter, port, socket_factory,
chrome_exe, args_list, extensions_list,
prefs_dict, local_state_dict, chrome_log_path, &chrome);
if (status.IsError())
return status;
}
scoped_ptr<Chrome> chrome;
status = LaunchChrome(context_getter, port, socket_factory,
capabilities, &chrome);
if (status.IsError())
return status;
std::list<std::string> web_view_ids;
Status status = chrome->GetWebViewIds(&web_view_ids);
status = chrome->GetWebViewIds(&web_view_ids);
if (status.IsError() || web_view_ids.empty()) {
chrome->Quit();
return status.IsError() ? status :
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment