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 // Don't override the user agent if blank (like for notebooks).
112 if (!device
->user_agent
.empty())
113 capabilities
->switches
.SetSwitch("user-agent", device
->user_agent
);
118 Status
ParseMobileEmulation(const base::Value
& option
,
119 Capabilities
* capabilities
) {
120 const base::DictionaryValue
* mobile_emulation
;
121 if (!option
.GetAsDictionary(&mobile_emulation
))
122 return Status(kUnknownError
, "'mobileEmulation' must be a dictionary");
124 if (mobile_emulation
->HasKey("deviceName")) {
125 // Cannot use any other options with deviceName.
126 if (mobile_emulation
->size() > 1)
127 return Status(kUnknownError
, "'deviceName' must be used alone");
129 std::string device_name
;
130 if (!mobile_emulation
->GetString("deviceName", &device_name
))
131 return Status(kUnknownError
, "'deviceName' must be a string");
133 return ParseDeviceName(device_name
, capabilities
);
136 if (mobile_emulation
->HasKey("deviceMetrics")) {
137 const base::DictionaryValue
* metrics
;
138 if (!mobile_emulation
->GetDictionary("deviceMetrics", &metrics
))
139 return Status(kUnknownError
, "'deviceMetrics' must be a dictionary");
143 double device_scale_factor
= 0;
146 if (!metrics
->GetInteger("width", &width
) ||
147 !metrics
->GetInteger("height", &height
) ||
148 !metrics
->GetDouble("pixelRatio", &device_scale_factor
))
149 return Status(kUnknownError
, "invalid 'deviceMetrics'");
151 if (metrics
->HasKey("touch")) {
152 if (!metrics
->GetBoolean("touch", &touch
))
153 return Status(kUnknownError
, "'touch' must be a boolean");
156 if (metrics
->HasKey("mobile")) {
157 if (!metrics
->GetBoolean("mobile", &mobile
))
158 return Status(kUnknownError
, "'mobile' must be a boolean");
161 DeviceMetrics
* device_metrics
=
162 new DeviceMetrics(width
, height
, device_scale_factor
, touch
, mobile
);
163 capabilities
->device_metrics
=
164 scoped_ptr
<DeviceMetrics
>(device_metrics
);
167 if (mobile_emulation
->HasKey("userAgent")) {
168 std::string user_agent
;
169 if (!mobile_emulation
->GetString("userAgent", &user_agent
))
170 return Status(kUnknownError
, "'userAgent' must be a string");
172 capabilities
->switches
.SetSwitch("user-agent", user_agent
);
178 Status
ParseSwitches(const base::Value
& option
,
179 Capabilities
* capabilities
) {
180 const base::ListValue
* switches_list
= NULL
;
181 if (!option
.GetAsList(&switches_list
))
182 return Status(kUnknownError
, "must be a list");
183 for (size_t i
= 0; i
< switches_list
->GetSize(); ++i
) {
184 std::string arg_string
;
185 if (!switches_list
->GetString(i
, &arg_string
))
186 return Status(kUnknownError
, "each argument must be a string");
187 capabilities
->switches
.SetUnparsedSwitch(arg_string
);
192 Status
ParseExtensions(const base::Value
& option
, Capabilities
* capabilities
) {
193 const base::ListValue
* extensions
= NULL
;
194 if (!option
.GetAsList(&extensions
))
195 return Status(kUnknownError
, "must be a list");
196 for (size_t i
= 0; i
< extensions
->GetSize(); ++i
) {
197 std::string extension
;
198 if (!extensions
->GetString(i
, &extension
)) {
199 return Status(kUnknownError
,
200 "each extension must be a base64 encoded string");
202 capabilities
->extensions
.push_back(extension
);
207 Status
ParseProxy(const base::Value
& option
, Capabilities
* capabilities
) {
208 const base::DictionaryValue
* proxy_dict
;
209 if (!option
.GetAsDictionary(&proxy_dict
))
210 return Status(kUnknownError
, "must be a dictionary");
211 std::string proxy_type
;
212 if (!proxy_dict
->GetString("proxyType", &proxy_type
))
213 return Status(kUnknownError
, "'proxyType' must be a string");
214 proxy_type
= base::ToLowerASCII(proxy_type
);
215 if (proxy_type
== "direct") {
216 capabilities
->switches
.SetSwitch("no-proxy-server");
217 } else if (proxy_type
== "system") {
219 } else if (proxy_type
== "pac") {
220 base::CommandLine::StringType proxy_pac_url
;
221 if (!proxy_dict
->GetString("proxyAutoconfigUrl", &proxy_pac_url
))
222 return Status(kUnknownError
, "'proxyAutoconfigUrl' must be a string");
223 capabilities
->switches
.SetSwitch("proxy-pac-url", proxy_pac_url
);
224 } else if (proxy_type
== "autodetect") {
225 capabilities
->switches
.SetSwitch("proxy-auto-detect");
226 } else if (proxy_type
== "manual") {
227 const char* const proxy_servers_options
[][2] = {
228 {"ftpProxy", "ftp"}, {"httpProxy", "http"}, {"sslProxy", "https"}};
229 const base::Value
* option_value
= NULL
;
230 std::string proxy_servers
;
231 for (size_t i
= 0; i
< arraysize(proxy_servers_options
); ++i
) {
232 if (!proxy_dict
->Get(proxy_servers_options
[i
][0], &option_value
) ||
233 option_value
->IsType(base::Value::TYPE_NULL
)) {
237 if (!option_value
->GetAsString(&value
)) {
240 base::StringPrintf("'%s' must be a string",
241 proxy_servers_options
[i
][0]));
243 // Converts into Chrome proxy scheme.
244 // Example: "http=localhost:9000;ftp=localhost:8000".
245 if (!proxy_servers
.empty())
246 proxy_servers
+= ";";
247 proxy_servers
+= base::StringPrintf(
248 "%s=%s", proxy_servers_options
[i
][1], value
.c_str());
251 std::string proxy_bypass_list
;
252 if (proxy_dict
->Get("noProxy", &option_value
) &&
253 !option_value
->IsType(base::Value::TYPE_NULL
)) {
254 if (!option_value
->GetAsString(&proxy_bypass_list
))
255 return Status(kUnknownError
, "'noProxy' must be a string");
258 if (proxy_servers
.empty() && proxy_bypass_list
.empty()) {
259 return Status(kUnknownError
,
260 "proxyType is 'manual' but no manual "
261 "proxy capabilities were found");
263 if (!proxy_servers
.empty())
264 capabilities
->switches
.SetSwitch("proxy-server", proxy_servers
);
265 if (!proxy_bypass_list
.empty()) {
266 capabilities
->switches
.SetSwitch("proxy-bypass-list",
270 return Status(kUnknownError
, "unrecognized proxy type:" + proxy_type
);
275 Status
ParseExcludeSwitches(const base::Value
& option
,
276 Capabilities
* capabilities
) {
277 const base::ListValue
* switches
= NULL
;
278 if (!option
.GetAsList(&switches
))
279 return Status(kUnknownError
, "must be a list");
280 for (size_t i
= 0; i
< switches
->GetSize(); ++i
) {
281 std::string switch_name
;
282 if (!switches
->GetString(i
, &switch_name
)) {
283 return Status(kUnknownError
,
284 "each switch to be removed must be a string");
286 capabilities
->exclude_switches
.insert(switch_name
);
291 Status
ParseUseRemoteBrowser(const base::Value
& option
,
292 Capabilities
* capabilities
) {
293 std::string server_addr
;
294 if (!option
.GetAsString(&server_addr
))
295 return Status(kUnknownError
, "must be 'host:port'");
297 std::vector
<std::string
> values
= base::SplitString(
298 server_addr
, ":", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
299 if (values
.size() != 2)
300 return Status(kUnknownError
, "must be 'host:port'");
303 base::StringToInt(values
[1], &port
);
305 return Status(kUnknownError
, "port must be > 0");
307 capabilities
->debugger_address
= NetAddress(values
[0], port
);
311 Status
ParseLoggingPrefs(const base::Value
& option
,
312 Capabilities
* capabilities
) {
313 const base::DictionaryValue
* logging_prefs
= NULL
;
314 if (!option
.GetAsDictionary(&logging_prefs
))
315 return Status(kUnknownError
, "must be a dictionary");
317 for (base::DictionaryValue::Iterator
pref(*logging_prefs
);
318 !pref
.IsAtEnd(); pref
.Advance()) {
319 std::string type
= pref
.key();
321 std::string level_name
;
322 if (!pref
.value().GetAsString(&level_name
) ||
323 !WebDriverLog::NameToLevel(level_name
, &level
)) {
324 return Status(kUnknownError
, "invalid log level for '" + type
+ "' log");
326 capabilities
->logging_prefs
.insert(std::make_pair(type
, level
));
331 Status
ParseInspectorDomainStatus(
332 PerfLoggingPrefs::InspectorDomainStatus
* to_set
,
333 const base::Value
& option
,
334 Capabilities
* capabilities
) {
336 if (!option
.GetAsBoolean(&desired_value
))
337 return Status(kUnknownError
, "must be a boolean");
339 *to_set
= PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyEnabled
;
341 *to_set
= PerfLoggingPrefs::InspectorDomainStatus::kExplicitlyDisabled
;
345 Status
ParsePerfLoggingPrefs(const base::Value
& option
,
346 Capabilities
* capabilities
) {
347 const base::DictionaryValue
* perf_logging_prefs
= NULL
;
348 if (!option
.GetAsDictionary(&perf_logging_prefs
))
349 return Status(kUnknownError
, "must be a dictionary");
351 std::map
<std::string
, Parser
> parser_map
;
352 parser_map
["bufferUsageReportingInterval"] = base::Bind(&ParseInterval
,
353 &capabilities
->perf_logging_prefs
.buffer_usage_reporting_interval
);
354 parser_map
["enableNetwork"] = base::Bind(
355 &ParseInspectorDomainStatus
, &capabilities
->perf_logging_prefs
.network
);
356 parser_map
["enablePage"] = base::Bind(
357 &ParseInspectorDomainStatus
, &capabilities
->perf_logging_prefs
.page
);
358 parser_map
["enableTimeline"] = base::Bind(
359 &ParseInspectorDomainStatus
, &capabilities
->perf_logging_prefs
.timeline
);
360 parser_map
["traceCategories"] = base::Bind(
361 &ParseString
, &capabilities
->perf_logging_prefs
.trace_categories
);
363 for (base::DictionaryValue::Iterator
it(*perf_logging_prefs
); !it
.IsAtEnd();
365 if (parser_map
.find(it
.key()) == parser_map
.end())
366 return Status(kUnknownError
, "unrecognized performance logging "
367 "option: " + it
.key());
368 Status status
= parser_map
[it
.key()].Run(it
.value(), capabilities
);
369 if (status
.IsError())
370 return Status(kUnknownError
, "cannot parse " + it
.key(), status
);
375 Status
ParseChromeOptions(
376 const base::Value
& capability
,
377 Capabilities
* capabilities
) {
378 const base::DictionaryValue
* chrome_options
= NULL
;
379 if (!capability
.GetAsDictionary(&chrome_options
))
380 return Status(kUnknownError
, "must be a dictionary");
382 bool is_android
= chrome_options
->HasKey("androidPackage");
383 bool is_remote
= chrome_options
->HasKey("debuggerAddress");
385 std::map
<std::string
, Parser
> parser_map
;
386 // Ignore 'args', 'binary' and 'extensions' capabilities by default, since the
387 // Java client always passes them.
388 parser_map
["args"] = base::Bind(&IgnoreCapability
);
389 parser_map
["binary"] = base::Bind(&IgnoreCapability
);
390 parser_map
["extensions"] = base::Bind(&IgnoreCapability
);
392 parser_map
["perfLoggingPrefs"] = base::Bind(&ParsePerfLoggingPrefs
);
395 parser_map
["androidActivity"] =
396 base::Bind(&ParseString
, &capabilities
->android_activity
);
397 parser_map
["androidDeviceSerial"] =
398 base::Bind(&ParseString
, &capabilities
->android_device_serial
);
399 parser_map
["androidPackage"] =
400 base::Bind(&ParseString
, &capabilities
->android_package
);
401 parser_map
["androidProcess"] =
402 base::Bind(&ParseString
, &capabilities
->android_process
);
403 parser_map
["androidUseRunningApp"] =
404 base::Bind(&ParseBoolean
, &capabilities
->android_use_running_app
);
405 parser_map
["args"] = base::Bind(&ParseSwitches
);
406 parser_map
["excludeSwitches"] = base::Bind(&ParseExcludeSwitches
);
407 parser_map
["loadAsync"] = base::Bind(&IgnoreDeprecatedOption
, "loadAsync");
408 } else if (is_remote
) {
409 parser_map
["debuggerAddress"] = base::Bind(&ParseUseRemoteBrowser
);
411 parser_map
["args"] = base::Bind(&ParseSwitches
);
412 parser_map
["binary"] = base::Bind(&ParseFilePath
, &capabilities
->binary
);
413 parser_map
["detach"] = base::Bind(&ParseBoolean
, &capabilities
->detach
);
414 parser_map
["excludeSwitches"] = base::Bind(&ParseExcludeSwitches
);
415 parser_map
["extensions"] = base::Bind(&ParseExtensions
);
416 parser_map
["forceDevToolsScreenshot"] = base::Bind(
417 &ParseBoolean
, &capabilities
->force_devtools_screenshot
);
418 parser_map
["loadAsync"] = base::Bind(&IgnoreDeprecatedOption
, "loadAsync");
419 parser_map
["localState"] =
420 base::Bind(&ParseDict
, &capabilities
->local_state
);
421 parser_map
["logPath"] = base::Bind(&ParseLogPath
);
422 parser_map
["minidumpPath"] =
423 base::Bind(&ParseString
, &capabilities
->minidump_path
);
424 parser_map
["mobileEmulation"] = base::Bind(&ParseMobileEmulation
);
425 parser_map
["prefs"] = base::Bind(&ParseDict
, &capabilities
->prefs
);
428 for (base::DictionaryValue::Iterator
it(*chrome_options
); !it
.IsAtEnd();
430 if (parser_map
.find(it
.key()) == parser_map
.end()) {
431 return Status(kUnknownError
,
432 "unrecognized chrome option: " + it
.key());
434 Status status
= parser_map
[it
.key()].Run(it
.value(), capabilities
);
435 if (status
.IsError())
436 return Status(kUnknownError
, "cannot parse " + it
.key(), status
);
443 Switches::Switches() {}
445 Switches::~Switches() {}
447 void Switches::SetSwitch(const std::string
& name
) {
448 SetSwitch(name
, NativeString());
451 void Switches::SetSwitch(const std::string
& name
, const std::string
& value
) {
453 SetSwitch(name
, base::UTF8ToUTF16(value
));
455 switch_map_
[name
] = value
;
459 void Switches::SetSwitch(const std::string
& name
, const base::string16
& value
) {
461 switch_map_
[name
] = value
;
463 SetSwitch(name
, base::UTF16ToUTF8(value
));
467 void Switches::SetSwitch(const std::string
& name
, const base::FilePath
& value
) {
468 SetSwitch(name
, value
.value());
471 void Switches::SetFromSwitches(const Switches
& switches
) {
472 for (SwitchMap::const_iterator iter
= switches
.switch_map_
.begin();
473 iter
!= switches
.switch_map_
.end();
475 switch_map_
[iter
->first
] = iter
->second
;
479 void Switches::SetUnparsedSwitch(const std::string
& unparsed_switch
) {
481 size_t equals_index
= unparsed_switch
.find('=');
482 if (equals_index
!= std::string::npos
)
483 value
= unparsed_switch
.substr(equals_index
+ 1);
486 size_t start_index
= 0;
487 if (unparsed_switch
.substr(0, 2) == "--")
489 name
= unparsed_switch
.substr(start_index
, equals_index
- start_index
);
491 SetSwitch(name
, value
);
494 void Switches::RemoveSwitch(const std::string
& name
) {
495 switch_map_
.erase(name
);
498 bool Switches::HasSwitch(const std::string
& name
) const {
499 return switch_map_
.count(name
) > 0;
502 std::string
Switches::GetSwitchValue(const std::string
& name
) const {
503 NativeString value
= GetSwitchValueNative(name
);
505 return base::UTF16ToUTF8(value
);
511 Switches::NativeString
Switches::GetSwitchValueNative(
512 const std::string
& name
) const {
513 SwitchMap::const_iterator iter
= switch_map_
.find(name
);
514 if (iter
== switch_map_
.end())
515 return NativeString();
519 size_t Switches::GetSize() const {
520 return switch_map_
.size();
523 void Switches::AppendToCommandLine(base::CommandLine
* command
) const {
524 for (SwitchMap::const_iterator iter
= switch_map_
.begin();
525 iter
!= switch_map_
.end();
527 command
->AppendSwitchNative(iter
->first
, iter
->second
);
531 std::string
Switches::ToString() const {
533 SwitchMap::const_iterator iter
= switch_map_
.begin();
534 while (iter
!= switch_map_
.end()) {
535 str
+= "--" + iter
->first
;
536 std::string value
= GetSwitchValue(iter
->first
);
537 if (value
.length()) {
538 if (value
.find(' ') != std::string::npos
)
539 value
= base::GetQuotedJSONString(value
);
543 if (iter
== switch_map_
.end())
550 PerfLoggingPrefs::PerfLoggingPrefs()
551 : network(InspectorDomainStatus::kDefaultEnabled
),
552 page(InspectorDomainStatus::kDefaultEnabled
),
553 timeline(InspectorDomainStatus::kDefaultDisabled
),
555 buffer_usage_reporting_interval(1000) {}
557 PerfLoggingPrefs::~PerfLoggingPrefs() {}
559 Capabilities::Capabilities()
560 : android_use_running_app(false),
562 force_devtools_screenshot(false) {}
564 Capabilities::~Capabilities() {}
566 bool Capabilities::IsAndroid() const {
567 return !android_package
.empty();
570 bool Capabilities::IsRemoteBrowser() const {
571 return debugger_address
.IsValid();
574 Status
Capabilities::Parse(const base::DictionaryValue
& desired_caps
) {
575 std::map
<std::string
, Parser
> parser_map
;
576 parser_map
["chromeOptions"] = base::Bind(&ParseChromeOptions
);
577 parser_map
["loggingPrefs"] = base::Bind(&ParseLoggingPrefs
);
578 parser_map
["proxy"] = base::Bind(&ParseProxy
);
579 for (std::map
<std::string
, Parser
>::iterator it
= parser_map
.begin();
580 it
!= parser_map
.end(); ++it
) {
581 const base::Value
* capability
= NULL
;
582 if (desired_caps
.Get(it
->first
, &capability
)) {
583 Status status
= it
->second
.Run(*capability
, this);
584 if (status
.IsError()) {
586 kUnknownError
, "cannot parse capability: " + it
->first
, status
);
590 // Perf log must be enabled if perf log prefs are specified; otherwise, error.
591 LoggingPrefs::const_iterator iter
= logging_prefs
.find(
592 WebDriverLog::kPerformanceType
);
593 if (iter
== logging_prefs
.end() || iter
->second
== Log::kOff
) {
594 const base::DictionaryValue
* chrome_options
= NULL
;
595 if (desired_caps
.GetDictionary("chromeOptions", &chrome_options
) &&
596 chrome_options
->HasKey("perfLoggingPrefs")) {
597 return Status(kUnknownError
, "perfLoggingPrefs specified, "
598 "but performance logging was not enabled");