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
ParseInterval(int* to_set
,
51 const base::Value
& option
,
52 Capabilities
* capabilities
) {
54 if (!option
.GetAsInteger(&parsed_int
))
55 return Status(kUnknownError
, "must be an integer");
57 return Status(kUnknownError
, "must be positive");
62 Status
ParseFilePath(base::FilePath
* to_set
,
63 const base::Value
& option
,
64 Capabilities
* capabilities
) {
65 base::FilePath::StringType str
;
66 if (!option
.GetAsString(&str
))
67 return Status(kUnknownError
, "must be a string");
68 *to_set
= base::FilePath(str
);
72 Status
ParseDict(scoped_ptr
<base::DictionaryValue
>* to_set
,
73 const base::Value
& option
,
74 Capabilities
* capabilities
) {
75 const base::DictionaryValue
* dict
= NULL
;
76 if (!option
.GetAsDictionary(&dict
))
77 return Status(kUnknownError
, "must be a dictionary");
78 to_set
->reset(dict
->DeepCopy());
82 Status
IgnoreDeprecatedOption(
83 const char* option_name
,
84 const base::Value
& option
,
85 Capabilities
* capabilities
) {
86 LOG(WARNING
) << "Deprecated chrome option is ignored: " << option_name
;
90 Status
IgnoreCapability(const base::Value
& option
, Capabilities
* capabilities
) {
94 Status
ParseLogPath(const base::Value
& option
, Capabilities
* capabilities
) {
95 if (!option
.GetAsString(&capabilities
->log_path
))
96 return Status(kUnknownError
, "must be a string");
100 Status
ParseDeviceName(std::string device_name
, Capabilities
* capabilities
) {
101 scoped_ptr
<MobileDevice
> device
;
102 Status status
= FindMobileDevice(device_name
, &device
);
104 if (status
.IsError()) {
105 return Status(kUnknownError
,
106 "'" + device_name
+ "' must be a valid device",
110 capabilities
->device_metrics
.reset(device
->device_metrics
.release());
111 capabilities
->switches
.SetSwitch("user-agent", device
->user_agent
);
116 Status
ParseMobileEmulation(const base::Value
& option
,
117 Capabilities
* capabilities
) {
118 const base::DictionaryValue
* mobile_emulation
;
119 if (!option
.GetAsDictionary(&mobile_emulation
))
120 return Status(kUnknownError
, "'mobileEmulation' must be a dictionary");
122 if (mobile_emulation
->HasKey("deviceName")) {
123 // Cannot use any other options with deviceName.
124 if (mobile_emulation
->size() > 1)
125 return Status(kUnknownError
, "'deviceName' must be used alone");
127 std::string device_name
;
128 if (!mobile_emulation
->GetString("deviceName", &device_name
))
129 return Status(kUnknownError
, "'deviceName' must be a string");
131 return ParseDeviceName(device_name
, capabilities
);
134 if (mobile_emulation
->HasKey("deviceMetrics")) {
135 const base::DictionaryValue
* metrics
;
136 if (!mobile_emulation
->GetDictionary("deviceMetrics", &metrics
))
137 return Status(kUnknownError
, "'deviceMetrics' must be a dictionary");
141 double device_scale_factor
= 0;
142 if (!metrics
->GetInteger("width", &width
) ||
143 !metrics
->GetInteger("height", &height
) ||
144 !metrics
->GetDouble("pixelRatio", &device_scale_factor
))
145 return Status(kUnknownError
, "invalid 'deviceMetrics'");
147 DeviceMetrics
* device_metrics
=
148 new DeviceMetrics(width
, height
, device_scale_factor
);
149 capabilities
->device_metrics
=
150 scoped_ptr
<DeviceMetrics
>(device_metrics
);
153 if (mobile_emulation
->HasKey("userAgent")) {
154 std::string user_agent
;
155 if (!mobile_emulation
->GetString("userAgent", &user_agent
))
156 return Status(kUnknownError
, "'userAgent' must be a string");
158 capabilities
->switches
.SetSwitch("user-agent", user_agent
);
164 Status
ParseSwitches(const base::Value
& option
,
165 Capabilities
* capabilities
) {
166 const base::ListValue
* switches_list
= NULL
;
167 if (!option
.GetAsList(&switches_list
))
168 return Status(kUnknownError
, "must be a list");
169 for (size_t i
= 0; i
< switches_list
->GetSize(); ++i
) {
170 std::string arg_string
;
171 if (!switches_list
->GetString(i
, &arg_string
))
172 return Status(kUnknownError
, "each argument must be a string");
173 capabilities
->switches
.SetUnparsedSwitch(arg_string
);
178 Status
ParseExtensions(const base::Value
& option
, Capabilities
* capabilities
) {
179 const base::ListValue
* extensions
= NULL
;
180 if (!option
.GetAsList(&extensions
))
181 return Status(kUnknownError
, "must be a list");
182 for (size_t i
= 0; i
< extensions
->GetSize(); ++i
) {
183 std::string extension
;
184 if (!extensions
->GetString(i
, &extension
)) {
185 return Status(kUnknownError
,
186 "each extension must be a base64 encoded string");
188 capabilities
->extensions
.push_back(extension
);
193 Status
ParseProxy(const base::Value
& option
, Capabilities
* capabilities
) {
194 const base::DictionaryValue
* proxy_dict
;
195 if (!option
.GetAsDictionary(&proxy_dict
))
196 return Status(kUnknownError
, "must be a dictionary");
197 std::string proxy_type
;
198 if (!proxy_dict
->GetString("proxyType", &proxy_type
))
199 return Status(kUnknownError
, "'proxyType' must be a string");
200 proxy_type
= base::StringToLowerASCII(proxy_type
);
201 if (proxy_type
== "direct") {
202 capabilities
->switches
.SetSwitch("no-proxy-server");
203 } else if (proxy_type
== "system") {
205 } else if (proxy_type
== "pac") {
206 base::CommandLine::StringType proxy_pac_url
;
207 if (!proxy_dict
->GetString("proxyAutoconfigUrl", &proxy_pac_url
))
208 return Status(kUnknownError
, "'proxyAutoconfigUrl' must be a string");
209 capabilities
->switches
.SetSwitch("proxy-pac-url", proxy_pac_url
);
210 } else if (proxy_type
== "autodetect") {
211 capabilities
->switches
.SetSwitch("proxy-auto-detect");
212 } else if (proxy_type
== "manual") {
213 const char* const proxy_servers_options
[][2] = {
214 {"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}};
215 const base::Value
* option_value
= NULL
;
216 std::string proxy_servers
;
217 for (size_t i
= 0; i
< arraysize(proxy_servers_options
); ++i
) {
218 if (!proxy_dict
->Get(proxy_servers_options
[i
][0], &option_value
) ||
219 option_value
->IsType(base::Value::TYPE_NULL
)) {
223 if (!option_value
->GetAsString(&value
)) {
226 base::StringPrintf("'%s' must be a string",
227 proxy_servers_options
[i
][0]));
229 // Converts into Chrome proxy scheme.
230 // Example: "http=localhost:9000;ftp=localhost:8000".
231 if (!proxy_servers
.empty())
232 proxy_servers
+= ";";
233 proxy_servers
+= base::StringPrintf(
234 "%s=%s", proxy_servers_options
[i
][1], value
.c_str());
237 std::string proxy_bypass_list
;
238 if (proxy_dict
->Get("noProxy", &option_value
) &&
239 !option_value
->IsType(base::Value::TYPE_NULL
)) {
240 if (!option_value
->GetAsString(&proxy_bypass_list
))
241 return Status(kUnknownError
, "'noProxy' must be a string");
244 if (proxy_servers
.empty() && proxy_bypass_list
.empty()) {
245 return Status(kUnknownError
,
246 "proxyType is 'manual' but no manual "
247 "proxy capabilities were found");
249 if (!proxy_servers
.empty())
250 capabilities
->switches
.SetSwitch("proxy-server", proxy_servers
);
251 if (!proxy_bypass_list
.empty()) {
252 capabilities
->switches
.SetSwitch("proxy-bypass-list",
256 return Status(kUnknownError
, "unrecognized proxy type:" + proxy_type
);
261 Status
ParseExcludeSwitches(const base::Value
& option
,
262 Capabilities
* capabilities
) {
263 const base::ListValue
* switches
= NULL
;
264 if (!option
.GetAsList(&switches
))
265 return Status(kUnknownError
, "must be a list");
266 for (size_t i
= 0; i
< switches
->GetSize(); ++i
) {
267 std::string switch_name
;
268 if (!switches
->GetString(i
, &switch_name
)) {
269 return Status(kUnknownError
,
270 "each switch to be removed must be a string");
272 capabilities
->exclude_switches
.insert(switch_name
);
277 Status
ParseUseRemoteBrowser(const base::Value
& option
,
278 Capabilities
* capabilities
) {
279 std::string server_addr
;
280 if (!option
.GetAsString(&server_addr
))
281 return Status(kUnknownError
, "must be 'host:port'");
283 std::vector
<std::string
> values
;
284 base::SplitString(server_addr
, ':', &values
);
285 if (values
.size() != 2)
286 return Status(kUnknownError
, "must be 'host:port'");
289 base::StringToInt(values
[1], &port
);
291 return Status(kUnknownError
, "port must be > 0");
293 capabilities
->debugger_address
= NetAddress(values
[0], port
);
297 Status
ParseLoggingPrefs(const base::Value
& option
,
298 Capabilities
* capabilities
) {
299 const base::DictionaryValue
* logging_prefs
= NULL
;
300 if (!option
.GetAsDictionary(&logging_prefs
))
301 return Status(kUnknownError
, "must be a dictionary");
303 for (base::DictionaryValue::Iterator
pref(*logging_prefs
);
304 !pref
.IsAtEnd(); pref
.Advance()) {
305 std::string type
= pref
.key();
307 std::string level_name
;
308 if (!pref
.value().GetAsString(&level_name
) ||
309 !WebDriverLog::NameToLevel(level_name
, &level
)) {
310 return Status(kUnknownError
, "invalid log level for '" + type
+ "' log");
312 capabilities
->logging_prefs
.insert(std::make_pair(type
, level
));
317 Status
ParseInspectorDomainStatus(
318 PerfLoggingPrefs::InspectorDomainStatus
* to_set
,
319 const base::Value
& option
,
320 Capabilities
* capabilities
) {
322 if (!option
.GetAsBoolean(&desired_value
))
323 return Status(kUnknownError
, "must be a boolean");
325 *to_set
= PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled
;
327 *to_set
= PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyDisabled
;
331 Status
ParsePerfLoggingPrefs(const base::Value
& option
,
332 Capabilities
* capabilities
) {
333 const base::DictionaryValue
* perf_logging_prefs
= NULL
;
334 if (!option
.GetAsDictionary(&perf_logging_prefs
))
335 return Status(kUnknownError
, "must be a dictionary");
337 std::map
<std::string
, Parser
> parser_map
;
338 parser_map
["bufferUsageReportingInterval"] = base::Bind(&ParseInterval
,
339 &capabilities
->perf_logging_prefs
.buffer_usage_reporting_interval
);
340 parser_map
["enableNetwork"] = base::Bind(
341 &ParseInspectorDomainStatus
, &capabilities
->perf_logging_prefs
.network
);
342 parser_map
["enablePage"] = base::Bind(
343 &ParseInspectorDomainStatus
, &capabilities
->perf_logging_prefs
.page
);
344 parser_map
["enableTimeline"] = base::Bind(
345 &ParseInspectorDomainStatus
, &capabilities
->perf_logging_prefs
.timeline
);
346 parser_map
["traceCategories"] = base::Bind(
347 &ParseString
, &capabilities
->perf_logging_prefs
.trace_categories
);
349 for (base::DictionaryValue::Iterator
it(*perf_logging_prefs
); !it
.IsAtEnd();
351 if (parser_map
.find(it
.key()) == parser_map
.end())
352 return Status(kUnknownError
, "unrecognized performance logging "
353 "option: " + it
.key());
354 Status status
= parser_map
[it
.key()].Run(it
.value(), capabilities
);
355 if (status
.IsError())
356 return Status(kUnknownError
, "cannot parse " + it
.key(), status
);
361 Status
ParseChromeOptions(
362 const base::Value
& capability
,
363 Capabilities
* capabilities
) {
364 const base::DictionaryValue
* chrome_options
= NULL
;
365 if (!capability
.GetAsDictionary(&chrome_options
))
366 return Status(kUnknownError
, "must be a dictionary");
368 bool is_android
= chrome_options
->HasKey("androidPackage");
369 bool is_remote
= chrome_options
->HasKey("debuggerAddress");
371 std::map
<std::string
, Parser
> parser_map
;
372 // Ignore 'args', 'binary' and 'extensions' capabilities by default, since the
373 // Java client always passes them.
374 parser_map
["args"] = base::Bind(&IgnoreCapability
);
375 parser_map
["binary"] = base::Bind(&IgnoreCapability
);
376 parser_map
["extensions"] = base::Bind(&IgnoreCapability
);
378 parser_map
["perfLoggingPrefs"] = base::Bind(&ParsePerfLoggingPrefs
);
381 parser_map
["androidActivity"] =
382 base::Bind(&ParseString
, &capabilities
->android_activity
);
383 parser_map
["androidDeviceSerial"] =
384 base::Bind(&ParseString
, &capabilities
->android_device_serial
);
385 parser_map
["androidPackage"] =
386 base::Bind(&ParseString
, &capabilities
->android_package
);
387 parser_map
["androidProcess"] =
388 base::Bind(&ParseString
, &capabilities
->android_process
);
389 parser_map
["androidUseRunningApp"] =
390 base::Bind(&ParseBoolean
, &capabilities
->android_use_running_app
);
391 parser_map
["args"] = base::Bind(&ParseSwitches
);
392 parser_map
["excludeSwitches"] = base::Bind(&ParseExcludeSwitches
);
393 parser_map
["loadAsync"] = base::Bind(&IgnoreDeprecatedOption
, "loadAsync");
394 } else if (is_remote
) {
395 parser_map
["debuggerAddress"] = base::Bind(&ParseUseRemoteBrowser
);
397 parser_map
["args"] = base::Bind(&ParseSwitches
);
398 parser_map
["binary"] = base::Bind(&ParseFilePath
, &capabilities
->binary
);
399 parser_map
["detach"] = base::Bind(&ParseBoolean
, &capabilities
->detach
);
400 parser_map
["excludeSwitches"] = base::Bind(&ParseExcludeSwitches
);
401 parser_map
["extensions"] = base::Bind(&ParseExtensions
);
402 parser_map
["forceDevToolsScreenshot"] = base::Bind(
403 &ParseBoolean
, &capabilities
->force_devtools_screenshot
);
404 parser_map
["loadAsync"] = base::Bind(&IgnoreDeprecatedOption
, "loadAsync");
405 parser_map
["localState"] =
406 base::Bind(&ParseDict
, &capabilities
->local_state
);
407 parser_map
["logPath"] = base::Bind(&ParseLogPath
);
408 parser_map
["minidumpPath"] =
409 base::Bind(&ParseString
, &capabilities
->minidump_path
);
410 parser_map
["mobileEmulation"] = base::Bind(&ParseMobileEmulation
);
411 parser_map
["prefs"] = base::Bind(&ParseDict
, &capabilities
->prefs
);
414 for (base::DictionaryValue::Iterator
it(*chrome_options
); !it
.IsAtEnd();
416 if (parser_map
.find(it
.key()) == parser_map
.end()) {
417 return Status(kUnknownError
,
418 "unrecognized chrome option: " + it
.key());
420 Status status
= parser_map
[it
.key()].Run(it
.value(), capabilities
);
421 if (status
.IsError())
422 return Status(kUnknownError
, "cannot parse " + it
.key(), status
);
429 Switches::Switches() {}
431 Switches::~Switches() {}
433 void Switches::SetSwitch(const std::string
& name
) {
434 SetSwitch(name
, NativeString());
437 void Switches::SetSwitch(const std::string
& name
, const std::string
& value
) {
439 SetSwitch(name
, base::UTF8ToUTF16(value
));
441 switch_map_
[name
] = value
;
445 void Switches::SetSwitch(const std::string
& name
, const base::string16
& value
) {
447 switch_map_
[name
] = value
;
449 SetSwitch(name
, base::UTF16ToUTF8(value
));
453 void Switches::SetSwitch(const std::string
& name
, const base::FilePath
& value
) {
454 SetSwitch(name
, value
.value());
457 void Switches::SetFromSwitches(const Switches
& switches
) {
458 for (SwitchMap::const_iterator iter
= switches
.switch_map_
.begin();
459 iter
!= switches
.switch_map_
.end();
461 switch_map_
[iter
->first
] = iter
->second
;
465 void Switches::SetUnparsedSwitch(const std::string
& unparsed_switch
) {
467 size_t equals_index
= unparsed_switch
.find('=');
468 if (equals_index
!= std::string::npos
)
469 value
= unparsed_switch
.substr(equals_index
+ 1);
472 size_t start_index
= 0;
473 if (unparsed_switch
.substr(0, 2) == "--")
475 name
= unparsed_switch
.substr(start_index
, equals_index
- start_index
);
477 SetSwitch(name
, value
);
480 void Switches::RemoveSwitch(const std::string
& name
) {
481 switch_map_
.erase(name
);
484 bool Switches::HasSwitch(const std::string
& name
) const {
485 return switch_map_
.count(name
) > 0;
488 std::string
Switches::GetSwitchValue(const std::string
& name
) const {
489 NativeString value
= GetSwitchValueNative(name
);
491 return base::UTF16ToUTF8(value
);
497 Switches::NativeString
Switches::GetSwitchValueNative(
498 const std::string
& name
) const {
499 SwitchMap::const_iterator iter
= switch_map_
.find(name
);
500 if (iter
== switch_map_
.end())
501 return NativeString();
505 size_t Switches::GetSize() const {
506 return switch_map_
.size();
509 void Switches::AppendToCommandLine(base::CommandLine
* command
) const {
510 for (SwitchMap::const_iterator iter
= switch_map_
.begin();
511 iter
!= switch_map_
.end();
513 command
->AppendSwitchNative(iter
->first
, iter
->second
);
517 std::string
Switches::ToString() const {
519 SwitchMap::const_iterator iter
= switch_map_
.begin();
520 while (iter
!= switch_map_
.end()) {
521 str
+= "--" + iter
->first
;
522 std::string value
= GetSwitchValue(iter
->first
);
523 if (value
.length()) {
524 if (value
.find(' ') != std::string::npos
)
525 value
= base::GetQuotedJSONString(value
);
529 if (iter
== switch_map_
.end())
536 PerfLoggingPrefs::PerfLoggingPrefs()
537 : network(InspectorDomainStatus::kDefaultEnabled
),
538 page(InspectorDomainStatus::kDefaultEnabled
),
539 timeline(InspectorDomainStatus::kDefaultEnabled
),
541 buffer_usage_reporting_interval(1000) {}
543 PerfLoggingPrefs::~PerfLoggingPrefs() {}
545 Capabilities::Capabilities()
546 : android_use_running_app(false),
548 force_devtools_screenshot(false) {}
550 Capabilities::~Capabilities() {}
552 bool Capabilities::IsAndroid() const {
553 return !android_package
.empty();
556 bool Capabilities::IsRemoteBrowser() const {
557 return debugger_address
.IsValid();
560 Status
Capabilities::Parse(const base::DictionaryValue
& desired_caps
) {
561 std::map
<std::string
, Parser
> parser_map
;
562 parser_map
["chromeOptions"] = base::Bind(&ParseChromeOptions
);
563 parser_map
["loggingPrefs"] = base::Bind(&ParseLoggingPrefs
);
564 parser_map
["proxy"] = base::Bind(&ParseProxy
);
565 for (std::map
<std::string
, Parser
>::iterator it
= parser_map
.begin();
566 it
!= parser_map
.end(); ++it
) {
567 const base::Value
* capability
= NULL
;
568 if (desired_caps
.Get(it
->first
, &capability
)) {
569 Status status
= it
->second
.Run(*capability
, this);
570 if (status
.IsError()) {
572 kUnknownError
, "cannot parse capability: " + it
->first
, status
);
576 // Perf log must be enabled if perf log prefs are specified; otherwise, error.
577 LoggingPrefs::const_iterator iter
= logging_prefs
.find(
578 WebDriverLog::kPerformanceType
);
579 if (iter
== logging_prefs
.end() || iter
->second
== Log::kOff
) {
580 const base::DictionaryValue
* chrome_options
= NULL
;
581 if (desired_caps
.GetDictionary("chromeOptions", &chrome_options
) &&
582 chrome_options
->HasKey("perfLoggingPrefs")) {
583 return Status(kUnknownError
, "perfLoggingPrefs specified, "
584 "but performance logging was not enabled");