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/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/web_view.h"
29 #include "chrome/test/chromedriver/chrome_launcher.h"
30 #include "chrome/test/chromedriver/logging.h"
31 #include "chrome/test/chromedriver/net/url_request_context_getter.h"
32 #include "chrome/test/chromedriver/session.h"
33 #include "chrome/test/chromedriver/util.h"
34 #include "chrome/test/chromedriver/version.h"
38 const char kWindowHandlePrefix
[] = "CDwindow-";
40 std::string
WebViewIdToWindowHandle(const std::string
& web_view_id
) {
41 return kWindowHandlePrefix
+ web_view_id
;
44 bool WindowHandleToWebViewId(const std::string
& window_handle
,
45 std::string
* web_view_id
) {
46 if (window_handle
.find(kWindowHandlePrefix
) != 0u)
48 *web_view_id
= window_handle
.substr(
49 std::string(kWindowHandlePrefix
).length());
55 InitSessionParams::InitSessionParams(
56 scoped_refptr
<URLRequestContextGetter
> context_getter
,
57 const SyncWebSocketFactory
& socket_factory
,
58 DeviceManager
* device_manager
,
59 PortServer
* port_server
,
60 PortManager
* port_manager
)
61 : context_getter(context_getter
),
62 socket_factory(socket_factory
),
63 device_manager(device_manager
),
64 port_server(port_server
),
65 port_manager(port_manager
) {}
67 InitSessionParams::~InitSessionParams() {}
71 scoped_ptr
<base::DictionaryValue
> CreateCapabilities(Chrome
* chrome
) {
72 scoped_ptr
<base::DictionaryValue
> caps(new base::DictionaryValue());
73 caps
->SetString("browserName", "chrome");
74 caps
->SetString("version", chrome
->GetVersion());
75 caps
->SetString("chrome.chromedriverVersion", kChromeDriverVersion
);
76 caps
->SetString("platform", chrome
->GetOperatingSystemName());
77 caps
->SetBoolean("javascriptEnabled", true);
78 caps
->SetBoolean("takesScreenshot", true);
79 caps
->SetBoolean("takesHeapSnapshot", true);
80 caps
->SetBoolean("handlesAlerts", true);
81 caps
->SetBoolean("databaseEnabled", false);
82 caps
->SetBoolean("locationContextEnabled", true);
83 caps
->SetBoolean("applicationCacheEnabled", false);
84 caps
->SetBoolean("browserConnectionEnabled", false);
85 caps
->SetBoolean("cssSelectorsEnabled", true);
86 caps
->SetBoolean("webStorageEnabled", true);
87 caps
->SetBoolean("rotatable", false);
88 caps
->SetBoolean("acceptSslCerts", true);
89 caps
->SetBoolean("nativeEvents", true);
90 scoped_ptr
<base::DictionaryValue
> chrome_caps(new base::DictionaryValue());
91 if (chrome
->GetAsDesktop()) {
92 chrome_caps
->SetString(
94 chrome
->GetAsDesktop()->command().GetSwitchValueNative(
97 caps
->Set("chrome", chrome_caps
.release());
102 Status
InitSessionHelper(
103 const InitSessionParams
& bound_params
,
105 const base::DictionaryValue
& params
,
106 scoped_ptr
<base::Value
>* value
) {
107 session
->driver_log
.reset(
108 new WebDriverLog(WebDriverLog::kDriverType
, Log::kAll
));
109 const base::DictionaryValue
* desired_caps
;
110 if (!params
.GetDictionary("desiredCapabilities", &desired_caps
))
111 return Status(kUnknownError
, "cannot find dict 'desiredCapabilities'");
113 Capabilities capabilities
;
114 Status status
= capabilities
.Parse(*desired_caps
);
115 if (status
.IsError())
118 Log::Level driver_level
= Log::kWarning
;
119 if (capabilities
.logging_prefs
.count(WebDriverLog::kDriverType
))
120 driver_level
= capabilities
.logging_prefs
[WebDriverLog::kDriverType
];
121 session
->driver_log
->set_min_level(driver_level
);
123 // Create Log's and DevToolsEventListener's for ones that are DevTools-based.
124 // Session will own the Log's, Chrome will own the listeners.
125 ScopedVector
<DevToolsEventListener
> devtools_event_listeners
;
126 status
= CreateLogs(capabilities
,
127 &session
->devtools_logs
,
128 &devtools_event_listeners
);
129 if (status
.IsError())
132 status
= LaunchChrome(bound_params
.context_getter
.get(),
133 bound_params
.socket_factory
,
134 bound_params
.device_manager
,
135 bound_params
.port_server
,
136 bound_params
.port_manager
,
138 devtools_event_listeners
,
140 if (status
.IsError())
143 std::list
<std::string
> web_view_ids
;
144 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
145 if (status
.IsError() || web_view_ids
.empty()) {
146 return status
.IsError() ? status
:
147 Status(kUnknownError
, "unable to discover open window in chrome");
150 session
->window
= web_view_ids
.front();
151 session
->detach
= capabilities
.detach
;
152 session
->force_devtools_screenshot
= capabilities
.force_devtools_screenshot
;
153 session
->capabilities
= CreateCapabilities(session
->chrome
.get());
154 value
->reset(session
->capabilities
->DeepCopy());
160 Status
ExecuteInitSession(
161 const InitSessionParams
& bound_params
,
163 const base::DictionaryValue
& params
,
164 scoped_ptr
<base::Value
>* value
) {
165 Status status
= InitSessionHelper(bound_params
, session
, params
, value
);
166 if (status
.IsError()) {
167 session
->quit
= true;
168 if (session
->chrome
!= NULL
)
169 session
->chrome
->Quit();
177 const base::DictionaryValue
& params
,
178 scoped_ptr
<base::Value
>* value
) {
179 session
->quit
= true;
180 if (allow_detach
&& session
->detach
)
183 return session
->chrome
->Quit();
186 Status
ExecuteGetSessionCapabilities(
188 const base::DictionaryValue
& params
,
189 scoped_ptr
<base::Value
>* value
) {
190 value
->reset(session
->capabilities
->DeepCopy());
194 Status
ExecuteGetCurrentWindowHandle(
196 const base::DictionaryValue
& params
,
197 scoped_ptr
<base::Value
>* value
) {
198 WebView
* web_view
= NULL
;
199 Status status
= session
->GetTargetWindow(&web_view
);
200 if (status
.IsError())
204 new base::StringValue(WebViewIdToWindowHandle(web_view
->GetId())));
208 Status
ExecuteLaunchApp(
210 const base::DictionaryValue
& params
,
211 scoped_ptr
<base::Value
>* value
) {
213 if (!params
.GetString("id", &id
))
214 return Status(kUnknownError
, "'id' must be a string");
216 if (!session
->chrome
->GetAsDesktop())
217 return Status(kUnknownError
,
218 "apps can only be launched on desktop platforms");
220 AutomationExtension
* extension
= NULL
;
222 session
->chrome
->GetAsDesktop()->GetAutomationExtension(&extension
);
223 if (status
.IsError())
226 return extension
->LaunchApp(id
);
231 const base::DictionaryValue
& params
,
232 scoped_ptr
<base::Value
>* value
) {
233 std::list
<std::string
> web_view_ids
;
234 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
235 if (status
.IsError())
237 bool is_last_web_view
= web_view_ids
.size() == 1u;
238 web_view_ids
.clear();
240 WebView
* web_view
= NULL
;
241 status
= session
->GetTargetWindow(&web_view
);
242 if (status
.IsError())
245 status
= session
->chrome
->CloseWebView(web_view
->GetId());
246 if (status
.IsError())
249 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
250 if ((status
.code() == kChromeNotReachable
&& is_last_web_view
) ||
251 (status
.IsOk() && web_view_ids
.empty())) {
252 // If no window is open, close is the equivalent of calling "quit".
253 session
->quit
= true;
254 return session
->chrome
->Quit();
260 Status
ExecuteGetWindowHandles(
262 const base::DictionaryValue
& params
,
263 scoped_ptr
<base::Value
>* value
) {
264 std::list
<std::string
> web_view_ids
;
265 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
266 if (status
.IsError())
268 scoped_ptr
<base::ListValue
> window_ids(new base::ListValue());
269 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
270 it
!= web_view_ids
.end(); ++it
) {
271 window_ids
->AppendString(WebViewIdToWindowHandle(*it
));
273 value
->reset(window_ids
.release());
277 Status
ExecuteSwitchToWindow(
279 const base::DictionaryValue
& params
,
280 scoped_ptr
<base::Value
>* value
) {
282 if (!params
.GetString("name", &name
) || name
.empty())
283 return Status(kUnknownError
, "'name' must be a nonempty string");
285 std::list
<std::string
> web_view_ids
;
286 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
287 if (status
.IsError())
290 std::string web_view_id
;
292 if (WindowHandleToWebViewId(name
, &web_view_id
)) {
293 // Check if any web_view matches |web_view_id|.
294 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
295 it
!= web_view_ids
.end(); ++it
) {
296 if (*it
== web_view_id
) {
302 // Check if any of the tab window names match |name|.
303 const char* kGetWindowNameScript
= "function() { return window.name; }";
304 base::ListValue args
;
305 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
306 it
!= web_view_ids
.end(); ++it
) {
307 scoped_ptr
<base::Value
> result
;
309 status
= session
->chrome
->GetWebViewById(*it
, &web_view
);
310 if (status
.IsError())
312 status
= web_view
->ConnectIfNecessary();
313 if (status
.IsError())
315 status
= web_view
->CallFunction(
316 std::string(), kGetWindowNameScript
, args
, &result
);
317 if (status
.IsError())
319 std::string window_name
;
320 if (!result
->GetAsString(&window_name
))
321 return Status(kUnknownError
, "failed to get window name");
322 if (window_name
== name
) {
331 return Status(kNoSuchWindow
);
333 if (session
->overridden_geoposition
) {
335 status
= session
->chrome
->GetWebViewById(web_view_id
, &web_view
);
336 if (status
.IsError())
338 status
= web_view
->ConnectIfNecessary();
339 if (status
.IsError())
341 status
= web_view
->OverrideGeolocation(*session
->overridden_geoposition
);
342 if (status
.IsError())
346 session
->window
= web_view_id
;
347 session
->SwitchToTopFrame();
348 session
->mouse_position
= WebPoint(0, 0);
352 Status
ExecuteSetTimeout(
354 const base::DictionaryValue
& params
,
355 scoped_ptr
<base::Value
>* value
) {
357 if (!params
.GetDouble("ms", &ms_double
))
358 return Status(kUnknownError
, "'ms' must be a double");
360 if (!params
.GetString("type", &type
))
361 return Status(kUnknownError
, "'type' must be a string");
363 base::TimeDelta timeout
=
364 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double
));
365 // TODO(frankf): implicit and script timeout should be cleared
366 // if negative timeout is specified.
367 if (type
== "implicit") {
368 session
->implicit_wait
= timeout
;
369 } else if (type
== "script") {
370 session
->script_timeout
= timeout
;
371 } else if (type
== "page load") {
372 session
->page_load_timeout
=
373 ((timeout
< base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
376 return Status(kUnknownError
, "unknown type of timeout:" + type
);
381 Status
ExecuteSetScriptTimeout(
383 const base::DictionaryValue
& params
,
384 scoped_ptr
<base::Value
>* value
) {
386 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
387 return Status(kUnknownError
, "'ms' must be a non-negative number");
388 session
->script_timeout
=
389 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
393 Status
ExecuteImplicitlyWait(
395 const base::DictionaryValue
& params
,
396 scoped_ptr
<base::Value
>* value
) {
398 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
399 return Status(kUnknownError
, "'ms' must be a non-negative number");
400 session
->implicit_wait
=
401 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
405 Status
ExecuteIsLoading(
407 const base::DictionaryValue
& params
,
408 scoped_ptr
<base::Value
>* value
) {
409 WebView
* web_view
= NULL
;
410 Status status
= session
->GetTargetWindow(&web_view
);
411 if (status
.IsError())
414 status
= web_view
->ConnectIfNecessary();
415 if (status
.IsError())
419 status
= web_view
->IsPendingNavigation(
420 session
->GetCurrentFrameId(), &is_pending
);
421 if (status
.IsError())
423 value
->reset(new base::FundamentalValue(is_pending
));
427 Status
ExecuteGetLocation(
429 const base::DictionaryValue
& params
,
430 scoped_ptr
<base::Value
>* value
) {
431 if (!session
->overridden_geoposition
) {
432 return Status(kUnknownError
,
433 "Location must be set before it can be retrieved");
435 base::DictionaryValue location
;
436 location
.SetDouble("latitude", session
->overridden_geoposition
->latitude
);
437 location
.SetDouble("longitude", session
->overridden_geoposition
->longitude
);
438 location
.SetDouble("accuracy", session
->overridden_geoposition
->accuracy
);
439 // Set a dummy altitude to make WebDriver clients happy.
440 // https://code.google.com/p/chromedriver/issues/detail?id=281
441 location
.SetDouble("altitude", 0);
442 value
->reset(location
.DeepCopy());
446 Status
ExecuteGetWindowPosition(
448 const base::DictionaryValue
& params
,
449 scoped_ptr
<base::Value
>* value
) {
450 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
454 "command only supported for desktop Chrome without debuggerAddress");
457 AutomationExtension
* extension
= NULL
;
458 Status status
= desktop
->GetAutomationExtension(&extension
);
459 if (status
.IsError())
463 status
= extension
->GetWindowPosition(&x
, &y
);
464 if (status
.IsError())
467 base::DictionaryValue position
;
468 position
.SetInteger("x", x
);
469 position
.SetInteger("y", y
);
470 value
->reset(position
.DeepCopy());
474 Status
ExecuteSetWindowPosition(
476 const base::DictionaryValue
& params
,
477 scoped_ptr
<base::Value
>* value
) {
479 if (!params
.GetDouble("x", &x
) || !params
.GetDouble("y", &y
))
480 return Status(kUnknownError
, "missing or invalid 'x' or 'y'");
482 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
486 "command only supported for desktop Chrome without debuggerAddress");
489 AutomationExtension
* extension
= NULL
;
490 Status status
= desktop
->GetAutomationExtension(&extension
);
491 if (status
.IsError())
494 return extension
->SetWindowPosition(static_cast<int>(x
), static_cast<int>(y
));
497 Status
ExecuteGetWindowSize(
499 const base::DictionaryValue
& params
,
500 scoped_ptr
<base::Value
>* value
) {
501 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
505 "command only supported for desktop Chrome without debuggerAddress");
508 AutomationExtension
* extension
= NULL
;
509 Status status
= desktop
->GetAutomationExtension(&extension
);
510 if (status
.IsError())
514 status
= extension
->GetWindowSize(&width
, &height
);
515 if (status
.IsError())
518 base::DictionaryValue size
;
519 size
.SetInteger("width", width
);
520 size
.SetInteger("height", height
);
521 value
->reset(size
.DeepCopy());
525 Status
ExecuteSetWindowSize(
527 const base::DictionaryValue
& params
,
528 scoped_ptr
<base::Value
>* value
) {
529 double width
, height
;
530 if (!params
.GetDouble("width", &width
) ||
531 !params
.GetDouble("height", &height
))
532 return Status(kUnknownError
, "missing or invalid 'width' or 'height'");
534 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
538 "command only supported for desktop Chrome without debuggerAddress");
541 AutomationExtension
* extension
= NULL
;
542 Status status
= desktop
->GetAutomationExtension(&extension
);
543 if (status
.IsError())
546 return extension
->SetWindowSize(
547 static_cast<int>(width
), static_cast<int>(height
));
550 Status
ExecuteMaximizeWindow(
552 const base::DictionaryValue
& params
,
553 scoped_ptr
<base::Value
>* value
) {
554 ChromeDesktopImpl
* desktop
= session
->chrome
->GetAsDesktop();
558 "command only supported for desktop Chrome without debuggerAddress");
561 AutomationExtension
* extension
= NULL
;
562 Status status
= desktop
->GetAutomationExtension(&extension
);
563 if (status
.IsError())
566 return extension
->MaximizeWindow();
569 Status
ExecuteGetAvailableLogTypes(
571 const base::DictionaryValue
& params
,
572 scoped_ptr
<base::Value
>* value
) {
573 scoped_ptr
<base::ListValue
> types(new base::ListValue());
574 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
575 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
578 types
->AppendString((*log
)->type());
580 *value
= types
.Pass();
584 Status
ExecuteGetLog(
586 const base::DictionaryValue
& params
,
587 scoped_ptr
<base::Value
>* value
) {
588 std::string log_type
;
589 if (!params
.GetString("type", &log_type
)) {
590 return Status(kUnknownError
, "missing or invalid 'type'");
592 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
593 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
596 if (log_type
== (*log
)->type()) {
597 *value
= (*log
)->GetAndClearEntries();
601 return Status(kUnknownError
, "log type '" + log_type
+ "' not found");
604 Status
ExecuteUploadFile(
606 const base::DictionaryValue
& params
,
607 scoped_ptr
<base::Value
>* value
) {
608 std::string base64_zip_data
;
609 if (!params
.GetString("file", &base64_zip_data
))
610 return Status(kUnknownError
, "missing or invalid 'file'");
611 std::string zip_data
;
612 if (!Base64Decode(base64_zip_data
, &zip_data
))
613 return Status(kUnknownError
, "unable to decode 'file'");
615 if (!session
->temp_dir
.IsValid()) {
616 if (!session
->temp_dir
.CreateUniqueTempDir())
617 return Status(kUnknownError
, "unable to create temp dir");
619 base::FilePath upload_dir
;
620 if (!base::CreateTemporaryDirInDir(session
->temp_dir
.path(),
621 FILE_PATH_LITERAL("upload"),
623 return Status(kUnknownError
, "unable to create temp dir");
625 std::string error_msg
;
626 base::FilePath upload
;
627 Status status
= UnzipSoleFile(upload_dir
, zip_data
, &upload
);
628 if (status
.IsError())
629 return Status(kUnknownError
, "unable to unzip 'file'", status
);
631 value
->reset(new base::StringValue(upload
.value()));
635 Status
ExecuteIsAutoReporting(
637 const base::DictionaryValue
& params
,
638 scoped_ptr
<base::Value
>* value
) {
639 value
->reset(new base::FundamentalValue(session
->auto_reporting_enabled
));
643 Status
ExecuteSetAutoReporting(
645 const base::DictionaryValue
& params
,
646 scoped_ptr
<base::Value
>* value
) {
648 if (!params
.GetBoolean("enabled", &enabled
))
649 return Status(kUnknownError
, "missing parameter 'enabled'");
650 session
->auto_reporting_enabled
= enabled
;