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.
4 
5 #include "chrome/test/chromedriver/session_commands.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/json/json_reader.h"
15 #include "base/run_loop.h"
16 #include "base/system/sys_info.h"
17 #include "base/threading/thread.h"
18 #include "base/values.h"
19 #include "chrome/test/chromedriver/capabilities.h"
20 #include "chrome/test/chromedriver/chrome/status.h"
21 #include "chrome/test/chromedriver/chrome/stub_chrome.h"
22 #include "chrome/test/chromedriver/chrome/stub_web_view.h"
23 #include "chrome/test/chromedriver/commands.h"
24 #include "chrome/test/chromedriver/logging.h"
25 #include "chrome/test/chromedriver/session.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 
TEST(SessionCommandsTest,ExecuteGetTimeouts)28 TEST(SessionCommandsTest, ExecuteGetTimeouts) {
29   Session session("id");
30   base::DictionaryValue params;
31   std::unique_ptr<base::Value> value;
32 
33   Status status = ExecuteGetTimeouts(&session, params, &value);
34   ASSERT_EQ(kOk, status.code());
35   base::DictionaryValue* response;
36   ASSERT_TRUE(value->GetAsDictionary(&response));
37 
38   int script;
39   ASSERT_TRUE(response->GetInteger("script", &script));
40   ASSERT_EQ(script, 30000);
41   int page_load;
42   ASSERT_TRUE(response->GetInteger("pageLoad", &page_load));
43   ASSERT_EQ(page_load, 300000);
44   int implicit;
45   ASSERT_TRUE(response->GetInteger("implicit", &implicit));
46   ASSERT_EQ(implicit, 0);
47 }
48 
TEST(SessionCommandsTest,ExecuteSetTimeouts)49 TEST(SessionCommandsTest, ExecuteSetTimeouts) {
50   Session session("id");
51   base::DictionaryValue params;
52   std::unique_ptr<base::Value> value;
53 
54   // W3C spec doesn't forbid passing in an empty object, so we should get kOk.
55   Status status = ExecuteSetTimeouts(&session, params, &value);
56   ASSERT_EQ(kOk, status.code());
57 
58   params.SetInteger("pageLoad", 5000);
59   status = ExecuteSetTimeouts(&session, params, &value);
60   ASSERT_EQ(kOk, status.code());
61 
62   params.SetInteger("script", 5000);
63   params.SetInteger("implicit", 5000);
64   status = ExecuteSetTimeouts(&session, params, &value);
65   ASSERT_EQ(kOk, status.code());
66 
67   params.SetInteger("implicit", -5000);
68   status = ExecuteSetTimeouts(&session, params, &value);
69   ASSERT_EQ(kInvalidArgument, status.code());
70 
71   params.Clear();
72   params.SetInteger("unknown", 5000);
73   status = ExecuteSetTimeouts(&session, params, &value);
74   ASSERT_EQ(kOk, status.code());
75 
76   // Old pre-W3C format.
77   params.Clear();
78   params.SetDouble("ms", 5000.0);
79   params.SetString("type", "page load");
80   status = ExecuteSetTimeouts(&session, params, &value);
81   ASSERT_EQ(kOk, status.code());
82 }
83 
TEST(SessionCommandsTest,MergeCapabilities)84 TEST(SessionCommandsTest, MergeCapabilities) {
85   base::DictionaryValue primary;
86   primary.SetString("strawberry", "velociraptor");
87   primary.SetString("pear", "unicorn");
88 
89   base::DictionaryValue secondary;
90   secondary.SetString("broccoli", "giraffe");
91   secondary.SetString("celery", "hippo");
92   secondary.SetString("eggplant", "elephant");
93 
94   base::DictionaryValue merged;
95 
96   // key collision should return false
97   ASSERT_FALSE(MergeCapabilities(&primary, &primary, &merged));
98   // non key collision should return true
99   ASSERT_TRUE(MergeCapabilities(&primary, &secondary, &merged));
100 
101   merged.Clear();
102   MergeCapabilities(&primary, &secondary, &merged);
103   primary.MergeDictionary(&secondary);
104 
105   ASSERT_EQ(primary, merged);
106 }
107 
TEST(SessionCommandsTest,ProcessCapabilities_Empty)108 TEST(SessionCommandsTest, ProcessCapabilities_Empty) {
109   // "capabilities" is required
110   base::DictionaryValue params;
111   base::DictionaryValue result;
112   Status status = ProcessCapabilities(params, &result);
113   ASSERT_EQ(kInvalidArgument, status.code());
114 
115   // "capabilities" must be a JSON object
116   params.SetList("capabilities", std::make_unique<base::ListValue>());
117   status = ProcessCapabilities(params, &result);
118   ASSERT_EQ(kInvalidArgument, status.code());
119 
120   // Empty "capabilities" is OK
121   params.SetDictionary("capabilities",
122                        std::make_unique<base::DictionaryValue>());
123   status = ProcessCapabilities(params, &result);
124   ASSERT_EQ(kOk, status.code()) << status.message();
125   ASSERT_TRUE(result.empty());
126 }
127 
TEST(SessionCommandsTest,ProcessCapabilities_AlwaysMatch)128 TEST(SessionCommandsTest, ProcessCapabilities_AlwaysMatch) {
129   base::DictionaryValue params;
130   base::DictionaryValue result;
131 
132   // "alwaysMatch" must be a JSON object
133   params.SetList("capabilities.alwaysMatch",
134                  std::make_unique<base::ListValue>());
135   Status status = ProcessCapabilities(params, &result);
136   ASSERT_EQ(kInvalidArgument, status.code());
137 
138   // Empty "alwaysMatch" is OK
139   params.SetDictionary("capabilities.alwaysMatch",
140                        std::make_unique<base::DictionaryValue>());
141   status = ProcessCapabilities(params, &result);
142   ASSERT_EQ(kOk, status.code()) << status.message();
143   ASSERT_TRUE(result.empty());
144 
145   // Invalid "alwaysMatch"
146   params.SetInteger("capabilities.alwaysMatch.browserName", 10);
147   status = ProcessCapabilities(params, &result);
148   ASSERT_EQ(kInvalidArgument, status.code());
149 
150   // Valid "alwaysMatch"
151   params.SetString("capabilities.alwaysMatch.browserName", "chrome");
152   status = ProcessCapabilities(params, &result);
153   ASSERT_EQ(kOk, status.code()) << status.message();
154   ASSERT_EQ(result.size(), 1u);
155   std::string result_string;
156   ASSERT_TRUE(result.GetString("browserName", &result_string));
157   ASSERT_EQ(result_string, "chrome");
158 
159   // Null "browserName" treated as not specifying "browserName"
160   params.SetPath({"capabilities", "alwaysMatch", "browserName"}, base::Value());
161   status = ProcessCapabilities(params, &result);
162   ASSERT_EQ(kOk, status.code()) << status.message();
163   ASSERT_FALSE(result.GetString("browserName", &result_string));
164 }
165 
TEST(SessionCommandsTest,ProcessCapabilities_FirstMatch)166 TEST(SessionCommandsTest, ProcessCapabilities_FirstMatch) {
167   base::DictionaryValue params;
168   base::DictionaryValue result;
169 
170   // "firstMatch" must be a JSON list
171   params.SetDictionary("capabilities.firstMatch",
172                        std::make_unique<base::DictionaryValue>());
173   Status status = ProcessCapabilities(params, &result);
174   ASSERT_EQ(kInvalidArgument, status.code());
175 
176   // "firstMatch" must have at least one entry
177   params.SetList("capabilities.firstMatch",
178                  std::make_unique<base::ListValue>());
179   status = ProcessCapabilities(params, &result);
180   ASSERT_EQ(kInvalidArgument, status.code());
181 
182   // Each entry must be a JSON object
183   base::ListValue* list_ptr;
184   ASSERT_TRUE(params.GetList("capabilities.firstMatch", &list_ptr));
185   list_ptr->Set(0, std::make_unique<base::ListValue>());
186   status = ProcessCapabilities(params, &result);
187   ASSERT_EQ(kInvalidArgument, status.code());
188 
189   // Empty JSON object allowed as an entry
190   list_ptr->Set(0, std::make_unique<base::DictionaryValue>());
191   status = ProcessCapabilities(params, &result);
192   ASSERT_EQ(kOk, status.code()) << status.message();
193   ASSERT_TRUE(result.empty());
194 
195   // Invalid entry
196   base::DictionaryValue* entry_ptr;
197   ASSERT_TRUE(list_ptr->GetDictionary(0, &entry_ptr));
198   entry_ptr->SetString("pageLoadStrategy", "invalid");
199   status = ProcessCapabilities(params, &result);
200   ASSERT_EQ(kInvalidArgument, status.code());
201 
202   // Valid entry
203   entry_ptr->SetString("pageLoadStrategy", "eager");
204   status = ProcessCapabilities(params, &result);
205   ASSERT_EQ(kOk, status.code()) << status.message();
206   ASSERT_EQ(result.size(), 1u);
207   std::string result_string;
208   ASSERT_TRUE(result.GetString("pageLoadStrategy", &result_string));
209   ASSERT_EQ(result_string, "eager");
210 
211   // Multiple entries, the first one should be selected.
212   list_ptr->Set(1, std::make_unique<base::DictionaryValue>());
213   ASSERT_TRUE(list_ptr->GetDictionary(1, &entry_ptr));
214   entry_ptr->SetString("pageLoadStrategy", "normal");
215   entry_ptr->SetString("browserName", "chrome");
216   status = ProcessCapabilities(params, &result);
217   ASSERT_EQ(kOk, status.code()) << status.message();
218   ASSERT_EQ(result.size(), 1u);
219   ASSERT_TRUE(result.GetString("pageLoadStrategy", &result_string));
220   ASSERT_EQ(result_string, "eager");
221 }
222 
223 namespace {
224 
ProcessCapabilitiesJson(const std::string & paramsJson,base::DictionaryValue * result_capabilities)225 Status ProcessCapabilitiesJson(const std::string& paramsJson,
226                                base::DictionaryValue* result_capabilities) {
227   std::unique_ptr<base::Value> params =
228       base::JSONReader::ReadDeprecated(paramsJson);
229   if (!params || !params->is_dict())
230     return Status(kUnknownError);
231   return ProcessCapabilities(
232       *static_cast<const base::DictionaryValue*>(params.get()),
233       result_capabilities);
234 }
235 
236 }  // namespace
237 
TEST(SessionCommandsTest,ProcessCapabilities_Merge)238 TEST(SessionCommandsTest, ProcessCapabilities_Merge) {
239   base::DictionaryValue result;
240   Status status(kOk);
241 
242   // Disallow setting same capability in alwaysMatch and firstMatch
243   status = ProcessCapabilitiesJson(
244       R"({
245         "capabilities": {
246           "alwaysMatch": { "pageLoadStrategy": "normal" },
247           "firstMatch": [
248             { "unhandledPromptBehavior": "accept" },
249             { "pageLoadStrategy": "normal" }
250           ]
251         }
252       })",
253       &result);
254   ASSERT_EQ(kInvalidArgument, status.code());
255 
256   // No conflicts between alwaysMatch and firstMatch, select first firstMatch
257   status = ProcessCapabilitiesJson(
258       R"({
259         "capabilities": {
260           "alwaysMatch": { "timeouts": { } },
261           "firstMatch": [
262             { "unhandledPromptBehavior": "accept" },
263             { "pageLoadStrategy": "normal" }
264           ]
265         }
266       })",
267       &result);
268   ASSERT_EQ(kOk, status.code()) << status.message();
269   ASSERT_EQ(result.size(), 2u);
270   ASSERT_TRUE(result.HasKey("timeouts"));
271   ASSERT_TRUE(result.HasKey("unhandledPromptBehavior"));
272   ASSERT_FALSE(result.HasKey("pageLoadStrategy"));
273 
274   // Selection by platformName
275   std::string platform_name =
276       base::ToLowerASCII(base::SysInfo::OperatingSystemName());
277   status = ProcessCapabilitiesJson(
278       R"({
279        "capabilities": {
280          "alwaysMatch": { "timeouts": { "script": 10 } },
281          "firstMatch": [
282            { "platformName": "LINUX", "pageLoadStrategy": "none" },
283            { "platformName": ")" +
284           platform_name + R"(", "pageLoadStrategy": "eager" }
285          ]
286        }
287      })",
288       &result);
289   printf("THIS IS PLATFORM: %s", platform_name.c_str());
290   ASSERT_EQ(kOk, status.code()) << status.message();
291   ASSERT_EQ(result.FindKey("platformName")->GetString(), platform_name);
292   ASSERT_EQ(result.FindKey("pageLoadStrategy")->GetString(), "eager");
293 
294   // Selection by browserName
295   status = ProcessCapabilitiesJson(
296       R"({
297         "capabilities": {
298           "alwaysMatch": { "timeouts": { } },
299           "firstMatch": [
300             { "browserName": "firefox", "unhandledPromptBehavior": "accept" },
301             { "browserName": "chrome", "pageLoadStrategy": "normal" }
302           ]
303         }
304       })",
305       &result);
306   ASSERT_EQ(kOk, status.code()) << status.message();
307   ASSERT_EQ(result.size(), 3u);
308   ASSERT_TRUE(result.HasKey("timeouts"));
309   ASSERT_EQ(result.FindKey("browserName")->GetString(), "chrome");
310   ASSERT_FALSE(result.HasKey("unhandledPromptBehavior"));
311   ASSERT_TRUE(result.HasKey("pageLoadStrategy"));
312 
313   // No acceptable firstMatch
314   status = ProcessCapabilitiesJson(
315       R"({
316         "capabilities": {
317           "alwaysMatch": { "timeouts": { } },
318           "firstMatch": [
319             { "browserName": "firefox", "unhandledPromptBehavior": "accept" },
320             { "browserName": "edge", "pageLoadStrategy": "normal" }
321           ]
322         }
323       })",
324       &result);
325   ASSERT_EQ(kSessionNotCreated, status.code());
326 }
327 
TEST(SessionCommandsTest,FileUpload)328 TEST(SessionCommandsTest, FileUpload) {
329   Session session("id");
330   base::DictionaryValue params;
331   std::unique_ptr<base::Value> value;
332   // Zip file entry that contains a single file with contents 'COW\n', base64
333   // encoded following RFC 1521.
334   const char kBase64ZipEntry[] =
335       "UEsDBBQAAAAAAMROi0K/wAzGBAAAAAQAAAADAAAAbW9vQ09XClBLAQIUAxQAAAAAAMROi0K/"
336       "wAzG\nBAAAAAQAAAADAAAAAAAAAAAAAACggQAAAABtb29QSwUGAAAAAAEAAQAxAAAAJQAAAA"
337       "AA\n";
338   params.SetString("file", kBase64ZipEntry);
339   Status status = ExecuteUploadFile(&session, params, &value);
340   ASSERT_EQ(kOk, status.code()) << status.message();
341   base::FilePath::StringType path;
342   ASSERT_TRUE(value->GetAsString(&path));
343   ASSERT_TRUE(base::PathExists(base::FilePath(path)));
344   std::string data;
345   ASSERT_TRUE(base::ReadFileToString(base::FilePath(path), &data));
346   ASSERT_STREQ("COW\n", data.c_str());
347 }
348 
349 namespace {
350 
351 class DetachChrome : public StubChrome {
352  public:
DetachChrome()353   DetachChrome() : quit_called_(false) {}
~DetachChrome()354   ~DetachChrome() override {}
355 
356   // Overridden from Chrome:
Quit()357   Status Quit() override {
358     quit_called_ = true;
359     return Status(kOk);
360   }
361 
362   bool quit_called_;
363 };
364 
365 }  // namespace
366 
TEST(SessionCommandsTest,MatchCapabilities)367 TEST(SessionCommandsTest, MatchCapabilities) {
368   base::DictionaryValue merged;
369   merged.SetString("browserName", "not chrome");
370 
371   ASSERT_FALSE(MatchCapabilities(&merged));
372 
373   merged.Clear();
374   merged.SetString("browserName", "chrome");
375 
376   ASSERT_TRUE(MatchCapabilities(&merged));
377 }
378 
TEST(SessionCommandsTest,MatchCapabilitiesVirtualAuthenticators)379 TEST(SessionCommandsTest, MatchCapabilitiesVirtualAuthenticators) {
380   // Match webauthn:virtualAuthenticators on desktop.
381   base::DictionaryValue merged;
382   merged.SetBoolPath("webauthn:virtualAuthenticators", true);
383   EXPECT_TRUE(MatchCapabilities(&merged));
384 
385   // Don't match webauthn:virtualAuthenticators on android.
386   merged.SetStringPath("goog:chromeOptions.androidPackage", "packageName");
387   EXPECT_FALSE(MatchCapabilities(&merged));
388 
389   // Don't match values other than bools.
390   merged.Clear();
391   merged.SetStringPath("webauthn:virtualAuthenticators", "not a bool");
392   EXPECT_FALSE(MatchCapabilities(&merged));
393 }
394 
TEST(SessionCommandsTest,MatchCapabilitiesVirtualAuthenticatorsLargeBlob)395 TEST(SessionCommandsTest, MatchCapabilitiesVirtualAuthenticatorsLargeBlob) {
396   // Match webauthn:extension:largeBlob on desktop.
397   base::DictionaryValue merged;
398   merged.SetBoolPath("webauthn:extension:largeBlob", true);
399   EXPECT_TRUE(MatchCapabilities(&merged));
400 
401   // Don't match webauthn:extension:largeBlob on android.
402   merged.SetStringPath("goog:chromeOptions.androidPackage", "packageName");
403   EXPECT_FALSE(MatchCapabilities(&merged));
404 
405   // Don't match values other than bools.
406   merged.Clear();
407   merged.SetStringPath("webauthn:extension:largeBlob", "not a bool");
408   EXPECT_FALSE(MatchCapabilities(&merged));
409 }
410 
TEST(SessionCommandsTest,Quit)411 TEST(SessionCommandsTest, Quit) {
412   DetachChrome* chrome = new DetachChrome();
413   Session session("id", std::unique_ptr<Chrome>(chrome));
414 
415   base::DictionaryValue params;
416   std::unique_ptr<base::Value> value;
417 
418   ASSERT_EQ(kOk, ExecuteQuit(false, &session, params, &value).code());
419   ASSERT_TRUE(chrome->quit_called_);
420 
421   chrome->quit_called_ = false;
422   ASSERT_EQ(kOk, ExecuteQuit(true, &session, params, &value).code());
423   ASSERT_TRUE(chrome->quit_called_);
424 }
425 
TEST(SessionCommandsTest,QuitWithDetach)426 TEST(SessionCommandsTest, QuitWithDetach) {
427   DetachChrome* chrome = new DetachChrome();
428   Session session("id", std::unique_ptr<Chrome>(chrome));
429   session.detach = true;
430 
431   base::DictionaryValue params;
432   std::unique_ptr<base::Value> value;
433 
434   ASSERT_EQ(kOk, ExecuteQuit(true, &session, params, &value).code());
435   ASSERT_FALSE(chrome->quit_called_);
436 
437   ASSERT_EQ(kOk, ExecuteQuit(false, &session, params, &value).code());
438   ASSERT_TRUE(chrome->quit_called_);
439 }
440 
441 namespace {
442 
443 class FailsToQuitChrome : public StubChrome {
444  public:
FailsToQuitChrome()445   FailsToQuitChrome() {}
~FailsToQuitChrome()446   ~FailsToQuitChrome() override {}
447 
448   // Overridden from Chrome:
Quit()449   Status Quit() override { return Status(kUnknownError); }
450 };
451 
452 }  // namespace
453 
TEST(SessionCommandsTest,QuitFails)454 TEST(SessionCommandsTest, QuitFails) {
455   Session session("id", std::unique_ptr<Chrome>(new FailsToQuitChrome()));
456   base::DictionaryValue params;
457   std::unique_ptr<base::Value> value;
458   ASSERT_EQ(kUnknownError, ExecuteQuit(false, &session, params, &value).code());
459 }
460 
461 namespace {
462 
463 class MockChrome : public StubChrome {
464  public:
MockChrome(BrowserInfo & binfo)465   explicit MockChrome(BrowserInfo& binfo) : web_view_("1") {
466     browser_info_ = binfo;
467   }
~MockChrome()468   ~MockChrome() override {}
469 
GetBrowserInfo() const470   const BrowserInfo* GetBrowserInfo() const override { return &browser_info_; }
471 
GetWebViewById(const std::string & id,WebView ** web_view)472   Status GetWebViewById(const std::string& id, WebView** web_view) override {
473     *web_view = &web_view_;
474     return Status(kOk);
475   }
476 
477  private:
478   BrowserInfo browser_info_;
479   StubWebView web_view_;
480 };
481 
482 }  // namespace
483 
TEST(SessionCommandsTest,ConfigureHeadlessSession_dotNotation)484 TEST(SessionCommandsTest, ConfigureHeadlessSession_dotNotation) {
485   Capabilities capabilities;
486   base::DictionaryValue caps;
487   base::Value::ListStorage args;
488   args.emplace_back("headless");
489   caps.SetPath({"goog:chromeOptions", "args"}, base::Value(args));
490 
491   base::DictionaryValue prefs;
492   prefs.SetKey("download.default_directory",
493                base::Value("/examples/python/downloads"));
494   caps.SetPath({"goog:chromeOptions", "prefs"}, prefs.Clone());
495 
496   Status status = capabilities.Parse(caps);
497   BrowserInfo binfo;
498   binfo.is_headless = true;
499   MockChrome* chrome = new MockChrome(binfo);
500   Session session("id", std::unique_ptr<Chrome>(chrome));
501 
502   status = internal::ConfigureHeadlessSession(&session, capabilities);
503   ASSERT_EQ(kOk, status.code()) << status.message();
504   ASSERT_TRUE(session.chrome->GetBrowserInfo()->is_headless);
505   ASSERT_STREQ("/examples/python/downloads",
506                session.headless_download_directory->c_str());
507 }
508 
TEST(SessionCommandsTest,ConfigureHeadlessSession_nestedMap)509 TEST(SessionCommandsTest, ConfigureHeadlessSession_nestedMap) {
510   Capabilities capabilities;
511   base::DictionaryValue caps;
512   base::Value::ListStorage args;
513   args.emplace_back("headless");
514   caps.SetPath({"goog:chromeOptions", "args"}, base::Value(args));
515 
516   base::DictionaryValue prefs;
517   std::unique_ptr<base::DictionaryValue> download(new base::DictionaryValue());
518   download->SetStringPath("default_directory", "/examples/python/downloads");
519   prefs.SetDictionary("download", std::move(download));
520   caps.SetPath({"goog:chromeOptions", "prefs"}, prefs.Clone());
521 
522   Status status = capabilities.Parse(caps);
523   BrowserInfo binfo;
524   binfo.is_headless = true;
525   MockChrome* chrome = new MockChrome(binfo);
526   Session session("id", std::unique_ptr<Chrome>(chrome));
527 
528   status = internal::ConfigureHeadlessSession(&session, capabilities);
529   ASSERT_EQ(kOk, status.code()) << status.message();
530   ASSERT_TRUE(session.chrome->GetBrowserInfo()->is_headless);
531   ASSERT_STREQ("/examples/python/downloads",
532                session.headless_download_directory->c_str());
533 }
534 
TEST(SessionCommandsTest,ConfigureHeadlessSession_noDownloadDir)535 TEST(SessionCommandsTest, ConfigureHeadlessSession_noDownloadDir) {
536   Capabilities capabilities;
537   base::DictionaryValue caps;
538   base::Value::ListStorage args;
539   args.emplace_back("headless");
540   caps.SetPath({"goog:chromeOptions", "args"}, base::Value(args));
541 
542   Status status = capabilities.Parse(caps);
543   BrowserInfo binfo;
544   binfo.is_headless = true;
545   MockChrome* chrome = new MockChrome(binfo);
546   Session session("id", std::unique_ptr<Chrome>(chrome));
547 
548   status = internal::ConfigureHeadlessSession(&session, capabilities);
549   ASSERT_EQ(kOk, status.code()) << status.message();
550   ASSERT_TRUE(session.chrome->GetBrowserInfo()->is_headless);
551   ASSERT_STREQ(".", session.headless_download_directory->c_str());
552 }
553 
TEST(SessionCommandsTest,ConfigureHeadlessSession_notHeadless)554 TEST(SessionCommandsTest, ConfigureHeadlessSession_notHeadless) {
555   Capabilities capabilities;
556   base::DictionaryValue caps;
557   base::DictionaryValue prefs;
558   std::unique_ptr<base::DictionaryValue> download(new base::DictionaryValue());
559   download->SetStringPath("default_directory", "/examples/python/downloads");
560   prefs.SetDictionary("download", std::move(download));
561   caps.SetPath({"goog:chromeOptions", "prefs"}, prefs.Clone());
562 
563   Status status = capabilities.Parse(caps);
564   BrowserInfo binfo;
565   MockChrome* chrome = new MockChrome(binfo);
566   Session session("id", std::unique_ptr<Chrome>(chrome));
567 
568   status = internal::ConfigureHeadlessSession(&session, capabilities);
569   ASSERT_EQ(kOk, status.code()) << status.message();
570   ASSERT_FALSE(session.chrome->GetBrowserInfo()->is_headless);
571   ASSERT_FALSE(session.headless_download_directory);
572 }
573 
TEST(SessionCommandsTest,ConfigureSession_allSet)574 TEST(SessionCommandsTest, ConfigureSession_allSet) {
575   BrowserInfo binfo;
576   MockChrome* chrome = new MockChrome(binfo);
577   Session session("id", std::unique_ptr<Chrome>(chrome));
578 
579   const base::DictionaryValue* params_in = nullptr;
580   base::Value value = base::JSONReader::Read(
581                           R"({
582         "capabilities": {
583           "alwaysMatch": { },
584           "firstMatch": [ {
585             "acceptInsecureCerts": false,
586             "browserName": "chrome",
587             "goog:chromeOptions": {
588             },
589             "goog:loggingPrefs": {
590               "driver": "DEBUG"
591             },
592             "pageLoadStrategy": "normal",
593             "timeouts": {
594               "implicit": 57000,
595               "pageLoad": 29000,
596               "script": 21000
597             },
598             "strictFileInteractability": true,
599             "unhandledPromptBehavior": "accept"
600           } ]
601         }
602       })")
603                           .value();
604   ASSERT_TRUE(value.GetAsDictionary(&params_in));
605 
606   const base::DictionaryValue* desired_caps_out;
607   base::DictionaryValue merged_out;
608   Capabilities capabilities_out;
609   Status status = internal::ConfigureSession(
610       &session, *params_in, &desired_caps_out, &merged_out, &capabilities_out);
611   ASSERT_EQ(kOk, status.code()) << status.message();
612   // Verify out parameters have been set
613   ASSERT_TRUE(desired_caps_out->is_dict());
614   ASSERT_TRUE(merged_out.is_dict());
615   ASSERT_TRUE(capabilities_out.logging_prefs["driver"]);
616   // Verify session settings are correct
617   ASSERT_EQ(kAccept, session.unhandled_prompt_behavior);
618   ASSERT_EQ(base::TimeDelta::FromSeconds(57), session.implicit_wait);
619   ASSERT_EQ(base::TimeDelta::FromSeconds(29), session.page_load_timeout);
620   ASSERT_EQ(base::TimeDelta::FromSeconds(21), session.script_timeout);
621   ASSERT_TRUE(session.strict_file_interactability);
622   ASSERT_EQ(Log::Level::kDebug, session.driver_log.get()->min_level());
623 }
624 
TEST(SessionCommandsTest,ConfigureSession_defaults)625 TEST(SessionCommandsTest, ConfigureSession_defaults) {
626   BrowserInfo binfo;
627   MockChrome* chrome = new MockChrome(binfo);
628   Session session("id", std::unique_ptr<Chrome>(chrome));
629 
630   const base::DictionaryValue* params_in = nullptr;
631   base::Value value = base::JSONReader::Read(
632                           R"({
633         "capabilities": {
634           "alwaysMatch": { },
635           "firstMatch": [ { } ]
636         }
637       })")
638                           .value();
639   ASSERT_TRUE(value.GetAsDictionary(&params_in));
640   const base::DictionaryValue* desired_caps_out;
641   base::DictionaryValue merged_out;
642   Capabilities capabilities_out;
643 
644   Status status = internal::ConfigureSession(
645       &session, *params_in, &desired_caps_out, &merged_out, &capabilities_out);
646   ASSERT_EQ(kOk, status.code()) << status.message();
647   ASSERT_TRUE(desired_caps_out->is_dict());
648   ASSERT_TRUE(merged_out.is_dict());
649   // Testing specific values could be fragile, but want to verify they are set
650   ASSERT_EQ(base::TimeDelta::FromSeconds(0), session.implicit_wait);
651   ASSERT_EQ(base::TimeDelta::FromSeconds(300), session.page_load_timeout);
652   ASSERT_EQ(base::TimeDelta::FromSeconds(30), session.script_timeout);
653   ASSERT_FALSE(session.strict_file_interactability);
654   ASSERT_EQ(Log::Level::kWarning, session.driver_log.get()->min_level());
655   // w3c values:
656   ASSERT_EQ(kDismissAndNotify, session.unhandled_prompt_behavior);
657 }
658 
TEST(SessionCommandsTest,ConfigureSession_legacyDefault)659 TEST(SessionCommandsTest, ConfigureSession_legacyDefault) {
660   BrowserInfo binfo;
661   MockChrome* chrome = new MockChrome(binfo);
662   Session session("id", std::unique_ptr<Chrome>(chrome));
663 
664   const base::DictionaryValue* params_in = nullptr;
665   base::Value value = base::JSONReader::Read(
666                           R"({
667         "desiredCapabilities": {
668           "browserName": "chrome",
669           "goog:chromeOptions": {
670              "w3c": false
671           }
672         }
673       })")
674                           .value();
675   ASSERT_TRUE(value.GetAsDictionary(&params_in));
676   const base::DictionaryValue* desired_caps_out;
677   base::DictionaryValue merged_out;
678   Capabilities capabilities_out;
679 
680   Status status = internal::ConfigureSession(
681       &session, *params_in, &desired_caps_out, &merged_out, &capabilities_out);
682   ASSERT_EQ(kOk, status.code()) << status.message();
683   ASSERT_TRUE(desired_caps_out->is_dict());
684   // legacy values:
685   ASSERT_EQ(kIgnore, session.unhandled_prompt_behavior);
686 }
687