1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/test/chromedriver/capabilities.h"
10 #include "base/callback.h"
11 #include "base/json/string_escape.h"
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_tokenizer.h"
16 #include "base/strings/string_util.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/values.h"
20 #include "chrome/test/chromedriver/chrome/mobile_device.h"
21 #include "chrome/test/chromedriver/chrome/status.h"
22 #include "chrome/test/chromedriver/logging.h"
23 #include "net/base/net_util.h"
27 typedef base::Callback<Status(const base::Value&, Capabilities*)> Parser;
31 const base::Value& option,
32 Capabilities* capabilities) {
33 if (!option.GetAsBoolean(to_set))
34 return Status(kUnknownError, "must be a boolean");
38 Status ParseString(std::string* to_set,
39 const base::Value& option,
40 Capabilities* capabilities) {
42 if (!option.GetAsString(&str))
43 return Status(kUnknownError, "must be a string");
45 return Status(kUnknownError, "cannot be empty");
50 Status ParseFilePath(base::FilePath* to_set,
51 const base::Value& option,
52 Capabilities* capabilities) {
53 base::FilePath::StringType str;
54 if (!option.GetAsString(&str))
55 return Status(kUnknownError, "must be a string");
56 *to_set = base::FilePath(str);
60 Status ParseDict(scoped_ptr<base::DictionaryValue>* to_set,
61 const base::Value& option,
62 Capabilities* capabilities) {
63 const base::DictionaryValue* dict = NULL;
64 if (!option.GetAsDictionary(&dict))
65 return Status(kUnknownError, "must be a dictionary");
66 to_set->reset(dict->DeepCopy());
70 Status IgnoreDeprecatedOption(
71 const char* option_name,
72 const base::Value& option,
73 Capabilities* capabilities) {
74 LOG(WARNING) << "Deprecated chrome option is ignored: " << option_name;
78 Status IgnoreCapability(const base::Value& option, Capabilities* capabilities) {
82 Status ParseLogPath(const base::Value& option, Capabilities* capabilities) {
83 if (!option.GetAsString(&capabilities->log_path))
84 return Status(kUnknownError, "must be a string");
88 Status ParseDeviceName(std::string device_name, Capabilities* capabilities) {
89 scoped_ptr<MobileDevice> device;
90 Status status = FindMobileDevice(device_name, &device);
92 if (status.IsError()) {
93 return Status(kUnknownError,
94 "'" + device_name + "' must be a valid device",
98 capabilities->device_metrics.reset(device->device_metrics.release());
99 capabilities->switches.SetSwitch("user-agent", device->user_agent);
104 Status ParseMobileEmulation(const base::Value& option,
105 Capabilities* capabilities) {
106 const base::DictionaryValue* mobile_emulation;
107 if (!option.GetAsDictionary(&mobile_emulation))
108 return Status(kUnknownError, "'mobileEmulation' must be a dictionary");
110 if (mobile_emulation->HasKey("deviceName")) {
111 // Cannot use any other options with deviceName.
112 if (mobile_emulation->size() > 1)
113 return Status(kUnknownError, "'deviceName' must be used alone");
115 std::string device_name;
116 if (!mobile_emulation->GetString("deviceName", &device_name))
117 return Status(kUnknownError, "'deviceName' must be a string");
119 return ParseDeviceName(device_name, capabilities);
122 if (mobile_emulation->HasKey("deviceMetrics")) {
123 const base::DictionaryValue* metrics;
124 if (!mobile_emulation->GetDictionary("deviceMetrics", &metrics))
125 return Status(kUnknownError, "'deviceMetrics' must be a dictionary");
129 double device_scale_factor = 0;
130 if (!metrics->GetInteger("width", &width) ||
131 !metrics->GetInteger("height", &height) ||
132 !metrics->GetDouble("pixelRatio", &device_scale_factor))
133 return Status(kUnknownError, "invalid 'deviceMetrics'");
135 DeviceMetrics* device_metrics =
136 new DeviceMetrics(width, height, device_scale_factor);
137 capabilities->device_metrics =
138 scoped_ptr<DeviceMetrics>(device_metrics);
141 if (mobile_emulation->HasKey("userAgent")) {
142 std::string user_agent;
143 if (!mobile_emulation->GetString("userAgent", &user_agent))
144 return Status(kUnknownError, "'userAgent' must be a string");
146 capabilities->switches.SetSwitch("user-agent", user_agent);
152 Status ParseSwitches(const base::Value& option,
153 Capabilities* capabilities) {
154 const base::ListValue* switches_list = NULL;
155 if (!option.GetAsList(&switches_list))
156 return Status(kUnknownError, "must be a list");
157 for (size_t i = 0; i < switches_list->GetSize(); ++i) {
158 std::string arg_string;
159 if (!switches_list->GetString(i, &arg_string))
160 return Status(kUnknownError, "each argument must be a string");
161 capabilities->switches.SetUnparsedSwitch(arg_string);
166 Status ParseExtensions(const base::Value& option, Capabilities* capabilities) {
167 const base::ListValue* extensions = NULL;
168 if (!option.GetAsList(&extensions))
169 return Status(kUnknownError, "must be a list");
170 for (size_t i = 0; i < extensions->GetSize(); ++i) {
171 std::string extension;
172 if (!extensions->GetString(i, &extension)) {
173 return Status(kUnknownError,
174 "each extension must be a base64 encoded string");
176 capabilities->extensions.push_back(extension);
181 Status ParseProxy(const base::Value& option, Capabilities* capabilities) {
182 const base::DictionaryValue* proxy_dict;
183 if (!option.GetAsDictionary(&proxy_dict))
184 return Status(kUnknownError, "must be a dictionary");
185 std::string proxy_type;
186 if (!proxy_dict->GetString("proxyType", &proxy_type))
187 return Status(kUnknownError, "'proxyType' must be a string");
188 proxy_type = base::StringToLowerASCII(proxy_type);
189 if (proxy_type == "direct") {
190 capabilities->switches.SetSwitch("no-proxy-server");
191 } else if (proxy_type == "system") {
193 } else if (proxy_type == "pac") {
194 CommandLine::StringType proxy_pac_url;
195 if (!proxy_dict->GetString("proxyAutoconfigUrl", &proxy_pac_url))
196 return Status(kUnknownError, "'proxyAutoconfigUrl' must be a string");
197 capabilities->switches.SetSwitch("proxy-pac-url", proxy_pac_url);
198 } else if (proxy_type == "autodetect") {
199 capabilities->switches.SetSwitch("proxy-auto-detect");
200 } else if (proxy_type == "manual") {
201 const char* proxy_servers_options[][2] = {
202 {"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}};
203 const base::Value* option_value = NULL;
204 std::string proxy_servers;
205 for (size_t i = 0; i < arraysize(proxy_servers_options); ++i) {
206 if (!proxy_dict->Get(proxy_servers_options[i][0], &option_value) ||
207 option_value->IsType(base::Value::TYPE_NULL)) {
211 if (!option_value->GetAsString(&value)) {
214 base::StringPrintf("'%s' must be a string",
215 proxy_servers_options[i][0]));
217 // Converts into Chrome proxy scheme.
218 // Example: "http=localhost:9000;ftp=localhost:8000".
219 if (!proxy_servers.empty())
220 proxy_servers += ";";
221 proxy_servers += base::StringPrintf(
222 "%s=%s", proxy_servers_options[i][1], value.c_str());
225 std::string proxy_bypass_list;
226 if (proxy_dict->Get("noProxy", &option_value) &&
227 !option_value->IsType(base::Value::TYPE_NULL)) {
228 if (!option_value->GetAsString(&proxy_bypass_list))
229 return Status(kUnknownError, "'noProxy' must be a string");
232 if (proxy_servers.empty() && proxy_bypass_list.empty()) {
233 return Status(kUnknownError,
234 "proxyType is 'manual' but no manual "
235 "proxy capabilities were found");
237 if (!proxy_servers.empty())
238 capabilities->switches.SetSwitch("proxy-server", proxy_servers);
239 if (!proxy_bypass_list.empty()) {
240 capabilities->switches.SetSwitch("proxy-bypass-list",
244 return Status(kUnknownError, "unrecognized proxy type:" + proxy_type);
249 Status ParseExcludeSwitches(const base::Value& option,
250 Capabilities* capabilities) {
251 const base::ListValue* switches = NULL;
252 if (!option.GetAsList(&switches))
253 return Status(kUnknownError, "must be a list");
254 for (size_t i = 0; i < switches->GetSize(); ++i) {
255 std::string switch_name;
256 if (!switches->GetString(i, &switch_name)) {
257 return Status(kUnknownError,
258 "each switch to be removed must be a string");
260 capabilities->exclude_switches.insert(switch_name);
265 Status ParseUseRemoteBrowser(const base::Value& option,
266 Capabilities* capabilities) {
267 std::string server_addr;
268 if (!option.GetAsString(&server_addr))
269 return Status(kUnknownError, "must be 'host:port'");
271 std::vector<std::string> values;
272 base::SplitString(server_addr, ':', &values);
273 if (values.size() != 2)
274 return Status(kUnknownError, "must be 'host:port'");
277 base::StringToInt(values[1], &port);
279 return Status(kUnknownError, "port must be > 0");
281 capabilities->debugger_address = NetAddress(values[0], port);
285 Status ParseLoggingPrefs(const base::Value& option,
286 Capabilities* capabilities) {
287 const base::DictionaryValue* logging_prefs = NULL;
288 if (!option.GetAsDictionary(&logging_prefs))
289 return Status(kUnknownError, "must be a dictionary");
291 for (base::DictionaryValue::Iterator pref(*logging_prefs);
292 !pref.IsAtEnd(); pref.Advance()) {
293 std::string type = pref.key();
295 std::string level_name;
296 if (!pref.value().GetAsString(&level_name) ||
297 !WebDriverLog::NameToLevel(level_name, &level)) {
298 return Status(kUnknownError, "invalid log level for '" + type + "' log");
300 capabilities->logging_prefs.insert(std::make_pair(type, level));
305 Status ParseInspectorDomainStatus(
306 PerfLoggingPrefs::InspectorDomainStatus* to_set,
307 const base::Value& option,
308 Capabilities* capabilities) {
310 if (!option.GetAsBoolean(&desired_value))
311 return Status(kUnknownError, "must be a boolean");
313 *to_set = PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled;
315 *to_set = PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyDisabled;
319 Status ParsePerfLoggingPrefs(const base::Value& option,
320 Capabilities* capabilities) {
321 const base::DictionaryValue* perf_logging_prefs = NULL;
322 if (!option.GetAsDictionary(&perf_logging_prefs))
323 return Status(kUnknownError, "must be a dictionary");
325 std::map<std::string, Parser> parser_map;
326 parser_map["enableNetwork"] = base::Bind(
327 &ParseInspectorDomainStatus, &capabilities->perf_logging_prefs.network);
328 parser_map["enablePage"] = base::Bind(
329 &ParseInspectorDomainStatus, &capabilities->perf_logging_prefs.page);
330 parser_map["enableTimeline"] = base::Bind(
331 &ParseInspectorDomainStatus, &capabilities->perf_logging_prefs.timeline);
332 parser_map["traceCategories"] = base::Bind(
333 &ParseString, &capabilities->perf_logging_prefs.trace_categories);
335 for (base::DictionaryValue::Iterator it(*perf_logging_prefs); !it.IsAtEnd();
337 if (parser_map.find(it.key()) == parser_map.end())
338 return Status(kUnknownError, "unrecognized performance logging "
339 "option: " + it.key());
340 Status status = parser_map[it.key()].Run(it.value(), capabilities);
341 if (status.IsError())
342 return Status(kUnknownError, "cannot parse " + it.key(), status);
347 Status ParseChromeOptions(
348 const base::Value& capability,
349 Capabilities* capabilities) {
350 const base::DictionaryValue* chrome_options = NULL;
351 if (!capability.GetAsDictionary(&chrome_options))
352 return Status(kUnknownError, "must be a dictionary");
354 bool is_android = chrome_options->HasKey("androidPackage");
355 bool is_remote = chrome_options->HasKey("debuggerAddress");
357 std::map<std::string, Parser> parser_map;
358 // Ignore 'args', 'binary' and 'extensions' capabilities by default, since the
359 // Java client always passes them.
360 parser_map["args"] = base::Bind(&IgnoreCapability);
361 parser_map["binary"] = base::Bind(&IgnoreCapability);
362 parser_map["extensions"] = base::Bind(&IgnoreCapability);
364 parser_map["perfLoggingPrefs"] = base::Bind(&ParsePerfLoggingPrefs);
367 parser_map["androidActivity"] =
368 base::Bind(&ParseString, &capabilities->android_activity);
369 parser_map["androidDeviceSerial"] =
370 base::Bind(&ParseString, &capabilities->android_device_serial);
371 parser_map["androidPackage"] =
372 base::Bind(&ParseString, &capabilities->android_package);
373 parser_map["androidProcess"] =
374 base::Bind(&ParseString, &capabilities->android_process);
375 parser_map["androidUseRunningApp"] =
376 base::Bind(&ParseBoolean, &capabilities->android_use_running_app);
377 parser_map["args"] = base::Bind(&ParseSwitches);
378 parser_map["loadAsync"] = base::Bind(&IgnoreDeprecatedOption, "loadAsync");
379 } else if (is_remote) {
380 parser_map["debuggerAddress"] = base::Bind(&ParseUseRemoteBrowser);
382 parser_map["args"] = base::Bind(&ParseSwitches);
383 parser_map["binary"] = base::Bind(&ParseFilePath, &capabilities->binary);
384 parser_map["detach"] = base::Bind(&ParseBoolean, &capabilities->detach);
385 parser_map["mobileEmulation"] = base::Bind(&ParseMobileEmulation);
386 parser_map["excludeSwitches"] = base::Bind(&ParseExcludeSwitches);
387 parser_map["extensions"] = base::Bind(&ParseExtensions);
388 parser_map["forceDevToolsScreenshot"] = base::Bind(
389 &ParseBoolean, &capabilities->force_devtools_screenshot);
390 parser_map["loadAsync"] = base::Bind(&IgnoreDeprecatedOption, "loadAsync");
391 parser_map["localState"] =
392 base::Bind(&ParseDict, &capabilities->local_state);
393 parser_map["logPath"] = base::Bind(&ParseLogPath);
394 parser_map["minidumpPath"] =
395 base::Bind(&ParseString, &capabilities->minidump_path);
396 parser_map["prefs"] = base::Bind(&ParseDict, &capabilities->prefs);
399 for (base::DictionaryValue::Iterator it(*chrome_options); !it.IsAtEnd();
401 if (parser_map.find(it.key()) == parser_map.end()) {
402 return Status(kUnknownError,
403 "unrecognized chrome option: " + it.key());
405 Status status = parser_map[it.key()].Run(it.value(), capabilities);
406 if (status.IsError())
407 return Status(kUnknownError, "cannot parse " + it.key(), status);
414 Switches::Switches() {}
416 Switches::~Switches() {}
418 void Switches::SetSwitch(const std::string& name) {
419 SetSwitch(name, NativeString());
422 void Switches::SetSwitch(const std::string& name, const std::string& value) {
424 SetSwitch(name, base::UTF8ToUTF16(value));
426 switch_map_[name] = value;
430 void Switches::SetSwitch(const std::string& name, const base::string16& value) {
432 switch_map_[name] = value;
434 SetSwitch(name, base::UTF16ToUTF8(value));
438 void Switches::SetSwitch(const std::string& name, const base::FilePath& value) {
439 SetSwitch(name, value.value());
442 void Switches::SetFromSwitches(const Switches& switches) {
443 for (SwitchMap::const_iterator iter = switches.switch_map_.begin();
444 iter != switches.switch_map_.end();
446 switch_map_[iter->first] = iter->second;
450 void Switches::SetUnparsedSwitch(const std::string& unparsed_switch) {
452 size_t equals_index = unparsed_switch.find('=');
453 if (equals_index != std::string::npos)
454 value = unparsed_switch.substr(equals_index + 1);
457 size_t start_index = 0;
458 if (unparsed_switch.substr(0, 2) == "--")
460 name = unparsed_switch.substr(start_index, equals_index - start_index);
462 SetSwitch(name, value);
465 void Switches::RemoveSwitch(const std::string& name) {
466 switch_map_.erase(name);
469 bool Switches::HasSwitch(const std::string& name) const {
470 return switch_map_.count(name) > 0;
473 std::string Switches::GetSwitchValue(const std::string& name) const {
474 NativeString value = GetSwitchValueNative(name);
476 return base::UTF16ToUTF8(value);
482 Switches::NativeString Switches::GetSwitchValueNative(
483 const std::string& name) const {
484 SwitchMap::const_iterator iter = switch_map_.find(name);
485 if (iter == switch_map_.end())
486 return NativeString();
490 size_t Switches::GetSize() const {
491 return switch_map_.size();
494 void Switches::AppendToCommandLine(CommandLine* command) const {
495 for (SwitchMap::const_iterator iter = switch_map_.begin();
496 iter != switch_map_.end();
498 command->AppendSwitchNative(iter->first, iter->second);
502 std::string Switches::ToString() const {
504 SwitchMap::const_iterator iter = switch_map_.begin();
505 while (iter != switch_map_.end()) {
506 str += "--" + iter->first;
507 std::string value = GetSwitchValue(iter->first);
508 if (value.length()) {
509 if (value.find(' ') != std::string::npos)
510 value = base::GetQuotedJSONString(value);
514 if (iter == switch_map_.end())
521 PerfLoggingPrefs::PerfLoggingPrefs()
522 : network(InspectorDomainStatus::kDefaultEnabled),
523 page(InspectorDomainStatus::kDefaultEnabled),
524 timeline(InspectorDomainStatus::kDefaultEnabled),
525 trace_categories() {}
527 PerfLoggingPrefs::~PerfLoggingPrefs() {}
529 Capabilities::Capabilities()
530 : android_use_running_app(false),
532 force_devtools_screenshot(false) {}
534 Capabilities::~Capabilities() {}
536 bool Capabilities::IsAndroid() const {
537 return !android_package.empty();
540 bool Capabilities::IsRemoteBrowser() const {
541 return debugger_address.IsValid();
544 Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
545 std::map<std::string, Parser> parser_map;
546 parser_map["chromeOptions"] = base::Bind(&ParseChromeOptions);
547 parser_map["loggingPrefs"] = base::Bind(&ParseLoggingPrefs);
548 parser_map["proxy"] = base::Bind(&ParseProxy);
549 for (std::map<std::string, Parser>::iterator it = parser_map.begin();
550 it != parser_map.end(); ++it) {
551 const base::Value* capability = NULL;
552 if (desired_caps.Get(it->first, &capability)) {
553 Status status = it->second.Run(*capability, this);
554 if (status.IsError()) {
556 kUnknownError, "cannot parse capability: " + it->first, status);
560 // Perf log must be enabled if perf log prefs are specified; otherwise, error.
561 LoggingPrefs::const_iterator iter = logging_prefs.find(
562 WebDriverLog::kPerformanceType);
563 if (iter == logging_prefs.end() || iter->second == Log::kOff) {
564 const base::DictionaryValue* chrome_options = NULL;
565 if (desired_caps.GetDictionary("chromeOptions", &chrome_options) &&
566 chrome_options->HasKey("perfLoggingPrefs")) {
567 return Status(kUnknownError, "perfLoggingPrefs specified, "
568 "but performance logging was not enabled");