| // 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/session_commands.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/run_loop.h" |
| #include "base/threading/thread.h" |
| #include "base/values.h" |
| #include "chrome/test/chromedriver/chrome/status.h" |
| #include "chrome/test/chromedriver/chrome/stub_chrome.h" |
| #include "chrome/test/chromedriver/commands.h" |
| #include "chrome/test/chromedriver/session.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| TEST(SessionCommandsTest, ExecuteGetTimeouts) { |
| Session session("id"); |
| base::DictionaryValue params; |
| std::unique_ptr<base::Value> value; |
| |
| Status status = ExecuteGetTimeouts(&session, params, &value); |
| ASSERT_EQ(kOk, status.code()); |
| base::DictionaryValue* response; |
| ASSERT_TRUE(value->GetAsDictionary(&response)); |
| |
| int script; |
| ASSERT_TRUE(response->GetInteger("script", &script)); |
| ASSERT_EQ(script, 30000); |
| int page_load; |
| ASSERT_TRUE(response->GetInteger("pageLoad", &page_load)); |
| ASSERT_EQ(page_load, 300000); |
| int implicit; |
| ASSERT_TRUE(response->GetInteger("implicit", &implicit)); |
| ASSERT_EQ(implicit, 0); |
| } |
| |
| TEST(SessionCommandsTest, ExecuteSetTimeouts) { |
| Session session("id"); |
| base::DictionaryValue params; |
| std::unique_ptr<base::Value> value; |
| |
| // W3C spec doesn't forbid passing in an empty object, so we should get kOk. |
| Status status = ExecuteSetTimeouts(&session, params, &value); |
| ASSERT_EQ(kOk, status.code()); |
| |
| params.SetInteger("pageLoad", 5000); |
| status = ExecuteSetTimeouts(&session, params, &value); |
| ASSERT_EQ(kOk, status.code()); |
| |
| params.SetInteger("script", 5000); |
| params.SetInteger("implicit", 5000); |
| status = ExecuteSetTimeouts(&session, params, &value); |
| ASSERT_EQ(kOk, status.code()); |
| |
| params.SetInteger("implicit", -5000); |
| status = ExecuteSetTimeouts(&session, params, &value); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| params.Clear(); |
| params.SetInteger("unknown", 5000); |
| status = ExecuteSetTimeouts(&session, params, &value); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // Old pre-W3C format. |
| params.Clear(); |
| params.SetDouble("ms", 5000.0); |
| params.SetString("type", "page load"); |
| status = ExecuteSetTimeouts(&session, params, &value); |
| ASSERT_EQ(kOk, status.code()); |
| } |
| |
| TEST(SessionCommandsTest, MergeCapabilities) { |
| base::DictionaryValue primary; |
| primary.SetString("strawberry", "velociraptor"); |
| primary.SetString("pear", "unicorn"); |
| |
| base::DictionaryValue secondary; |
| secondary.SetString("broccoli", "giraffe"); |
| secondary.SetString("celery", "hippo"); |
| secondary.SetString("eggplant", "elephant"); |
| |
| base::DictionaryValue merged; |
| |
| // key collision should return false |
| ASSERT_FALSE(MergeCapabilities(&primary, &primary, &merged)); |
| // non key collision should return true |
| ASSERT_TRUE(MergeCapabilities(&primary, &secondary, &merged)); |
| |
| merged.Clear(); |
| MergeCapabilities(&primary, &secondary, &merged); |
| primary.MergeDictionary(&secondary); |
| |
| ASSERT_EQ(primary, merged); |
| } |
| |
| TEST(SessionCommandsTest, ProcessCapabilities_Empty) { |
| // "capabilities" is required |
| base::DictionaryValue params; |
| base::DictionaryValue result; |
| Status status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // "capabilities" must be a JSON object |
| params.SetList("capabilities", std::make_unique<base::ListValue>()); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // Empty "capabilities" is OK |
| params.SetDictionary("capabilities", |
| std::make_unique<base::DictionaryValue>()); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_TRUE(result.empty()); |
| } |
| |
| TEST(SessionCommandsTest, ProcessCapabilities_AlwaysMatch) { |
| base::DictionaryValue params; |
| base::DictionaryValue result; |
| |
| // "alwaysMatch" must be a JSON object |
| params.SetList("capabilities.alwaysMatch", |
| std::make_unique<base::ListValue>()); |
| Status status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // Empty "alwaysMatch" is OK |
| params.SetDictionary("capabilities.alwaysMatch", |
| std::make_unique<base::DictionaryValue>()); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_TRUE(result.empty()); |
| |
| // Invalid "alwaysMatch" |
| params.SetInteger("capabilities.alwaysMatch.browserName", 10); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // Valid "alwaysMatch" |
| params.SetString("capabilities.alwaysMatch.browserName", "chrome"); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_EQ(result.size(), 1u); |
| std::string result_string; |
| ASSERT_TRUE(result.GetString("browserName", &result_string)); |
| ASSERT_EQ(result_string, "chrome"); |
| |
| // Null "browserName" treated as not specifying "browserName" |
| params.SetPath({"capabilities", "alwaysMatch", "browserName"}, base::Value()); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_FALSE(result.GetString("browserName", &result_string)); |
| } |
| |
| TEST(SessionCommandsTest, ProcessCapabilities_FirstMatch) { |
| base::DictionaryValue params; |
| base::DictionaryValue result; |
| |
| // "firstMatch" must be a JSON list |
| params.SetDictionary("capabilities.firstMatch", |
| std::make_unique<base::DictionaryValue>()); |
| Status status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // "firstMatch" must have at least one entry |
| params.SetList("capabilities.firstMatch", |
| std::make_unique<base::ListValue>()); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // Each entry must be a JSON object |
| base::ListValue* list_ptr; |
| ASSERT_TRUE(params.GetList("capabilities.firstMatch", &list_ptr)); |
| list_ptr->Set(0, std::make_unique<base::ListValue>()); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // Empty JSON object allowed as an entry |
| list_ptr->Set(0, std::make_unique<base::DictionaryValue>()); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_TRUE(result.empty()); |
| |
| // Invalid entry |
| base::DictionaryValue* entry_ptr; |
| ASSERT_TRUE(list_ptr->GetDictionary(0, &entry_ptr)); |
| entry_ptr->SetString("pageLoadStrategy", "invalid"); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // Valid entry |
| entry_ptr->SetString("pageLoadStrategy", "eager"); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_EQ(result.size(), 1u); |
| std::string result_string; |
| ASSERT_TRUE(result.GetString("pageLoadStrategy", &result_string)); |
| ASSERT_EQ(result_string, "eager"); |
| |
| // Multiple entries, the first one should be selected. |
| list_ptr->Set(1, std::make_unique<base::DictionaryValue>()); |
| ASSERT_TRUE(list_ptr->GetDictionary(1, &entry_ptr)); |
| entry_ptr->SetString("pageLoadStrategy", "normal"); |
| entry_ptr->SetString("browserName", "chrome"); |
| status = ProcessCapabilities(params, &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_EQ(result.size(), 1u); |
| ASSERT_TRUE(result.GetString("pageLoadStrategy", &result_string)); |
| ASSERT_EQ(result_string, "eager"); |
| } |
| |
| namespace { |
| |
| Status ProcessCapabilitiesJson(const std::string& paramsJson, |
| base::DictionaryValue* result_capabilities) { |
| std::unique_ptr<base::Value> params = |
| base::JSONReader::ReadDeprecated(paramsJson); |
| if (!params || !params->is_dict()) |
| return Status(kUnknownError); |
| return ProcessCapabilities( |
| *static_cast<const base::DictionaryValue*>(params.get()), |
| result_capabilities); |
| } |
| |
| } // namespace |
| |
| TEST(SessionCommandsTest, ProcessCapabilities_Merge) { |
| base::DictionaryValue result; |
| Status status(kOk); |
| |
| // Disallow setting same capability in alwaysMatch and firstMatch |
| status = ProcessCapabilitiesJson( |
| R"({ |
| "capabilities": { |
| "alwaysMatch": { "pageLoadStrategy": "normal" }, |
| "firstMatch": [ |
| { "unhandledPromptBehavior": "accept" }, |
| { "pageLoadStrategy": "normal" } |
| ] |
| } |
| })", |
| &result); |
| ASSERT_EQ(kInvalidArgument, status.code()); |
| |
| // No conflicts between alwaysMatch and firstMatch, select first firstMatch |
| status = ProcessCapabilitiesJson( |
| R"({ |
| "capabilities": { |
| "alwaysMatch": { "timeouts": { } }, |
| "firstMatch": [ |
| { "unhandledPromptBehavior": "accept" }, |
| { "pageLoadStrategy": "normal" } |
| ] |
| } |
| })", |
| &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_EQ(result.size(), 2u); |
| ASSERT_TRUE(result.HasKey("timeouts")); |
| ASSERT_TRUE(result.HasKey("unhandledPromptBehavior")); |
| ASSERT_FALSE(result.HasKey("pageLoadStrategy")); |
| |
| // Selection by browserName |
| status = ProcessCapabilitiesJson( |
| R"({ |
| "capabilities": { |
| "alwaysMatch": { "timeouts": { } }, |
| "firstMatch": [ |
| { "browserName": "firefox", "unhandledPromptBehavior": "accept" }, |
| { "browserName": "chrome", "pageLoadStrategy": "normal" } |
| ] |
| } |
| })", |
| &result); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| ASSERT_EQ(result.size(), 3u); |
| ASSERT_TRUE(result.HasKey("timeouts")); |
| ASSERT_EQ(result.FindKey("browserName")->GetString(), "chrome"); |
| ASSERT_FALSE(result.HasKey("unhandledPromptBehavior")); |
| ASSERT_TRUE(result.HasKey("pageLoadStrategy")); |
| |
| // No acceptable firstMatch |
| status = ProcessCapabilitiesJson( |
| R"({ |
| "capabilities": { |
| "alwaysMatch": { "timeouts": { } }, |
| "firstMatch": [ |
| { "browserName": "firefox", "unhandledPromptBehavior": "accept" }, |
| { "browserName": "edge", "pageLoadStrategy": "normal" } |
| ] |
| } |
| })", |
| &result); |
| ASSERT_EQ(kSessionNotCreated, status.code()); |
| } |
| |
| TEST(SessionCommandsTest, FileUpload) { |
| Session session("id"); |
| base::DictionaryValue params; |
| std::unique_ptr<base::Value> value; |
| // Zip file entry that contains a single file with contents 'COW\n', base64 |
| // encoded following RFC 1521. |
| const char kBase64ZipEntry[] = |
| "UEsDBBQAAAAAAMROi0K/wAzGBAAAAAQAAAADAAAAbW9vQ09XClBLAQIUAxQAAAAAAMROi0K/" |
| "wAzG\nBAAAAAQAAAADAAAAAAAAAAAAAACggQAAAABtb29QSwUGAAAAAAEAAQAxAAAAJQAAAA" |
| "AA\n"; |
| params.SetString("file", kBase64ZipEntry); |
| Status status = ExecuteUploadFile(&session, params, &value); |
| ASSERT_EQ(kOk, status.code()) << status.message(); |
| base::FilePath::StringType path; |
| ASSERT_TRUE(value->GetAsString(&path)); |
| ASSERT_TRUE(base::PathExists(base::FilePath(path))); |
| std::string data; |
| ASSERT_TRUE(base::ReadFileToString(base::FilePath(path), &data)); |
| ASSERT_STREQ("COW\n", data.c_str()); |
| } |
| |
| namespace { |
| |
| class DetachChrome : public StubChrome { |
| public: |
| DetachChrome() : quit_called_(false) {} |
| ~DetachChrome() override {} |
| |
| // Overridden from Chrome: |
| Status Quit() override { |
| quit_called_ = true; |
| return Status(kOk); |
| } |
| |
| bool quit_called_; |
| }; |
| |
| } // namespace |
| |
| TEST(SessionCommandsTest, MatchCapabilities) { |
| base::DictionaryValue merged; |
| merged.SetString("browserName", "not chrome"); |
| |
| ASSERT_FALSE(MatchCapabilities(&merged)); |
| |
| merged.Clear(); |
| merged.SetString("browserName", "chrome"); |
| |
| ASSERT_TRUE(MatchCapabilities(&merged)); |
| } |
| |
| TEST(SessionCommandsTest, Quit) { |
| DetachChrome* chrome = new DetachChrome(); |
| Session session("id", std::unique_ptr<Chrome>(chrome)); |
| |
| base::DictionaryValue params; |
| std::unique_ptr<base::Value> value; |
| |
| ASSERT_EQ(kOk, ExecuteQuit(false, &session, params, &value).code()); |
| ASSERT_TRUE(chrome->quit_called_); |
| |
| chrome->quit_called_ = false; |
| ASSERT_EQ(kOk, ExecuteQuit(true, &session, params, &value).code()); |
| ASSERT_TRUE(chrome->quit_called_); |
| } |
| |
| TEST(SessionCommandsTest, QuitWithDetach) { |
| DetachChrome* chrome = new DetachChrome(); |
| Session session("id", std::unique_ptr<Chrome>(chrome)); |
| session.detach = true; |
| |
| base::DictionaryValue params; |
| std::unique_ptr<base::Value> value; |
| |
| ASSERT_EQ(kOk, ExecuteQuit(true, &session, params, &value).code()); |
| ASSERT_FALSE(chrome->quit_called_); |
| |
| ASSERT_EQ(kOk, ExecuteQuit(false, &session, params, &value).code()); |
| ASSERT_TRUE(chrome->quit_called_); |
| } |
| |
| namespace { |
| |
| class FailsToQuitChrome : public StubChrome { |
| public: |
| FailsToQuitChrome() {} |
| ~FailsToQuitChrome() override {} |
| |
| // Overridden from Chrome: |
| Status Quit() override { return Status(kUnknownError); } |
| }; |
| |
| } // namespace |
| |
| TEST(SessionCommandsTest, QuitFails) { |
| Session session("id", std::unique_ptr<Chrome>(new FailsToQuitChrome())); |
| base::DictionaryValue params; |
| std::unique_ptr<base::Value> value; |
| ASSERT_EQ(kUnknownError, ExecuteQuit(false, &session, params, &value).code()); |
| } |
| |
| TEST(SessionCommandsTest, AutoReporting) { |
| DetachChrome* chrome = new DetachChrome(); |
| Session session("id", std::unique_ptr<Chrome>(chrome)); |
| base::DictionaryValue params; |
| std::unique_ptr<base::Value> value; |
| StatusCode status_code; |
| bool enabled; |
| |
| // autoreporting should be disabled by default |
| status_code = ExecuteIsAutoReporting(&session, params, &value).code(); |
| ASSERT_EQ(kOk, status_code); |
| ASSERT_FALSE(session.auto_reporting_enabled); |
| ASSERT_TRUE(value.get()->GetAsBoolean(&enabled)); |
| ASSERT_FALSE(enabled); |
| |
| // an error should be given if the |enabled| parameter is not set |
| status_code = ExecuteSetAutoReporting(&session, params, &value).code(); |
| ASSERT_EQ(kInvalidArgument, status_code); |
| |
| // try to enable autoreporting |
| params.SetBoolean("enabled", true); |
| status_code = ExecuteSetAutoReporting(&session, params, &value).code(); |
| ASSERT_EQ(kOk, status_code); |
| ASSERT_TRUE(session.auto_reporting_enabled); |
| |
| // check that autoreporting was enabled successfully |
| status_code = ExecuteIsAutoReporting(&session, params, &value).code(); |
| ASSERT_EQ(kOk, status_code); |
| ASSERT_TRUE(value.get()->GetAsBoolean(&enabled)); |
| ASSERT_TRUE(enabled); |
| |
| // try to disable autoreporting |
| params.SetBoolean("enabled", false); |
| status_code = ExecuteSetAutoReporting(&session, params, &value).code(); |
| ASSERT_EQ(kOk, status_code); |
| ASSERT_FALSE(session.auto_reporting_enabled); |
| |
| // check that autoreporting was disabled successfully |
| status_code = ExecuteIsAutoReporting(&session, params, &value).code(); |
| ASSERT_EQ(kOk, status_code); |
| ASSERT_TRUE(value.get()->GetAsBoolean(&enabled)); |
| ASSERT_FALSE(enabled); |
| } |