Commit 88694263 authored by John Chen's avatar John Chen Committed by Commit Bot

[ChromeDriver] W3C compliant unknown capability handling

Per W3C spec, unrecognized capabilities should trigger invalid argument
error, instead of being silently ignored.

Bug: chromedriver:1997
Change-Id: I84b2a888ad97d709737368b9394c8efdd475bf3a
Reviewed-on: https://chromium-review.googlesource.com/c/1325562Reviewed-by: default avatarCaleb Rouleau <crouleau@chromium.org>
Commit-Queue: John Chen <johnchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606733}
parent a6da6f60
...@@ -713,7 +713,8 @@ bool Capabilities::IsRemoteBrowser() const { ...@@ -713,7 +713,8 @@ bool Capabilities::IsRemoteBrowser() const {
return debugger_address.IsValid(); return debugger_address.IsValid();
} }
Status Capabilities::Parse(const base::DictionaryValue& desired_caps) { Status Capabilities::Parse(const base::DictionaryValue& desired_caps,
bool w3c_compliant) {
std::map<std::string, Parser> parser_map; std::map<std::string, Parser> parser_map;
// W3C defined capabilities. // W3C defined capabilities.
...@@ -727,18 +728,21 @@ Status Capabilities::Parse(const base::DictionaryValue& desired_caps) { ...@@ -727,18 +728,21 @@ Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
parser_map["pageLoadStrategy"] = base::BindRepeating(&ParsePageLoadStrategy); parser_map["pageLoadStrategy"] = base::BindRepeating(&ParsePageLoadStrategy);
parser_map["proxy"] = base::BindRepeating(&ParseProxy); parser_map["proxy"] = base::BindRepeating(&ParseProxy);
parser_map["timeouts"] = base::BindRepeating(&ParseTimeouts); parser_map["timeouts"] = base::BindRepeating(&ParseTimeouts);
// TODO(https://crbug.com/chromedriver/2596): "unexpectedAlertBehaviour" is if (!w3c_compliant) {
// legacy name of "unhandledPromptBehavior", remove when we stop supporting // TODO(https://crbug.com/chromedriver/2596): "unexpectedAlertBehaviour" is
// legacy mode. // legacy name of "unhandledPromptBehavior", remove when we stop supporting
parser_map["unexpectedAlertBehaviour"] = // legacy mode.
base::BindRepeating(&ParseUnhandledPromptBehavior); parser_map["unexpectedAlertBehaviour"] =
base::BindRepeating(&ParseUnhandledPromptBehavior);
}
parser_map["unhandledPromptBehavior"] = parser_map["unhandledPromptBehavior"] =
base::BindRepeating(&ParseUnhandledPromptBehavior); base::BindRepeating(&ParseUnhandledPromptBehavior);
// ChromeDriver specific capabilities. // ChromeDriver specific capabilities.
// goog:chromeOptions is the current spec conformance, but chromeOptions is // goog:chromeOptions is the current spec conformance, but chromeOptions is
// still supported // still supported in legacy mode.
if (desired_caps.GetDictionary("goog:chromeOptions", nullptr)) { if (w3c_compliant ||
desired_caps.GetDictionary("goog:chromeOptions", nullptr)) {
parser_map["goog:chromeOptions"] = base::BindRepeating(&ParseChromeOptions); parser_map["goog:chromeOptions"] = base::BindRepeating(&ParseChromeOptions);
} else { } else {
parser_map["chromeOptions"] = base::BindRepeating(&ParseChromeOptions); parser_map["chromeOptions"] = base::BindRepeating(&ParseChromeOptions);
...@@ -752,15 +756,24 @@ Status Capabilities::Parse(const base::DictionaryValue& desired_caps) { ...@@ -752,15 +756,24 @@ Status Capabilities::Parse(const base::DictionaryValue& desired_caps) {
parser_map["networkConnectionEnabled"] = parser_map["networkConnectionEnabled"] =
base::BindRepeating(&ParseBoolean, &network_emulation_enabled); base::BindRepeating(&ParseBoolean, &network_emulation_enabled);
} }
// goog:testName is set by some tests to help debugging, and is ignored.
parser_map["goog:testName"] = base::BindRepeating(&IgnoreCapability);
for (auto it = parser_map.begin(); it != parser_map.end(); ++it) { for (base::DictionaryValue::Iterator it(desired_caps); !it.IsAtEnd();
const base::Value* capability = NULL; it.Advance()) {
if (desired_caps.Get(it->first, &capability)) { if (parser_map.find(it.key()) == parser_map.end()) {
Status status = it->second.Run(*capability, this); // The specified capability is unrecognized. W3C spec requires us to
if (status.IsError()) { // return an error. In legacy mode, for backward compatibility reasons,
return Status(kInvalidArgument, "cannot parse capability: " + it->first, // we ignore unrecognized capabilities.
status); if (w3c_compliant)
} return Status(kInvalidArgument, "unrecognized capability: " + it.key());
else
continue;
}
Status status = parser_map[it.key()].Run(it.value(), this);
if (status.IsError()) {
return Status(kInvalidArgument, "cannot parse capability: " + it.key(),
status);
} }
} }
// Perf log must be enabled if perf log prefs are specified; otherwise, error. // Perf log must be enabled if perf log prefs are specified; otherwise, error.
......
...@@ -99,7 +99,8 @@ struct Capabilities { ...@@ -99,7 +99,8 @@ struct Capabilities {
// Accepts all W3C defined capabilities (including those not yet supported by // Accepts all W3C defined capabilities (including those not yet supported by
// ChromeDriver) and all ChromeDriver-specific extensions. // ChromeDriver) and all ChromeDriver-specific extensions.
Status Parse(const base::DictionaryValue& desired_caps); Status Parse(const base::DictionaryValue& desired_caps,
bool w3c_compliant = true);
// Check if all specified capabilities are supported by ChromeDriver. // Check if all specified capabilities are supported by ChromeDriver.
// The long term goal is to support all standard capabilities, thus making // The long term goal is to support all standard capabilities, thus making
......
...@@ -102,6 +102,24 @@ TEST(Switches, Unparsed) { ...@@ -102,6 +102,24 @@ TEST(Switches, Unparsed) {
ASSERT_EQ("---e=--1=1 --a --b --c=1 --d=1", switches.ToString()); ASSERT_EQ("---e=--1=1 --a --b --c=1 --d=1", switches.ToString());
} }
TEST(ParseCapabilities, UnknownCapabilityLegacy) {
// In legacy mode, unknown capabilities are ignored.
Capabilities capabilities;
base::DictionaryValue caps;
caps.SetString("foo", "bar");
Status status = capabilities.Parse(caps, false);
ASSERT_TRUE(status.IsOk());
}
TEST(ParseCapabilities, UnknownCapabilityW3c) {
// In W3C mode, unknown capabilities results in error.
Capabilities capabilities;
base::DictionaryValue caps;
caps.SetString("foo", "bar");
Status status = capabilities.Parse(caps);
ASSERT_EQ(status.code(), kInvalidArgument);
}
TEST(ParseCapabilities, WithAndroidPackage) { TEST(ParseCapabilities, WithAndroidPackage) {
Capabilities capabilities; Capabilities capabilities;
base::DictionaryValue caps; base::DictionaryValue caps;
......
...@@ -228,7 +228,7 @@ class ChromeDriver(object): ...@@ -228,7 +228,7 @@ class ChromeDriver(object):
options['w3c'] = send_w3c_capability options['w3c'] = send_w3c_capability
params = { params = {
'chromeOptions': options, 'goog:chromeOptions': options,
'loggingPrefs': logging_prefs 'loggingPrefs': logging_prefs
} }
...@@ -238,7 +238,10 @@ class ChromeDriver(object): ...@@ -238,7 +238,10 @@ class ChromeDriver(object):
if unexpected_alert_behaviour: if unexpected_alert_behaviour:
assert type(unexpected_alert_behaviour) is str assert type(unexpected_alert_behaviour) is str
params['unexpectedAlertBehaviour'] = unexpected_alert_behaviour if send_w3c_request:
params['unhandledPromptBehavior'] = unexpected_alert_behaviour
else:
params['unexpectedAlertBehaviour'] = unexpected_alert_behaviour
if network_connection: if network_connection:
params['networkConnectionEnabled'] = network_connection params['networkConnectionEnabled'] = network_connection
......
...@@ -353,22 +353,22 @@ def _ReplaceBinary(payload, binary): ...@@ -353,22 +353,22 @@ def _ReplaceBinary(payload, binary):
trigger ChromeDriver's mechanism for locating the Chrome binary. trigger ChromeDriver's mechanism for locating the Chrome binary.
""" """
if ("desiredCapabilities" in payload if ("desiredCapabilities" in payload
and "chromeOptions" in payload["desiredCapabilities"]): and "goog:chromeOptions" in payload["desiredCapabilities"]):
if binary: if binary:
(payload["desiredCapabilities"]["chromeOptions"] (payload["desiredCapabilities"]["goog:chromeOptions"]
["binary"]) = binary ["binary"]) = binary
elif "binary" in payload["desiredCapabilities"]["chromeOptions"]: elif "binary" in payload["desiredCapabilities"]["goog:chromeOptions"]:
del payload["desiredCapabilities"]["chromeOptions"]["binary"] del payload["desiredCapabilities"]["goog:chromeOptions"]["binary"]
elif binary: elif binary:
if "desiredCapabilities" not in payload: if "desiredCapabilities" not in payload:
payload["desiredCapabilities"] = { payload["desiredCapabilities"] = {
"chromeOptions": { "goog:chromeOptions": {
"binary": binary "binary": binary
} }
} }
elif "chromeOptions" not in payload["desiredCapabilities"]: elif "goog:chromeOptions" not in payload["desiredCapabilities"]:
payload["desiredCapabilities"]["chromeOptions"] = { payload["desiredCapabilities"]["goog:chromeOptions"] = {
"binary": binary "binary": binary
} }
...@@ -399,7 +399,7 @@ class _Payload(object): ...@@ -399,7 +399,7 @@ class _Payload(object):
word InitSession: word InitSession:
[1532467931.153][INFO]: [<session_id>] COMMAND InitSession { [1532467931.153][INFO]: [<session_id>] COMMAND InitSession {
"desiredCapabilities": { "desiredCapabilities": {
"chromeOptions": { "goog:chromeOptions": {
"args": [ "no-sandbox", "disable-gpu" ], "args": [ "no-sandbox", "disable-gpu" ],
"binary": "<binary_path>" "binary": "<binary_path>"
} }
......
...@@ -215,7 +215,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase): ...@@ -215,7 +215,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase):
def testReplaceBinary(self): def testReplaceBinary(self):
payload_dict = { payload_dict = {
"desiredCapabilities": { "desiredCapabilities": {
"chromeOptions": { "goog:chromeOptions": {
"binary": "/path/to/logged binary/with spaces/" "binary": "/path/to/logged binary/with spaces/"
}, },
"other_things": ["some", "uninteresting", "strings"] "other_things": ["some", "uninteresting", "strings"]
...@@ -223,7 +223,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase): ...@@ -223,7 +223,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase):
} }
payload_replaced = { payload_replaced = {
"desiredCapabilities": { "desiredCapabilities": {
"chromeOptions": { "goog:chromeOptions": {
"binary": "replacement_binary" "binary": "replacement_binary"
}, },
"other_things": ["some", "uninteresting", "strings"] "other_things": ["some", "uninteresting", "strings"]
...@@ -235,7 +235,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase): ...@@ -235,7 +235,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase):
def testReplaceBinary_none(self): def testReplaceBinary_none(self):
payload_dict = { payload_dict = {
"desiredCapabilities": { "desiredCapabilities": {
"chromeOptions": { "goog:chromeOptions": {
"binary": "/path/to/logged binary/with spaces/" "binary": "/path/to/logged binary/with spaces/"
}, },
"other_things": ["some", "uninteresting", "strings"] "other_things": ["some", "uninteresting", "strings"]
...@@ -243,7 +243,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase): ...@@ -243,7 +243,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase):
} }
payload_replaced = { payload_replaced = {
"desiredCapabilities": { "desiredCapabilities": {
"chromeOptions": {}, "goog:chromeOptions": {},
"other_things": ["some", "uninteresting", "strings"] "other_things": ["some", "uninteresting", "strings"]
} }
} }
...@@ -254,7 +254,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase): ...@@ -254,7 +254,7 @@ class ChromeDriverClientReplayUnitTest(unittest.TestCase):
payload_dict = {"desiredCapabilities": {}} payload_dict = {"desiredCapabilities": {}}
payload_replaced = { payload_replaced = {
"desiredCapabilities": { "desiredCapabilities": {
"chromeOptions": { "goog:chromeOptions": {
"binary": "replacement_binary" "binary": "replacement_binary"
} }
} }
......
...@@ -231,7 +231,7 @@ Status InitSessionHelper(const InitSessionParams& bound_params, ...@@ -231,7 +231,7 @@ Status InitSessionHelper(const InitSessionParams& bound_params,
} }
Capabilities capabilities; Capabilities capabilities;
Status status = capabilities.Parse(*desired_caps); Status status = capabilities.Parse(*desired_caps, session->w3c_compliant);
if (status.IsError()) if (status.IsError())
return status; return status;
status = capabilities.CheckSupport(); status = capabilities.CheckSupport();
......
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