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;
174 const base::DictionaryValue& params,
175 scoped_ptr<base::Value>* value) {
176 session->quit = true;
177 if (allow_detach && session->detach)
180 return session->chrome->Quit();
183 Status ExecuteGetSessionCapabilities(
185 const base::DictionaryValue& params,
186 scoped_ptr<base::Value>* value) {
187 value->reset(session->capabilities->DeepCopy());
191 Status ExecuteGetCurrentWindowHandle(
193 const base::DictionaryValue& params,
194 scoped_ptr<base::Value>* value) {
195 WebView* web_view = NULL;
196 Status status = session->GetTargetWindow(&web_view);
197 if (status.IsError())
200 value->reset(new StringValue(WebViewIdToWindowHandle(web_view->GetId())));
206 const base::DictionaryValue& params,
207 scoped_ptr<base::Value>* value) {
208 std::list<std::string> web_view_ids;
209 Status status = session->chrome->GetWebViewIds(&web_view_ids);
210 if (status.IsError())
212 bool is_last_web_view = web_view_ids.size() == 1u;
213 web_view_ids.clear();
215 WebView* web_view = NULL;
216 status = session->GetTargetWindow(&web_view);
217 if (status.IsError())
220 status = session->chrome->CloseWebView(web_view->GetId());
221 if (status.IsError())
224 status = session->chrome->GetWebViewIds(&web_view_ids);
225 if ((status.code() == kChromeNotReachable && is_last_web_view) ||
226 (status.IsOk() && web_view_ids.empty())) {
227 // If no window is open, close is the equivalent of calling "quit".
228 session->quit = true;
229 return session->chrome->Quit();
235 Status ExecuteGetWindowHandles(
237 const base::DictionaryValue& params,
238 scoped_ptr<base::Value>* value) {
239 std::list<std::string> web_view_ids;
240 Status status = session->chrome->GetWebViewIds(&web_view_ids);
241 if (status.IsError())
243 scoped_ptr<base::ListValue> window_ids(new base::ListValue());
244 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
245 it != web_view_ids.end(); ++it) {
246 window_ids->AppendString(WebViewIdToWindowHandle(*it));
248 value->reset(window_ids.release());
252 Status ExecuteSwitchToWindow(
254 const base::DictionaryValue& params,
255 scoped_ptr<base::Value>* value) {
257 if (!params.GetString("name", &name) || name.empty())
258 return Status(kUnknownError, "'name' must be a nonempty string");
260 std::list<std::string> web_view_ids;
261 Status status = session->chrome->GetWebViewIds(&web_view_ids);
262 if (status.IsError())
265 std::string web_view_id;
267 if (WindowHandleToWebViewId(name, &web_view_id)) {
268 // Check if any web_view matches |web_view_id|.
269 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
270 it != web_view_ids.end(); ++it) {
271 if (*it == web_view_id) {
277 // Check if any of the tab window names match |name|.
278 const char* kGetWindowNameScript = "function() { return window.name; }";
279 base::ListValue args;
280 for (std::list<std::string>::const_iterator it = web_view_ids.begin();
281 it != web_view_ids.end(); ++it) {
282 scoped_ptr<base::Value> result;
284 status = session->chrome->GetWebViewById(*it, &web_view);
285 if (status.IsError())
287 status = web_view->ConnectIfNecessary();
288 if (status.IsError())
290 status = web_view->CallFunction(
291 std::string(), kGetWindowNameScript, args, &result);
292 if (status.IsError())
294 std::string window_name;
295 if (!result->GetAsString(&window_name))
296 return Status(kUnknownError, "failed to get window name");
297 if (window_name == name) {
306 return Status(kNoSuchWindow);
308 if (session->overridden_geoposition) {
310 status = session->chrome->GetWebViewById(web_view_id, &web_view);
311 if (status.IsError())
313 status = web_view->ConnectIfNecessary();
314 if (status.IsError())
316 status = web_view->OverrideGeolocation(*session->overridden_geoposition);
317 if (status.IsError())
321 session->window = web_view_id;
322 session->SwitchToTopFrame();
323 session->mouse_position = WebPoint(0, 0);
327 Status ExecuteSetTimeout(
329 const base::DictionaryValue& params,
330 scoped_ptr<base::Value>* value) {
332 if (!params.GetDouble("ms", &ms_double))
333 return Status(kUnknownError, "'ms' must be a double");
335 if (!params.GetString("type", &type))
336 return Status(kUnknownError, "'type' must be a string");
338 base::TimeDelta timeout =
339 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double));
340 // TODO(frankf): implicit and script timeout should be cleared
341 // if negative timeout is specified.
342 if (type == "implicit") {
343 session->implicit_wait = timeout;
344 } else if (type == "script") {
345 session->script_timeout = timeout;
346 } else if (type == "page load") {
347 session->page_load_timeout =
348 ((timeout < base::TimeDelta()) ? Session::kDefaultPageLoadTimeout
351 return Status(kUnknownError, "unknown type of timeout:" + type);
356 Status ExecuteSetScriptTimeout(
358 const base::DictionaryValue& params,
359 scoped_ptr<base::Value>* value) {
361 if (!params.GetDouble("ms", &ms) || ms < 0)
362 return Status(kUnknownError, "'ms' must be a non-negative number");
363 session->script_timeout =
364 base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
368 Status ExecuteImplicitlyWait(
370 const base::DictionaryValue& params,
371 scoped_ptr<base::Value>* value) {
373 if (!params.GetDouble("ms", &ms) || ms < 0)
374 return Status(kUnknownError, "'ms' must be a non-negative number");
375 session->implicit_wait =
376 base::TimeDelta::FromMilliseconds(static_cast<int>(ms));
380 Status ExecuteIsLoading(
382 const base::DictionaryValue& params,
383 scoped_ptr<base::Value>* value) {
384 WebView* web_view = NULL;
385 Status status = session->GetTargetWindow(&web_view);
386 if (status.IsError())
389 status = web_view->ConnectIfNecessary();
390 if (status.IsError())
394 status = web_view->IsPendingNavigation(
395 session->GetCurrentFrameId(), &is_pending);
396 if (status.IsError())
398 value->reset(new base::FundamentalValue(is_pending));
402 Status ExecuteGetLocation(
404 const base::DictionaryValue& params,
405 scoped_ptr<base::Value>* value) {
406 if (!session->overridden_geoposition) {
407 return Status(kUnknownError,
408 "Location must be set before it can be retrieved");
410 base::DictionaryValue location;
411 location.SetDouble("latitude", session->overridden_geoposition->latitude);
412 location.SetDouble("longitude", session->overridden_geoposition->longitude);
413 location.SetDouble("accuracy", session->overridden_geoposition->accuracy);
414 // Set a dummy altitude to make WebDriver clients happy.
415 // https://code.google.com/p/chromedriver/issues/detail?id=281
416 location.SetDouble("altitude", 0);
417 value->reset(location.DeepCopy());
421 Status ExecuteGetWindowPosition(
423 const base::DictionaryValue& params,
424 scoped_ptr<base::Value>* value) {
425 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
429 "command only supported for desktop Chrome without debuggerAddress");
432 AutomationExtension* extension = NULL;
433 Status status = desktop->GetAutomationExtension(&extension);
434 if (status.IsError())
438 status = extension->GetWindowPosition(&x, &y);
439 if (status.IsError())
442 base::DictionaryValue position;
443 position.SetInteger("x", x);
444 position.SetInteger("y", y);
445 value->reset(position.DeepCopy());
449 Status ExecuteSetWindowPosition(
451 const base::DictionaryValue& params,
452 scoped_ptr<base::Value>* value) {
454 if (!params.GetDouble("x", &x) || !params.GetDouble("y", &y))
455 return Status(kUnknownError, "missing or invalid 'x' or 'y'");
457 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
461 "command only supported for desktop Chrome without debuggerAddress");
464 AutomationExtension* extension = NULL;
465 Status status = desktop->GetAutomationExtension(&extension);
466 if (status.IsError())
469 return extension->SetWindowPosition(static_cast<int>(x), static_cast<int>(y));
472 Status ExecuteGetWindowSize(
474 const base::DictionaryValue& params,
475 scoped_ptr<base::Value>* value) {
476 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
480 "command only supported for desktop Chrome without debuggerAddress");
483 AutomationExtension* extension = NULL;
484 Status status = desktop->GetAutomationExtension(&extension);
485 if (status.IsError())
489 status = extension->GetWindowSize(&width, &height);
490 if (status.IsError())
493 base::DictionaryValue size;
494 size.SetInteger("width", width);
495 size.SetInteger("height", height);
496 value->reset(size.DeepCopy());
500 Status ExecuteSetWindowSize(
502 const base::DictionaryValue& params,
503 scoped_ptr<base::Value>* value) {
504 double width, height;
505 if (!params.GetDouble("width", &width) ||
506 !params.GetDouble("height", &height))
507 return Status(kUnknownError, "missing or invalid 'width' or 'height'");
509 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
513 "command only supported for desktop Chrome without debuggerAddress");
516 AutomationExtension* extension = NULL;
517 Status status = desktop->GetAutomationExtension(&extension);
518 if (status.IsError())
521 return extension->SetWindowSize(
522 static_cast<int>(width), static_cast<int>(height));
525 Status ExecuteMaximizeWindow(
527 const base::DictionaryValue& params,
528 scoped_ptr<base::Value>* value) {
529 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop();
533 "command only supported for desktop Chrome without debuggerAddress");
536 AutomationExtension* extension = NULL;
537 Status status = desktop->GetAutomationExtension(&extension);
538 if (status.IsError())
541 return extension->MaximizeWindow();
544 Status ExecuteGetAvailableLogTypes(
546 const base::DictionaryValue& params,
547 scoped_ptr<base::Value>* value) {
548 scoped_ptr<base::ListValue> types(new base::ListValue());
549 std::vector<WebDriverLog*> logs = session->GetAllLogs();
550 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
553 types->AppendString((*log)->type());
555 *value = types.Pass();
559 Status ExecuteGetLog(
561 const base::DictionaryValue& params,
562 scoped_ptr<base::Value>* value) {
563 std::string log_type;
564 if (!params.GetString("type", &log_type)) {
565 return Status(kUnknownError, "missing or invalid 'type'");
567 std::vector<WebDriverLog*> logs = session->GetAllLogs();
568 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin();
571 if (log_type == (*log)->type()) {
572 *value = (*log)->GetAndClearEntries();
576 return Status(kUnknownError, "log type '" + log_type + "' not found");
579 Status ExecuteUploadFile(
581 const base::DictionaryValue& params,
582 scoped_ptr<base::Value>* value) {
583 std::string base64_zip_data;
584 if (!params.GetString("file", &base64_zip_data))
585 return Status(kUnknownError, "missing or invalid 'file'");
586 std::string zip_data;
587 if (!Base64Decode(base64_zip_data, &zip_data))
588 return Status(kUnknownError, "unable to decode 'file'");
590 if (!session->temp_dir.IsValid()) {
591 if (!session->temp_dir.CreateUniqueTempDir())
592 return Status(kUnknownError, "unable to create temp dir");
594 base::FilePath upload_dir;
595 if (!file_util::CreateTemporaryDirInDir(
596 session->temp_dir.path(), FILE_PATH_LITERAL("upload"), &upload_dir)) {
597 return Status(kUnknownError, "unable to create temp dir");
599 std::string error_msg;
600 base::FilePath upload;
601 Status status = UnzipSoleFile(upload_dir, zip_data, &upload);
602 if (status.IsError())
603 return Status(kUnknownError, "unable to unzip 'file'", status);
605 value->reset(new base::StringValue(upload.value()));