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/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 scoped_ptr
<base::DictionaryValue
> chrome_caps(new base::DictionaryValue());
96 ChromeDesktopImpl
* desktop
= NULL
;
97 Status status
= chrome
->GetAsDesktop(&desktop
);
99 chrome_caps
->SetString(
101 desktop
->command().GetSwitchValueNative("user-data-dir"));
104 caps
->Set("chrome", chrome_caps
.release());
108 Status
InitSessionHelper(
109 const InitSessionParams
& bound_params
,
111 const base::DictionaryValue
& params
,
112 scoped_ptr
<base::Value
>* value
) {
113 session
->driver_log
.reset(
114 new WebDriverLog(WebDriverLog::kDriverType
, Log::kAll
));
115 const base::DictionaryValue
* desired_caps
;
116 if (!params
.GetDictionary("desiredCapabilities", &desired_caps
))
117 return Status(kUnknownError
, "cannot find dict 'desiredCapabilities'");
119 Capabilities capabilities
;
120 Status status
= capabilities
.Parse(*desired_caps
);
121 if (status
.IsError())
124 Log::Level driver_level
= Log::kWarning
;
125 if (capabilities
.logging_prefs
.count(WebDriverLog::kDriverType
))
126 driver_level
= capabilities
.logging_prefs
[WebDriverLog::kDriverType
];
127 session
->driver_log
->set_min_level(driver_level
);
129 // Create Log's and DevToolsEventListener's for ones that are DevTools-based.
130 // Session will own the Log's, Chrome will own the listeners.
131 // Also create |CommandListener|s for the appropriate logs.
132 ScopedVector
<DevToolsEventListener
> devtools_event_listeners
;
133 ScopedVector
<CommandListener
> command_listeners
;
134 status
= CreateLogs(capabilities
,
136 &session
->devtools_logs
,
137 &devtools_event_listeners
,
139 if (status
.IsError())
142 // |session| will own the |CommandListener|s.
143 session
->command_listeners
.swap(command_listeners
);
145 status
= LaunchChrome(bound_params
.context_getter
.get(),
146 bound_params
.socket_factory
,
147 bound_params
.device_manager
,
148 bound_params
.port_server
,
149 bound_params
.port_manager
,
151 &devtools_event_listeners
,
153 if (status
.IsError())
156 std::list
<std::string
> web_view_ids
;
157 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
158 if (status
.IsError() || web_view_ids
.empty()) {
159 return status
.IsError() ? status
:
160 Status(kUnknownError
, "unable to discover open window in chrome");
163 session
->window
= web_view_ids
.front();
164 session
->detach
= capabilities
.detach
;
165 session
->force_devtools_screenshot
= capabilities
.force_devtools_screenshot
;
166 session
->capabilities
= CreateCapabilities(session
->chrome
.get());
167 value
->reset(session
->capabilities
->DeepCopy());
173 Status
ExecuteInitSession(
174 const InitSessionParams
& bound_params
,
176 const base::DictionaryValue
& params
,
177 scoped_ptr
<base::Value
>* value
) {
178 Status status
= InitSessionHelper(bound_params
, session
, params
, value
);
179 if (status
.IsError()) {
180 session
->quit
= true;
181 if (session
->chrome
!= NULL
)
182 session
->chrome
->Quit();
190 const base::DictionaryValue
& params
,
191 scoped_ptr
<base::Value
>* value
) {
192 session
->quit
= true;
193 if (allow_detach
&& session
->detach
)
196 return session
->chrome
->Quit();
199 Status
ExecuteGetSessionCapabilities(
201 const base::DictionaryValue
& params
,
202 scoped_ptr
<base::Value
>* value
) {
203 value
->reset(session
->capabilities
->DeepCopy());
207 Status
ExecuteGetCurrentWindowHandle(
209 const base::DictionaryValue
& params
,
210 scoped_ptr
<base::Value
>* value
) {
211 WebView
* web_view
= NULL
;
212 Status status
= session
->GetTargetWindow(&web_view
);
213 if (status
.IsError())
217 new base::StringValue(WebViewIdToWindowHandle(web_view
->GetId())));
221 Status
ExecuteLaunchApp(
223 const base::DictionaryValue
& params
,
224 scoped_ptr
<base::Value
>* value
) {
226 if (!params
.GetString("id", &id
))
227 return Status(kUnknownError
, "'id' must be a string");
229 ChromeDesktopImpl
* desktop
= NULL
;
230 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
231 if (status
.IsError())
234 AutomationExtension
* extension
= NULL
;
235 status
= desktop
->GetAutomationExtension(&extension
);
236 if (status
.IsError())
239 return extension
->LaunchApp(id
);
244 const base::DictionaryValue
& params
,
245 scoped_ptr
<base::Value
>* value
) {
246 std::list
<std::string
> web_view_ids
;
247 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
248 if (status
.IsError())
250 bool is_last_web_view
= web_view_ids
.size() == 1u;
251 web_view_ids
.clear();
253 WebView
* web_view
= NULL
;
254 status
= session
->GetTargetWindow(&web_view
);
255 if (status
.IsError())
258 status
= session
->chrome
->CloseWebView(web_view
->GetId());
259 if (status
.IsError())
262 status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
263 if ((status
.code() == kChromeNotReachable
&& is_last_web_view
) ||
264 (status
.IsOk() && web_view_ids
.empty())) {
265 // If no window is open, close is the equivalent of calling "quit".
266 session
->quit
= true;
267 return session
->chrome
->Quit();
273 Status
ExecuteGetWindowHandles(
275 const base::DictionaryValue
& params
,
276 scoped_ptr
<base::Value
>* value
) {
277 std::list
<std::string
> web_view_ids
;
278 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
279 if (status
.IsError())
281 scoped_ptr
<base::ListValue
> window_ids(new base::ListValue());
282 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
283 it
!= web_view_ids
.end(); ++it
) {
284 window_ids
->AppendString(WebViewIdToWindowHandle(*it
));
286 value
->reset(window_ids
.release());
290 Status
ExecuteSwitchToWindow(
292 const base::DictionaryValue
& params
,
293 scoped_ptr
<base::Value
>* value
) {
295 if (!params
.GetString("name", &name
) || name
.empty())
296 return Status(kUnknownError
, "'name' must be a nonempty string");
298 std::list
<std::string
> web_view_ids
;
299 Status status
= session
->chrome
->GetWebViewIds(&web_view_ids
);
300 if (status
.IsError())
303 std::string web_view_id
;
305 if (WindowHandleToWebViewId(name
, &web_view_id
)) {
306 // Check if any web_view matches |web_view_id|.
307 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
308 it
!= web_view_ids
.end(); ++it
) {
309 if (*it
== web_view_id
) {
315 // Check if any of the tab window names match |name|.
316 const char* kGetWindowNameScript
= "function() { return window.name; }";
317 base::ListValue args
;
318 for (std::list
<std::string
>::const_iterator it
= web_view_ids
.begin();
319 it
!= web_view_ids
.end(); ++it
) {
320 scoped_ptr
<base::Value
> result
;
322 status
= session
->chrome
->GetWebViewById(*it
, &web_view
);
323 if (status
.IsError())
325 status
= web_view
->ConnectIfNecessary();
326 if (status
.IsError())
328 status
= web_view
->CallFunction(
329 std::string(), kGetWindowNameScript
, args
, &result
);
330 if (status
.IsError())
332 std::string window_name
;
333 if (!result
->GetAsString(&window_name
))
334 return Status(kUnknownError
, "failed to get window name");
335 if (window_name
== name
) {
344 return Status(kNoSuchWindow
);
346 if (session
->overridden_geoposition
) {
348 status
= session
->chrome
->GetWebViewById(web_view_id
, &web_view
);
349 if (status
.IsError())
351 status
= web_view
->ConnectIfNecessary();
352 if (status
.IsError())
354 status
= web_view
->OverrideGeolocation(*session
->overridden_geoposition
);
355 if (status
.IsError())
359 session
->window
= web_view_id
;
360 session
->SwitchToTopFrame();
361 session
->mouse_position
= WebPoint(0, 0);
365 Status
ExecuteSetTimeout(
367 const base::DictionaryValue
& params
,
368 scoped_ptr
<base::Value
>* value
) {
370 if (!params
.GetDouble("ms", &ms_double
))
371 return Status(kUnknownError
, "'ms' must be a double");
373 if (!params
.GetString("type", &type
))
374 return Status(kUnknownError
, "'type' must be a string");
376 base::TimeDelta timeout
=
377 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double
));
378 // TODO(frankf): implicit and script timeout should be cleared
379 // if negative timeout is specified.
380 if (type
== "implicit") {
381 session
->implicit_wait
= timeout
;
382 } else if (type
== "script") {
383 session
->script_timeout
= timeout
;
384 } else if (type
== "page load") {
385 session
->page_load_timeout
=
386 ((timeout
< base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
389 return Status(kUnknownError
, "unknown type of timeout:" + type
);
394 Status
ExecuteSetScriptTimeout(
396 const base::DictionaryValue
& params
,
397 scoped_ptr
<base::Value
>* value
) {
399 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
400 return Status(kUnknownError
, "'ms' must be a non-negative number");
401 session
->script_timeout
=
402 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
406 Status
ExecuteImplicitlyWait(
408 const base::DictionaryValue
& params
,
409 scoped_ptr
<base::Value
>* value
) {
411 if (!params
.GetDouble("ms", &ms
) || ms
< 0)
412 return Status(kUnknownError
, "'ms' must be a non-negative number");
413 session
->implicit_wait
=
414 base::TimeDelta::FromMilliseconds(static_cast<int>(ms
));
418 Status
ExecuteIsLoading(
420 const base::DictionaryValue
& params
,
421 scoped_ptr
<base::Value
>* value
) {
422 WebView
* web_view
= NULL
;
423 Status status
= session
->GetTargetWindow(&web_view
);
424 if (status
.IsError())
427 status
= web_view
->ConnectIfNecessary();
428 if (status
.IsError())
432 status
= web_view
->IsPendingNavigation(
433 session
->GetCurrentFrameId(), &is_pending
);
434 if (status
.IsError())
436 value
->reset(new base::FundamentalValue(is_pending
));
440 Status
ExecuteGetLocation(
442 const base::DictionaryValue
& params
,
443 scoped_ptr
<base::Value
>* value
) {
444 if (!session
->overridden_geoposition
) {
445 return Status(kUnknownError
,
446 "Location must be set before it can be retrieved");
448 base::DictionaryValue location
;
449 location
.SetDouble("latitude", session
->overridden_geoposition
->latitude
);
450 location
.SetDouble("longitude", session
->overridden_geoposition
->longitude
);
451 location
.SetDouble("accuracy", session
->overridden_geoposition
->accuracy
);
452 // Set a dummy altitude to make WebDriver clients happy.
453 // https://code.google.com/p/chromedriver/issues/detail?id=281
454 location
.SetDouble("altitude", 0);
455 value
->reset(location
.DeepCopy());
459 Status
ExecuteGetWindowPosition(
461 const base::DictionaryValue
& params
,
462 scoped_ptr
<base::Value
>* value
) {
463 ChromeDesktopImpl
* desktop
= NULL
;
464 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
465 if (status
.IsError())
468 AutomationExtension
* extension
= NULL
;
469 status
= desktop
->GetAutomationExtension(&extension
);
470 if (status
.IsError())
474 status
= extension
->GetWindowPosition(&x
, &y
);
475 if (status
.IsError())
478 base::DictionaryValue position
;
479 position
.SetInteger("x", x
);
480 position
.SetInteger("y", y
);
481 value
->reset(position
.DeepCopy());
485 Status
ExecuteSetWindowPosition(
487 const base::DictionaryValue
& params
,
488 scoped_ptr
<base::Value
>* value
) {
491 if (!params
.GetDouble("x", &x
) || !params
.GetDouble("y", &y
))
492 return Status(kUnknownError
, "missing or invalid 'x' or 'y'");
494 ChromeDesktopImpl
* desktop
= NULL
;
495 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
496 if (status
.IsError())
499 AutomationExtension
* extension
= NULL
;
500 status
= desktop
->GetAutomationExtension(&extension
);
501 if (status
.IsError())
504 return extension
->SetWindowPosition(static_cast<int>(x
), static_cast<int>(y
));
507 Status
ExecuteGetWindowSize(
509 const base::DictionaryValue
& params
,
510 scoped_ptr
<base::Value
>* value
) {
511 ChromeDesktopImpl
* desktop
= NULL
;
512 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
513 if (status
.IsError())
516 AutomationExtension
* extension
= NULL
;
517 status
= desktop
->GetAutomationExtension(&extension
);
518 if (status
.IsError())
522 status
= extension
->GetWindowSize(&width
, &height
);
523 if (status
.IsError())
526 base::DictionaryValue size
;
527 size
.SetInteger("width", width
);
528 size
.SetInteger("height", height
);
529 value
->reset(size
.DeepCopy());
533 Status
ExecuteSetWindowSize(
535 const base::DictionaryValue
& params
,
536 scoped_ptr
<base::Value
>* value
) {
539 if (!params
.GetDouble("width", &width
) ||
540 !params
.GetDouble("height", &height
))
541 return Status(kUnknownError
, "missing or invalid 'width' or 'height'");
543 ChromeDesktopImpl
* desktop
= NULL
;
544 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
545 if (status
.IsError())
548 AutomationExtension
* extension
= NULL
;
549 status
= desktop
->GetAutomationExtension(&extension
);
550 if (status
.IsError())
553 return extension
->SetWindowSize(
554 static_cast<int>(width
), static_cast<int>(height
));
557 Status
ExecuteMaximizeWindow(
559 const base::DictionaryValue
& params
,
560 scoped_ptr
<base::Value
>* value
) {
561 ChromeDesktopImpl
* desktop
= NULL
;
562 Status status
= session
->chrome
->GetAsDesktop(&desktop
);
563 if (status
.IsError())
566 AutomationExtension
* extension
= NULL
;
567 status
= desktop
->GetAutomationExtension(&extension
);
568 if (status
.IsError())
571 return extension
->MaximizeWindow();
574 Status
ExecuteGetAvailableLogTypes(
576 const base::DictionaryValue
& params
,
577 scoped_ptr
<base::Value
>* value
) {
578 scoped_ptr
<base::ListValue
> types(new base::ListValue());
579 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
580 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
583 types
->AppendString((*log
)->type());
585 *value
= types
.Pass();
589 Status
ExecuteGetLog(
591 const base::DictionaryValue
& params
,
592 scoped_ptr
<base::Value
>* value
) {
593 std::string log_type
;
594 if (!params
.GetString("type", &log_type
)) {
595 return Status(kUnknownError
, "missing or invalid 'type'");
597 std::vector
<WebDriverLog
*> logs
= session
->GetAllLogs();
598 for (std::vector
<WebDriverLog
*>::const_iterator log
= logs
.begin();
601 if (log_type
== (*log
)->type()) {
602 *value
= (*log
)->GetAndClearEntries();
606 return Status(kUnknownError
, "log type '" + log_type
+ "' not found");
609 Status
ExecuteUploadFile(
611 const base::DictionaryValue
& params
,
612 scoped_ptr
<base::Value
>* value
) {
613 std::string base64_zip_data
;
614 if (!params
.GetString("file", &base64_zip_data
))
615 return Status(kUnknownError
, "missing or invalid 'file'");
616 std::string zip_data
;
617 if (!Base64Decode(base64_zip_data
, &zip_data
))
618 return Status(kUnknownError
, "unable to decode 'file'");
620 if (!session
->temp_dir
.IsValid()) {
621 if (!session
->temp_dir
.CreateUniqueTempDir())
622 return Status(kUnknownError
, "unable to create temp dir");
624 base::FilePath upload_dir
;
625 if (!base::CreateTemporaryDirInDir(session
->temp_dir
.path(),
626 FILE_PATH_LITERAL("upload"),
628 return Status(kUnknownError
, "unable to create temp dir");
630 std::string error_msg
;
631 base::FilePath upload
;
632 Status status
= UnzipSoleFile(upload_dir
, zip_data
, &upload
);
633 if (status
.IsError())
634 return Status(kUnknownError
, "unable to unzip 'file'", status
);
636 value
->reset(new base::StringValue(upload
.value()));
640 Status
ExecuteIsAutoReporting(
642 const base::DictionaryValue
& params
,
643 scoped_ptr
<base::Value
>* value
) {
644 value
->reset(new base::FundamentalValue(session
->auto_reporting_enabled
));
648 Status
ExecuteSetAutoReporting(
650 const base::DictionaryValue
& params
,
651 scoped_ptr
<base::Value
>* value
) {
653 if (!params
.GetBoolean("enabled", &enabled
))
654 return Status(kUnknownError
, "missing parameter 'enabled'");
655 session
->auto_reporting_enabled
= enabled
;