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/session_commands.h"
10 #include "base/callback.h"
11 #include "base/files/file_util.h"
12 #include "base/logging.h" // For CHECK macros.
13 #include "base/memory/ref_counted.h"
14 #include "base/synchronization/lock.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/thread_task_runner_handle.h"
17 #include "base/values.h"
18 #include "chrome/test/chromedriver/basic_types.h"
19 #include "chrome/test/chromedriver/capabilities.h"
20 #include "chrome/test/chromedriver/chrome/automation_extension.h"
21 #include "chrome/test/chromedriver/chrome/browser_info.h"
22 #include "chrome/test/chromedriver/chrome/chrome.h"
23 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
24 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
25 #include "chrome/test/chromedriver/chrome/device_manager.h"
26 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
27 #include "chrome/test/chromedriver/chrome/geoposition.h"
28 #include "chrome/test/chromedriver/chrome/status.h"
29 #include "chrome/test/chromedriver/chrome/web_view.h"
30 #include "chrome/test/chromedriver/chrome_launcher.h"
31 #include "chrome/test/chromedriver/command_listener.h"
32 #include "chrome/test/chromedriver/logging.h"
33 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
34 #include "chrome/test/chromedriver/session.h"
35 #include "chrome/test/chromedriver/util.h"
36 #include "chrome/test/chromedriver/version.h"
40 const char kWindowHandlePrefix
[] = "CDwindow-";
42 std::string
WebViewIdToWindowHandle(const std::string
& web_view_id
) {
43 return kWindowHandlePrefix
+ web_view_id
;
46 bool WindowHandleToWebViewId(const std::string
& window_handle
,
47 std::string
* web_view_id
) {
48 if (window_handle
.find(kWindowHandlePrefix
) != 0u)
50 *web_view_id
= window_handle
.substr(
51 std::string(kWindowHandlePrefix
).length());
57 InitSessionParams::InitSessionParams(
58 scoped_refptr
<URLRequestContextGetter
> context_getter
,
59 const SyncWebSocketFactory
& socket_factory
,
60 DeviceManager
* device_manager
,
61 PortServer
* port_server
,
62 PortManager
* port_manager
)
63 : context_getter(context_getter
),
64 socket_factory(socket_factory
),
65 device_manager(device_manager
),
66 port_server(port_server
),
67 port_manager(port_manager
) {}
69 InitSessionParams::~InitSessionParams() {}
73 scoped_ptr
<base::DictionaryValue
> CreateCapabilities(Chrome
* chrome
) {
74 scoped_ptr
<base::DictionaryValue
> caps(new base::DictionaryValue());
75 caps
->SetString("browserName", "chrome");
76 caps
->SetString("version", chrome
->GetBrowserInfo()->browser_version
);
77 caps
->SetString("chrome.chromedriverVersion", kChromeDriverVersion
);
78 caps
->SetString("platform", chrome
->GetOperatingSystemName());
79 caps
->SetBoolean("javascriptEnabled", true);
80 caps
->SetBoolean("takesScreenshot", true);
81 caps
->SetBoolean("takesHeapSnapshot", true);
82 caps
->SetBoolean("handlesAlerts", true);
83 caps
->SetBoolean("databaseEnabled", false);
84 caps
->SetBoolean("locationContextEnabled", true);
85 caps
->SetBoolean("mobileEmulationEnabled",
86 chrome
->IsMobileEmulationEnabled());
87 caps
->SetBoolean("applicationCacheEnabled", false);
88 caps
->SetBoolean("browserConnectionEnabled", false);
89 caps
->SetBoolean("cssSelectorsEnabled", true);
90 caps
->SetBoolean("webStorageEnabled", true);
91 caps
->SetBoolean("rotatable", false);
92 caps
->SetBoolean("acceptSslCerts", true);
93 caps
->SetBoolean("nativeEvents", true);
94 caps
->SetBoolean("hasTouchScreen", chrome
->HasTouchScreen());
95 scoped_ptr
<base::DictionaryValue
> chrome_caps(new base::DictionaryValue());
97 ChromeDesktopImpl
* desktop
= NULL
;
98 Status status
= chrome
->GetAsDesktop(&desktop
);
100 chrome_caps
->SetString(
102 desktop
->command().GetSwitchValueNative("user-data-dir"));
105 caps
->Set("chrome", chrome_caps
.release());
109 Status
CheckSessionCreated(Session
* session
) {
110 WebView
* web_view
= NULL
;
111 Status status
= session
->GetTargetWindow(&web_view
);
112 if (status
.IsError())
113 return Status(kSessionNotCreatedException
, status
);
115 status
= web_view
->ConnectIfNecessary();
116 if (status
.IsError())
117 return Status(kSessionNotCreatedException
, status
);
119 base::ListValue args
;
120 scoped_ptr
<base::Value
> result(new base::FundamentalValue(0));
121 status
= web_view
->CallFunction(session
->GetCurrentFrameId(),
122 "function(s) { return 1; }", args
, &result
);
123 if (status
.IsError())
124 return Status(kSessionNotCreatedException
, status
);
127 if (!result
->GetAsInteger(&response
) || response
!= 1) {
128 return Status(kSessionNotCreatedException
,
129 "unexpected response from browser");
135 Status
InitSessionHelper(
136 const InitSessionParams
& bound_params
,
138 const base::DictionaryValue
& params
,
139 scoped_ptr
<base::Value
>* value
) {
140 session
->driver_log
.reset(
141 new WebDriverLog(WebDriverLog::kDriverType
, Log::kAll
));
142 const base::DictionaryValue
* desired_caps
;
143 if (!params
.GetDictionary("desiredCapabilities", &desired_caps
))
144 return Status(kUnknownError
, "cannot find dict 'desiredCapabilities'");
146 Capabilities capabilities
;
147 Status status
= capabilities
.Parse(*desired_caps
);
148 if (status
.IsError())
151 Log::Level driver_level
= Log::kWarning
;
152 if (capabilities
.logging_prefs
.count(WebDriverLog::kDriverType
))
153 driver_level
= capabilities
.logging_prefs
[WebDriverLog::kDriverType
];
154 session
->driver_log
->set_min_level(driver_level
);
156 // Create Log's and DevToolsEventListener's for ones that are DevTools-based.
157 // Session will own the Log's, Chrome will own the listeners.
158 // Also create |CommandListener|s for the appropriate logs.
159 ScopedVector
<DevToolsEventListener
> devtools_event_listeners
;
160 ScopedVector
<CommandListener
> command_listeners
;
161 status
= CreateLogs(capabilities
,
163 &session
->devtools_logs
,
164 &devtools_event_listeners
,
166 if (status
.IsError())
169 // |session| will own the |CommandListener|s.
170 session
->command_listeners
.swap(command_listeners
);
172 status
= LaunchChrome(bound_params
.context_getter
.get(),
173 bound_params
.socket_factory
,
174 bound_params
.device_manager
,
175 bound_params
.port_server
,
176 bound_params
.port_manager
,
178 &devtools_event_listeners
,
180 if (status
.IsError())
183 std::list
<std::string
> web_view_ids
;
184 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
185 if (status
.IsError() || web_view_ids
.empty()) {
186 return status
.IsError() ? status
:
187 Status(kUnknownError
, "unable to discover open window in chrome");
190 session
->window
= web_view_ids
.front();
191 session
->detach
= capabilities
.detach
;
192 session
->force_devtools_screenshot
= capabilities
.force_devtools_screenshot
;
193 session
->capabilities
= CreateCapabilities(session
->chrome
.get());
194 value
->reset(session
->capabilities
->DeepCopy());
195 return CheckSessionCreated(session
);
198 Status
SwitchToWebView(Session
* session
, const std::string
& web_view_id
) {
199 if (session
->overridden_geoposition
) {
201 Status status
= session
->chrome
->GetWebViewById(web_view_id
, &web_view
);
202 if (status
.IsError())
204 status
= web_view
->ConnectIfNecessary();
205 if (status
.IsError())
207 status
= web_view
->OverrideGeolocation(*session
->overridden_geoposition
);
208 if (status
.IsError())
212 if (session
->overridden_network_conditions
) {
214 Status status
= session
->chrome
->GetWebViewById(web_view_id
, &web_view
);
215 if (status
.IsError())
217 status
= web_view
->ConnectIfNecessary();
218 if (status
.IsError())
220 status
= web_view
->OverrideNetworkConditions(
221 *session
->overridden_network_conditions
);
222 if (status
.IsError())
226 session
->window
= web_view_id
;
227 session
->SwitchToTopFrame();
228 session
->mouse_position
= WebPoint(0, 0);
234 Status
ExecuteInitSession(
235 const InitSessionParams
& bound_params
,
237 const base::DictionaryValue
& params
,
238 scoped_ptr
<base::Value
>* value
) {
239 Status status
= InitSessionHelper(bound_params
, session
, params
, value
);
240 if (status
.IsError()) {
241 session
->quit
= true;
242 if (session
->chrome
!= NULL
)
243 session
->chrome
->Quit();
251 const base::DictionaryValue
& params
,
252 scoped_ptr
<base::Value
>* value
) {
253 session
->quit
= true;
254 if (allow_detach
&& session
->detach
)
257 return session
->chrome
->Quit();
260 Status
ExecuteGetSessionCapabilities(
262 const base::DictionaryValue
& params
,
263 scoped_ptr
<base::Value
>* value
) {
264 value
->reset(session
->capabilities
->DeepCopy());
268 Status
ExecuteGetCurrentWindowHandle(
270 const base::DictionaryValue
& params
,
271 scoped_ptr
<base::Value
>* value
) {
272 WebView
* web_view
= NULL
;
273 Status status
= session
->GetTargetWindow(&web_view
);
274 if (status
.IsError())
278 new base::StringValue(WebViewIdToWindowHandle(web_view
->GetId())));
282 Status
ExecuteLaunchApp(
284 const base::DictionaryValue
& params
,
285 scoped_ptr
<base::Value
>* value
) {
287 if (!params
.GetString("id", &id
))
288 return Status(kUnknownError
, "'id' must be a string");
290 ChromeDesktopImpl
* desktop
= NULL
;
291 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
292 if (status
.IsError())
295 AutomationExtension
* extension
= NULL
;
296 status
= desktop
->GetAutomationExtension(&extension
);
297 if (status
.IsError())
300 status
= extension
->LaunchApp(id
);
301 if (status
.IsError())
304 std::string web_view_id
;
305 base::TimeDelta timeout
= base::TimeDelta::FromSeconds(60);
306 status
= desktop
->WaitForNewAppWindow(timeout
, id
, &web_view_id
);
307 if (status
.IsError())
310 return SwitchToWebView(session
, web_view_id
);
315 const base::DictionaryValue
& params
,
316 scoped_ptr
<base::Value
>* value
) {
317 std::list
<std::string
> web_view_ids
;
318 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
319 if (status
.IsError())
321 bool is_last_web_view
= web_view_ids
.size() == 1u;
322 web_view_ids
.clear();
324 WebView
* web_view
= NULL
;
325 status
= session
->GetTargetWindow(&web_view
);
326 if (status
.IsError())
329 status
= session
->chrome
->CloseWebView(web_view
->GetId());
330 if (status
.IsError())
333 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
334 if ((status
.code() == kChromeNotReachable
&& is_last_web_view
) ||
335 (status
.IsOk() && web_view_ids
.empty())) {
336 // If no window is open, close is the equivalent of calling "quit".
337 session
->quit
= true;
338 return session
->chrome
->Quit();
344 Status
ExecuteGetWindowHandles(
346 const base::DictionaryValue
& params
,
347 scoped_ptr
<base::Value
>* value
) {
348 std::list
<std::string
> web_view_ids
;
349 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
350 if (status
.IsError())
352 scoped_ptr
<base::ListValue
> window_ids(new base::ListValue());
353 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
354 it
!= web_view_ids
.end(); ++it
) {
355 window_ids
->AppendString(WebViewIdToWindowHandle(*it
));
357 value
->reset(window_ids
.release());
361 Status
ExecuteSwitchToWindow(
363 const base::DictionaryValue
& params
,
364 scoped_ptr
<base::Value
>* value
) {
366 if (!params
.GetString("name", &name
) || name
.empty())
367 return Status(kUnknownError
, "'name' must be a nonempty string");
369 std::list
<std::string
> web_view_ids
;
370 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
371 if (status
.IsError())
374 std::string web_view_id
;
376 if (WindowHandleToWebViewId(name
, &web_view_id
)) {
377 // Check if any web_view matches |web_view_id|.
378 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
379 it
!= web_view_ids
.end(); ++it
) {
380 if (*it
== web_view_id
) {
386 // Check if any of the tab window names match |name|.
387 const char* kGetWindowNameScript
= "function() { return window.name; }";
388 base::ListValue args
;
389 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
390 it
!= web_view_ids
.end(); ++it
) {
391 scoped_ptr
<base::Value
> result
;
393 status
= session
->chrome
->GetWebViewById(*it
, &web_view
);
394 if (status
.IsError())
396 status
= web_view
->ConnectIfNecessary();
397 if (status
.IsError())
399 status
= web_view
->CallFunction(
400 std::string(), kGetWindowNameScript
, args
, &result
);
401 if (status
.IsError())
403 std::string window_name
;
404 if (!result
->GetAsString(&window_name
))
405 return Status(kUnknownError
, "failed to get window name");
406 if (window_name
== name
) {
415 return Status(kNoSuchWindow
);
416 return SwitchToWebView(session
, web_view_id
);
419 Status
ExecuteSetTimeout(
421 const base::DictionaryValue
& params
,
422 scoped_ptr
<base::Value
>* value
) {
424 if (!params
.GetDouble("ms", &ms_double
))
425 return Status(kUnknownError
, "'ms' must be a double");
427 if (!params
.GetString("type", &type
))
428 return Status(kUnknownError
, "'type' must be a string");
430 base::TimeDelta timeout
=
431 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double
));
432 // TODO(frankf): implicit and script timeout should be cleared
433 // if negative timeout is specified.
434 if (type
== "implicit") {
435 session
->implicit_wait
= timeout
;
436 } else if (type
== "script") {
437 session
->script_timeout
= timeout
;
438 } else if (type
== "page load") {
439 session
->page_load_timeout
=
440 ((timeout
< base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
443 return Status(kUnknownError
, "unknown type of timeout:" + type
);
448 Status
ExecuteSetScriptTimeout(
450 const base::DictionaryValue
& params
,
451 scoped_ptr
<base::Value
>* value
) {
453 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
454 return Status(kUnknownError
, "'ms' must be a non-negative number");
455 session
->script_timeout
=
456 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
460 Status
ExecuteImplicitlyWait(
462 const base::DictionaryValue
& params
,
463 scoped_ptr
<base::Value
>* value
) {
465 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
466 return Status(kUnknownError
, "'ms' must be a non-negative number");
467 session
->implicit_wait
=
468 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
472 Status
ExecuteIsLoading(
474 const base::DictionaryValue
& params
,
475 scoped_ptr
<base::Value
>* value
) {
476 WebView
* web_view
= NULL
;
477 Status status
= session
->GetTargetWindow(&web_view
);
478 if (status
.IsError())
481 status
= web_view
->ConnectIfNecessary();
482 if (status
.IsError())
486 status
= web_view
->IsPendingNavigation(
487 session
->GetCurrentFrameId(), &is_pending
);
488 if (status
.IsError())
490 value
->reset(new base::FundamentalValue(is_pending
));
494 Status
ExecuteGetLocation(
496 const base::DictionaryValue
& params
,
497 scoped_ptr
<base::Value
>* value
) {
498 if (!session
->overridden_geoposition
) {
499 return Status(kUnknownError
,
500 "Location must be set before it can be retrieved");
502 base::DictionaryValue location
;
503 location
.SetDouble("latitude", session
->overridden_geoposition
->latitude
);
504 location
.SetDouble("longitude", session
->overridden_geoposition
->longitude
);
505 location
.SetDouble("accuracy", session
->overridden_geoposition
->accuracy
);
506 // Set a dummy altitude to make WebDriver clients happy.
507 // https://code.google.com/p/chromedriver/issues/detail?id=281
508 location
.SetDouble("altitude", 0);
509 value
->reset(location
.DeepCopy());
513 Status
ExecuteGetNetworkConditions(
515 const base::DictionaryValue
& params
,
516 scoped_ptr
<base::Value
>* value
) {
517 if (!session
->overridden_network_conditions
) {
518 return Status(kUnknownError
,
519 "network conditions must be set before it can be retrieved");
521 base::DictionaryValue conditions
;
522 conditions
.SetBoolean("offline",
523 session
->overridden_network_conditions
->offline
);
524 conditions
.SetInteger("latency",
525 session
->overridden_network_conditions
->latency
);
526 conditions
.SetInteger(
527 "download_throughput",
528 session
->overridden_network_conditions
->download_throughput
);
529 conditions
.SetInteger(
531 session
->overridden_network_conditions
->upload_throughput
);
532 value
->reset(conditions
.DeepCopy());
536 Status
ExecuteGetWindowPosition(
538 const base::DictionaryValue
& params
,
539 scoped_ptr
<base::Value
>* value
) {
540 ChromeDesktopImpl
* desktop
= NULL
;
541 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
542 if (status
.IsError())
545 AutomationExtension
* extension
= NULL
;
546 status
= desktop
->GetAutomationExtension(&extension
);
547 if (status
.IsError())
551 status
= extension
->GetWindowPosition(&x
, &y
);
552 if (status
.IsError())
555 base::DictionaryValue position
;
556 position
.SetInteger("x", x
);
557 position
.SetInteger("y", y
);
558 value
->reset(position
.DeepCopy());
562 Status
ExecuteSetWindowPosition(
564 const base::DictionaryValue
& params
,
565 scoped_ptr
<base::Value
>* value
) {
568 if (!params
.GetDouble("x", &x
) || !params
.GetDouble("y", &y
))
569 return Status(kUnknownError
, "missing or invalid 'x' or 'y'");
571 ChromeDesktopImpl
* desktop
= NULL
;
572 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
573 if (status
.IsError())
576 AutomationExtension
* extension
= NULL
;
577 status
= desktop
->GetAutomationExtension(&extension
);
578 if (status
.IsError())
581 return extension
->SetWindowPosition(static_cast<int>(x
), static_cast<int>(y
));
584 Status
ExecuteGetWindowSize(
586 const base::DictionaryValue
& params
,
587 scoped_ptr
<base::Value
>* value
) {
588 ChromeDesktopImpl
* desktop
= NULL
;
589 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
590 if (status
.IsError())
593 AutomationExtension
* extension
= NULL
;
594 status
= desktop
->GetAutomationExtension(&extension
);
595 if (status
.IsError())
599 status
= extension
->GetWindowSize(&width
, &height
);
600 if (status
.IsError())
603 base::DictionaryValue size
;
604 size
.SetInteger("width", width
);
605 size
.SetInteger("height", height
);
606 value
->reset(size
.DeepCopy());
610 Status
ExecuteSetWindowSize(
612 const base::DictionaryValue
& params
,
613 scoped_ptr
<base::Value
>* value
) {
616 if (!params
.GetDouble("width", &width
) ||
617 !params
.GetDouble("height", &height
))
618 return Status(kUnknownError
, "missing or invalid 'width' or 'height'");
620 ChromeDesktopImpl
* desktop
= NULL
;
621 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
622 if (status
.IsError())
625 AutomationExtension
* extension
= NULL
;
626 status
= desktop
->GetAutomationExtension(&extension
);
627 if (status
.IsError())
630 return extension
->SetWindowSize(
631 static_cast<int>(width
), static_cast<int>(height
));
634 Status
ExecuteMaximizeWindow(
636 const base::DictionaryValue
& params
,
637 scoped_ptr
<base::Value
>* value
) {
638 ChromeDesktopImpl
* desktop
= NULL
;
639 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
640 if (status
.IsError())
643 AutomationExtension
* extension
= NULL
;
644 status
= desktop
->GetAutomationExtension(&extension
);
645 if (status
.IsError())
648 return extension
->MaximizeWindow();
651 Status
ExecuteGetAvailableLogTypes(
653 const base::DictionaryValue
& params
,
654 scoped_ptr
<base::Value
>* value
) {
655 scoped_ptr
<base::ListValue
> types(new base::ListValue());
656 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
657 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
660 types
->AppendString((*log
)->type());
662 *value
= types
.Pass();
666 Status
ExecuteGetLog(
668 const base::DictionaryValue
& params
,
669 scoped_ptr
<base::Value
>* value
) {
670 std::string log_type
;
671 if (!params
.GetString("type", &log_type
)) {
672 return Status(kUnknownError
, "missing or invalid 'type'");
674 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
675 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
678 if (log_type
== (*log
)->type()) {
679 *value
= (*log
)->GetAndClearEntries();
683 return Status(kUnknownError
, "log type '" + log_type
+ "' not found");
686 Status
ExecuteUploadFile(
688 const base::DictionaryValue
& params
,
689 scoped_ptr
<base::Value
>* value
) {
690 std::string base64_zip_data
;
691 if (!params
.GetString("file", &base64_zip_data
))
692 return Status(kUnknownError
, "missing or invalid 'file'");
693 std::string zip_data
;
694 if (!Base64Decode(base64_zip_data
, &zip_data
))
695 return Status(kUnknownError
, "unable to decode 'file'");
697 if (!session
->temp_dir
.IsValid()) {
698 if (!session
->temp_dir
.CreateUniqueTempDir())
699 return Status(kUnknownError
, "unable to create temp dir");
701 base::FilePath upload_dir
;
702 if (!base::CreateTemporaryDirInDir(session
->temp_dir
.path(),
703 FILE_PATH_LITERAL("upload"),
705 return Status(kUnknownError
, "unable to create temp dir");
707 std::string error_msg
;
708 base::FilePath upload
;
709 Status status
= UnzipSoleFile(upload_dir
, zip_data
, &upload
);
710 if (status
.IsError())
711 return Status(kUnknownError
, "unable to unzip 'file'", status
);
713 value
->reset(new base::StringValue(upload
.value()));
717 Status
ExecuteIsAutoReporting(
719 const base::DictionaryValue
& params
,
720 scoped_ptr
<base::Value
>* value
) {
721 value
->reset(new base::FundamentalValue(session
->auto_reporting_enabled
));
725 Status
ExecuteSetAutoReporting(
727 const base::DictionaryValue
& params
,
728 scoped_ptr
<base::Value
>* value
) {
730 if (!params
.GetBoolean("enabled", &enabled
))
731 return Status(kUnknownError
, "missing parameter 'enabled'");
732 session
->auto_reporting_enabled
= enabled
;