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/message_loop/message_loop_proxy.h"
15 #include "base/synchronization/lock.h"
16 #include "base/synchronization/waitable_event.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/chrome.h"
22 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
23 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
24 #include "chrome/test/chromedriver/chrome/device_manager.h"
25 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
26 #include "chrome/test/chromedriver/chrome/geoposition.h"
27 #include "chrome/test/chromedriver/chrome/status.h"
28 #include "chrome/test/chromedriver/chrome/version.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 scoped_ptr
<base::DictionaryValue
> chrome_caps(new base::DictionaryValue());
95 if (chrome
->GetAsDesktop()) {
96 chrome_caps
->SetString(
98 chrome
->GetAsDesktop()->command().GetSwitchValueNative(
101 caps
->Set("chrome", chrome_caps
.release());
105 Status
InitSessionHelper(
106 const InitSessionParams
& bound_params
,
108 const base::DictionaryValue
& params
,
109 scoped_ptr
<base::Value
>* value
) {
110 session
->driver_log
.reset(
111 new WebDriverLog(WebDriverLog::kDriverType
, Log::kAll
));
112 const base::DictionaryValue
* desired_caps
;
113 if (!params
.GetDictionary("desiredCapabilities", &desired_caps
))
114 return Status(kUnknownError
, "cannot find dict 'desiredCapabilities'");
116 Capabilities capabilities
;
117 Status status
= capabilities
.Parse(*desired_caps
);
118 if (status
.IsError())
121 Log::Level driver_level
= Log::kWarning
;
122 if (capabilities
.logging_prefs
.count(WebDriverLog::kDriverType
))
123 driver_level
= capabilities
.logging_prefs
[WebDriverLog::kDriverType
];
124 session
->driver_log
->set_min_level(driver_level
);
126 // Create Log's and DevToolsEventListener's for ones that are DevTools-based.
127 // Session will own the Log's, Chrome will own the listeners.
128 // Also create |CommandListener|s for the appropriate logs.
129 ScopedVector
<DevToolsEventListener
> devtools_event_listeners
;
130 ScopedVector
<CommandListener
> command_listeners
;
131 status
= CreateLogs(capabilities
,
133 &session
->devtools_logs
,
134 &devtools_event_listeners
,
136 if (status
.IsError())
139 // |session| will own the |CommandListener|s.
140 session
->command_listeners
.swap(command_listeners
);
142 status
= LaunchChrome(bound_params
.context_getter
.get(),
143 bound_params
.socket_factory
,
144 bound_params
.device_manager
,
145 bound_params
.port_server
,
146 bound_params
.port_manager
,
148 devtools_event_listeners
,
150 if (status
.IsError())
153 std::list
<std::string
> web_view_ids
;
154 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
155 if (status
.IsError() || web_view_ids
.empty()) {
156 return status
.IsError() ? status
:
157 Status(kUnknownError
, "unable to discover open window in chrome");
160 session
->window
= web_view_ids
.front();
161 session
->detach
= capabilities
.detach
;
162 session
->force_devtools_screenshot
= capabilities
.force_devtools_screenshot
;
163 session
->capabilities
= CreateCapabilities(session
->chrome
.get());
164 value
->reset(session
->capabilities
->DeepCopy());
170 Status
ExecuteInitSession(
171 const InitSessionParams
& bound_params
,
173 const base::DictionaryValue
& params
,
174 scoped_ptr
<base::Value
>* value
) {
175 Status status
= InitSessionHelper(bound_params
, session
, params
, value
);
176 if (status
.IsError()) {
177 session
->quit
= true;
178 if (session
->chrome
!= NULL
)
179 session
->chrome
->Quit();
187 const base::DictionaryValue
& params
,
188 scoped_ptr
<base::Value
>* value
) {
189 session
->quit
= true;
190 if (allow_detach
&& session
->detach
)
193 return session
->chrome
->Quit();
196 Status
ExecuteGetSessionCapabilities(
198 const base::DictionaryValue
& params
,
199 scoped_ptr
<base::Value
>* value
) {
200 value
->reset(session
->capabilities
->DeepCopy());
204 Status
ExecuteGetCurrentWindowHandle(
206 const base::DictionaryValue
& params
,
207 scoped_ptr
<base::Value
>* value
) {
208 WebView
* web_view
= NULL
;
209 Status status
= session
->GetTargetWindow(&web_view
);
210 if (status
.IsError())
214 new base::StringValue(WebViewIdToWindowHandle(web_view
->GetId())));
218 Status
ExecuteLaunchApp(
220 const base::DictionaryValue
& params
,
221 scoped_ptr
<base::Value
>* value
) {
223 if (!params
.GetString("id", &id
))
224 return Status(kUnknownError
, "'id' must be a string");
226 if (!session
->chrome
->GetAsDesktop())
227 return Status(kUnknownError
,
228 "apps can only be launched on desktop platforms");
230 AutomationExtension
* extension
= NULL
;
232 session
->chrome
->GetAsDesktop()->GetAutomationExtension(&extension
);
233 if (status
.IsError())
236 return extension
->LaunchApp(id
);
241 const base::DictionaryValue
& params
,
242 scoped_ptr
<base::Value
>* value
) {
243 std::list
<std::string
> web_view_ids
;
244 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
245 if (status
.IsError())
247 bool is_last_web_view
= web_view_ids
.size() == 1u;
248 web_view_ids
.clear();
250 WebView
* web_view
= NULL
;
251 status
= session
->GetTargetWindow(&web_view
);
252 if (status
.IsError())
255 status
= session
->chrome
->CloseWebView(web_view
->GetId());
256 if (status
.IsError())
259 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
260 if ((status
.code() == kChromeNotReachable
&& is_last_web_view
) ||
261 (status
.IsOk() && web_view_ids
.empty())) {
262 // If no window is open, close is the equivalent of calling "quit".
263 session
->quit
= true;
264 return session
->chrome
->Quit();
270 Status
ExecuteGetWindowHandles(
272 const base::DictionaryValue
& params
,
273 scoped_ptr
<base::Value
>* value
) {
274 std::list
<std::string
> web_view_ids
;
275 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
276 if (status
.IsError())
278 scoped_ptr
<base::ListValue
> window_ids(new base::ListValue());
279 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
280 it
!= web_view_ids
.end(); ++it
) {
281 window_ids
->AppendString(WebViewIdToWindowHandle(*it
));
283 value
->reset(window_ids
.release());
287 Status
ExecuteSwitchToWindow(
289 const base::DictionaryValue
& params
,
290 scoped_ptr
<base::Value
>* value
) {
292 if (!params
.GetString("name", &name
) || name
.empty())
293 return Status(kUnknownError
, "'name' must be a nonempty string");
295 std::list
<std::string
> web_view_ids
;
296 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
297 if (status
.IsError())
300 std::string web_view_id
;
302 if (WindowHandleToWebViewId(name
, &web_view_id
)) {
303 // Check if any web_view matches |web_view_id|.
304 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
305 it
!= web_view_ids
.end(); ++it
) {
306 if (*it
== web_view_id
) {
312 // Check if any of the tab window names match |name|.
313 const char* kGetWindowNameScript
= "function() { return window.name; }";
314 base::ListValue args
;
315 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
316 it
!= web_view_ids
.end(); ++it
) {
317 scoped_ptr
<base::Value
> result
;
319 status
= session
->chrome
->GetWebViewById(*it
, &web_view
);
320 if (status
.IsError())
322 status
= web_view
->ConnectIfNecessary();
323 if (status
.IsError())
325 status
= web_view
->CallFunction(
326 std::string(), kGetWindowNameScript
, args
, &result
);
327 if (status
.IsError())
329 std::string window_name
;
330 if (!result
->GetAsString(&window_name
))
331 return Status(kUnknownError
, "failed to get window name");
332 if (window_name
== name
) {
341 return Status(kNoSuchWindow
);
343 if (session
->overridden_geoposition
) {
345 status
= session
->chrome
->GetWebViewById(web_view_id
, &web_view
);
346 if (status
.IsError())
348 status
= web_view
->ConnectIfNecessary();
349 if (status
.IsError())
351 status
= web_view
->OverrideGeolocation(*session
->overridden_geoposition
);
352 if (status
.IsError())
356 session
->window
= web_view_id
;
357 session
->SwitchToTopFrame();
358 session
->mouse_position
= WebPoint(0, 0);
362 Status
ExecuteSetTimeout(
364 const base::DictionaryValue
& params
,
365 scoped_ptr
<base::Value
>* value
) {
367 if (!params
.GetDouble("ms", &ms_double
))
368 return Status(kUnknownError
, "'ms' must be a double");
370 if (!params
.GetString("type", &type
))
371 return Status(kUnknownError
, "'type' must be a string");
373 base::TimeDelta timeout
=
374 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double
));
375 // TODO(frankf): implicit and script timeout should be cleared
376 // if negative timeout is specified.
377 if (type
== "implicit") {
378 session
->implicit_wait
= timeout
;
379 } else if (type
== "script") {
380 session
->script_timeout
= timeout
;
381 } else if (type
== "page load") {
382 session
->page_load_timeout
=
383 ((timeout
< base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
386 return Status(kUnknownError
, "unknown type of timeout:" + type
);
391 Status
ExecuteSetScriptTimeout(
393 const base::DictionaryValue
& params
,
394 scoped_ptr
<base::Value
>* value
) {
396 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
397 return Status(kUnknownError
, "'ms' must be a non-negative number");
398 session
->script_timeout
=
399 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
403 Status
ExecuteImplicitlyWait(
405 const base::DictionaryValue
& params
,
406 scoped_ptr
<base::Value
>* value
) {
408 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
409 return Status(kUnknownError
, "'ms' must be a non-negative number");
410 session
->implicit_wait
=
411 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
415 Status
ExecuteIsLoading(
417 const base::DictionaryValue
& params
,
418 scoped_ptr
<base::Value
>* value
) {
419 WebView
* web_view
= NULL
;
420 Status status
= session
->GetTargetWindow(&web_view
);
421 if (status
.IsError())
424 status
= web_view
->ConnectIfNecessary();
425 if (status
.IsError())
429 status
= web_view
->IsPendingNavigation(
430 session
->GetCurrentFrameId(), &is_pending
);
431 if (status
.IsError())
433 value
->reset(new base::FundamentalValue(is_pending
));
437 Status
ExecuteGetLocation(
439 const base::DictionaryValue
& params
,
440 scoped_ptr
<base::Value
>* value
) {
441 if (!session
->overridden_geoposition
) {
442 return Status(kUnknownError
,
443 "Location must be set before it can be retrieved");
445 base::DictionaryValue location
;
446 location
.SetDouble("latitude", session
->overridden_geoposition
->latitude
);
447 location
.SetDouble("longitude", session
->overridden_geoposition
->longitude
);
448 location
.SetDouble("accuracy", session
->overridden_geoposition
->accuracy
);
449 // Set a dummy altitude to make WebDriver clients happy.
450 // https://code.google.com/p/chromedriver/issues/detail?id=281
451 location
.SetDouble("altitude", 0);
452 value
->reset(location
.DeepCopy());
456 Status
ExecuteGetWindowPosition(
458 const base::DictionaryValue
& params
,
459 scoped_ptr
<base::Value
>* value
) {
460 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
464 "command only supported for desktop Chrome without debuggerAddress");
467 AutomationExtension
* extension
= NULL
;
468 Status status
= desktop
->GetAutomationExtension(&extension
);
469 if (status
.IsError())
473 status
= extension
->GetWindowPosition(&x
, &y
);
474 if (status
.IsError())
477 base::DictionaryValue position
;
478 position
.SetInteger("x", x
);
479 position
.SetInteger("y", y
);
480 value
->reset(position
.DeepCopy());
484 Status
ExecuteSetWindowPosition(
486 const base::DictionaryValue
& params
,
487 scoped_ptr
<base::Value
>* value
) {
490 if (!params
.GetDouble("x", &x
) || !params
.GetDouble("y", &y
))
491 return Status(kUnknownError
, "missing or invalid 'x' or 'y'");
493 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
497 "command only supported for desktop Chrome without debuggerAddress");
500 AutomationExtension
* extension
= NULL
;
501 Status status
= desktop
->GetAutomationExtension(&extension
);
502 if (status
.IsError())
505 return extension
->SetWindowPosition(static_cast<int>(x
), static_cast<int>(y
));
508 Status
ExecuteGetWindowSize(
510 const base::DictionaryValue
& params
,
511 scoped_ptr
<base::Value
>* value
) {
512 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
516 "command only supported for desktop Chrome without debuggerAddress");
519 AutomationExtension
* extension
= NULL
;
520 Status status
= desktop
->GetAutomationExtension(&extension
);
521 if (status
.IsError())
525 status
= extension
->GetWindowSize(&width
, &height
);
526 if (status
.IsError())
529 base::DictionaryValue size
;
530 size
.SetInteger("width", width
);
531 size
.SetInteger("height", height
);
532 value
->reset(size
.DeepCopy());
536 Status
ExecuteSetWindowSize(
538 const base::DictionaryValue
& params
,
539 scoped_ptr
<base::Value
>* value
) {
542 if (!params
.GetDouble("width", &width
) ||
543 !params
.GetDouble("height", &height
))
544 return Status(kUnknownError
, "missing or invalid 'width' or 'height'");
546 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
550 "command only supported for desktop Chrome without debuggerAddress");
553 AutomationExtension
* extension
= NULL
;
554 Status status
= desktop
->GetAutomationExtension(&extension
);
555 if (status
.IsError())
558 return extension
->SetWindowSize(
559 static_cast<int>(width
), static_cast<int>(height
));
562 Status
ExecuteMaximizeWindow(
564 const base::DictionaryValue
& params
,
565 scoped_ptr
<base::Value
>* value
) {
566 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
570 "command only supported for desktop Chrome without debuggerAddress");
573 AutomationExtension
* extension
= NULL
;
574 Status status
= desktop
->GetAutomationExtension(&extension
);
575 if (status
.IsError())
578 return extension
->MaximizeWindow();
581 Status
ExecuteGetAvailableLogTypes(
583 const base::DictionaryValue
& params
,
584 scoped_ptr
<base::Value
>* value
) {
585 scoped_ptr
<base::ListValue
> types(new base::ListValue());
586 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
587 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
590 types
->AppendString((*log
)->type());
592 *value
= types
.Pass();
596 Status
ExecuteGetLog(
598 const base::DictionaryValue
& params
,
599 scoped_ptr
<base::Value
>* value
) {
600 std::string log_type
;
601 if (!params
.GetString("type", &log_type
)) {
602 return Status(kUnknownError
, "missing or invalid 'type'");
604 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
605 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
608 if (log_type
== (*log
)->type()) {
609 *value
= (*log
)->GetAndClearEntries();
613 return Status(kUnknownError
, "log type '" + log_type
+ "' not found");
616 Status
ExecuteUploadFile(
618 const base::DictionaryValue
& params
,
619 scoped_ptr
<base::Value
>* value
) {
620 std::string base64_zip_data
;
621 if (!params
.GetString("file", &base64_zip_data
))
622 return Status(kUnknownError
, "missing or invalid 'file'");
623 std::string zip_data
;
624 if (!Base64Decode(base64_zip_data
, &zip_data
))
625 return Status(kUnknownError
, "unable to decode 'file'");
627 if (!session
->temp_dir
.IsValid()) {
628 if (!session
->temp_dir
.CreateUniqueTempDir())
629 return Status(kUnknownError
, "unable to create temp dir");
631 base::FilePath upload_dir
;
632 if (!base::CreateTemporaryDirInDir(session
->temp_dir
.path(),
633 FILE_PATH_LITERAL("upload"),
635 return Status(kUnknownError
, "unable to create temp dir");
637 std::string error_msg
;
638 base::FilePath upload
;
639 Status status
= UnzipSoleFile(upload_dir
, zip_data
, &upload
);
640 if (status
.IsError())
641 return Status(kUnknownError
, "unable to unzip 'file'", status
);
643 value
->reset(new base::StringValue(upload
.value()));
647 Status
ExecuteIsAutoReporting(
649 const base::DictionaryValue
& params
,
650 scoped_ptr
<base::Value
>* value
) {
651 value
->reset(new base::FundamentalValue(session
->auto_reporting_enabled
));
655 Status
ExecuteSetAutoReporting(
657 const base::DictionaryValue
& params
,
658 scoped_ptr
<base::Value
>* value
) {
660 if (!params
.GetBoolean("enabled", &enabled
))
661 return Status(kUnknownError
, "missing parameter 'enabled'");
662 session
->auto_reporting_enabled
= enabled
;