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. 4 5 #include "chrome/test/chromedriver/session_commands.h" 6 7 #include <list> 8 9 #include "base/bind.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/version.h" 29 #include "chrome/test/chromedriver/chrome/web_view.h" 30 #include "chrome/test/chromedriver/chrome_launcher.h" 31 #include "chrome/test/chromedriver/logging.h" 32 #include "chrome/test/chromedriver/net/url_request_context_getter.h" 33 #include "chrome/test/chromedriver/session.h" 34 #include "chrome/test/chromedriver/util.h" 35 #include "chrome/test/chromedriver/version.h" 36 37 namespace { 38 39 const char kWindowHandlePrefix[] = "CDwindow-"; 40 41 std::string WebViewIdToWindowHandle(const std::string& web_view_id) { 42 return kWindowHandlePrefix + web_view_id; 43 } 44 45 bool WindowHandleToWebViewId(const std::string& window_handle, 46 std::string* web_view_id) { 47 if (window_handle.find(kWindowHandlePrefix) != 0u) 48 return false; 49 *web_view_id = window_handle.substr( 50 std::string(kWindowHandlePrefix).length()); 51 return true; 52 } 53 54 } // namespace 55 56 InitSessionParams::InitSessionParams( 57 scoped_refptr<URLRequestContextGetter> context_getter, 58 const SyncWebSocketFactory& socket_factory, 59 DeviceManager* device_manager, 60 PortServer* port_server, 61 PortManager* port_manager) 62 : context_getter(context_getter), 63 socket_factory(socket_factory), 64 device_manager(device_manager), 65 port_server(port_server), 66 port_manager(port_manager) {} 67 68 InitSessionParams::~InitSessionParams() {} 69 70 namespace { 71 72 scoped_ptr<base::DictionaryValue> CreateCapabilities(Chrome* chrome) { 73 scoped_ptr<base::DictionaryValue> caps(new base::DictionaryValue()); 74 caps->SetString("browserName", "chrome"); 75 caps->SetString("version", chrome->GetBrowserInfo()->browser_version); 76 caps->SetString("chrome.chromedriverVersion", kChromeDriverVersion); 77 caps->SetString("platform", chrome->GetOperatingSystemName()); 78 caps->SetBoolean("javascriptEnabled", true); 79 caps->SetBoolean("takesScreenshot", true); 80 caps->SetBoolean("takesHeapSnapshot", true); 81 caps->SetBoolean("handlesAlerts", true); 82 caps->SetBoolean("databaseEnabled", false); 83 caps->SetBoolean("locationContextEnabled", true); 84 caps->SetBoolean("mobileEmulationEnabled", 85 chrome->IsMobileEmulationEnabled()); 86 caps->SetBoolean("applicationCacheEnabled", false); 87 caps->SetBoolean("browserConnectionEnabled", false); 88 caps->SetBoolean("cssSelectorsEnabled", true); 89 caps->SetBoolean("webStorageEnabled", true); 90 caps->SetBoolean("rotatable", false); 91 caps->SetBoolean("acceptSslCerts", true); 92 caps->SetBoolean("nativeEvents", true); 93 scoped_ptr<base::DictionaryValue> chrome_caps(new base::DictionaryValue()); 94 if (chrome->GetAsDesktop()) { 95 chrome_caps->SetString( 96 "userDataDir", 97 chrome->GetAsDesktop()->command().GetSwitchValueNative( 98 "user-data-dir")); 99 } 100 caps->Set("chrome", chrome_caps.release()); 101 return caps.Pass(); 102 } 103 104 Status InitSessionHelper( 105 const InitSessionParams& bound_params, 106 Session* session, 107 const base::DictionaryValue& params, 108 scoped_ptr<base::Value>* value) { 109 session->driver_log.reset( 110 new WebDriverLog(WebDriverLog::kDriverType, Log::kAll)); 111 const base::DictionaryValue* desired_caps; 112 if (!params.GetDictionary("desiredCapabilities", &desired_caps)) 113 return Status(kUnknownError, "cannot find dict 'desiredCapabilities'"); 114 115 Capabilities capabilities; 116 Status status = capabilities.Parse(*desired_caps); 117 if (status.IsError()) 118 return status; 119 120 Log::Level driver_level = Log::kWarning; 121 if (capabilities.logging_prefs.count(WebDriverLog::kDriverType)) 122 driver_level = capabilities.logging_prefs[WebDriverLog::kDriverType]; 123 session->driver_log->set_min_level(driver_level); 124 125 // Create Log's and DevToolsEventListener's for ones that are DevTools-based. 126 // Session will own the Log's, Chrome will own the listeners. 127 ScopedVector<DevToolsEventListener> devtools_event_listeners; 128 status = CreateLogs(capabilities, 129 &session->devtools_logs, 130 &devtools_event_listeners); 131 if (status.IsError()) 132 return status; 133 134 status = LaunchChrome(bound_params.context_getter.get(), 135 bound_params.socket_factory, 136 bound_params.device_manager, 137 bound_params.port_server, 138 bound_params.port_manager, 139 capabilities, 140 devtools_event_listeners, 141 &session->chrome); 142 if (status.IsError()) 143 return status; 144 145 std::list<std::string> web_view_ids; 146 status = session->chrome->GetWebViewIds(&web_view_ids); 147 if (status.IsError() || web_view_ids.empty()) { 148 return status.IsError() ? status : 149 Status(kUnknownError, "unable to discover open window in chrome"); 150 } 151 152 session->window = web_view_ids.front(); 153 session->detach = capabilities.detach; 154 session->force_devtools_screenshot = capabilities.force_devtools_screenshot; 155 session->capabilities = CreateCapabilities(session->chrome.get()); 156 value->reset(session->capabilities->DeepCopy()); 157 return Status(kOk); 158 } 159 160 } // namespace 161 162 Status ExecuteInitSession( 163 const InitSessionParams& bound_params, 164 Session* session, 165 const base::DictionaryValue& params, 166 scoped_ptr<base::Value>* value) { 167 Status status = InitSessionHelper(bound_params, session, params, value); 168 if (status.IsError()) { 169 session->quit = true; 170 if (session->chrome != NULL) 171 session->chrome->Quit(); 172 } 173 return status; 174 } 175 176 Status ExecuteQuit( 177 bool allow_detach, 178 Session* session, 179 const base::DictionaryValue& params, 180 scoped_ptr<base::Value>* value) { 181 session->quit = true; 182 if (allow_detach && session->detach) 183 return Status(kOk); 184 else 185 return session->chrome->Quit(); 186 } 187 188 Status ExecuteGetSessionCapabilities( 189 Session* session, 190 const base::DictionaryValue& params, 191 scoped_ptr<base::Value>* value) { 192 value->reset(session->capabilities->DeepCopy()); 193 return Status(kOk); 194 } 195 196 Status ExecuteGetCurrentWindowHandle( 197 Session* session, 198 const base::DictionaryValue& params, 199 scoped_ptr<base::Value>* value) { 200 WebView* web_view = NULL; 201 Status status = session->GetTargetWindow(&web_view); 202 if (status.IsError()) 203 return status; 204 205 value->reset( 206 new base::StringValue(WebViewIdToWindowHandle(web_view->GetId()))); 207 return Status(kOk); 208 } 209 210 Status ExecuteLaunchApp( 211 Session* session, 212 const base::DictionaryValue& params, 213 scoped_ptr<base::Value>* value) { 214 std::string id; 215 if (!params.GetString("id", &id)) 216 return Status(kUnknownError, "'id' must be a string"); 217 218 if (!session->chrome->GetAsDesktop()) 219 return Status(kUnknownError, 220 "apps can only be launched on desktop platforms"); 221 222 AutomationExtension* extension = NULL; 223 Status status = 224 session->chrome->GetAsDesktop()->GetAutomationExtension(&extension); 225 if (status.IsError()) 226 return status; 227 228 return extension->LaunchApp(id); 229 } 230 231 Status ExecuteClose( 232 Session* session, 233 const base::DictionaryValue& params, 234 scoped_ptr<base::Value>* value) { 235 std::list<std::string> web_view_ids; 236 Status status = session->chrome->GetWebViewIds(&web_view_ids); 237 if (status.IsError()) 238 return status; 239 bool is_last_web_view = web_view_ids.size() == 1u; 240 web_view_ids.clear(); 241 242 WebView* web_view = NULL; 243 status = session->GetTargetWindow(&web_view); 244 if (status.IsError()) 245 return status; 246 247 status = session->chrome->CloseWebView(web_view->GetId()); 248 if (status.IsError()) 249 return status; 250 251 status = session->chrome->GetWebViewIds(&web_view_ids); 252 if ((status.code() == kChromeNotReachable && is_last_web_view) || 253 (status.IsOk() && web_view_ids.empty())) { 254 // If no window is open, close is the equivalent of calling "quit". 255 session->quit = true; 256 return session->chrome->Quit(); 257 } 258 259 return status; 260 } 261 262 Status ExecuteGetWindowHandles( 263 Session* session, 264 const base::DictionaryValue& params, 265 scoped_ptr<base::Value>* value) { 266 std::list<std::string> web_view_ids; 267 Status status = session->chrome->GetWebViewIds(&web_view_ids); 268 if (status.IsError()) 269 return status; 270 scoped_ptr<base::ListValue> window_ids(new base::ListValue()); 271 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 272 it != web_view_ids.end(); ++it) { 273 window_ids->AppendString(WebViewIdToWindowHandle(*it)); 274 } 275 value->reset(window_ids.release()); 276 return Status(kOk); 277 } 278 279 Status ExecuteSwitchToWindow( 280 Session* session, 281 const base::DictionaryValue& params, 282 scoped_ptr<base::Value>* value) { 283 std::string name; 284 if (!params.GetString("name", &name) || name.empty()) 285 return Status(kUnknownError, "'name' must be a nonempty string"); 286 287 std::list<std::string> web_view_ids; 288 Status status = session->chrome->GetWebViewIds(&web_view_ids); 289 if (status.IsError()) 290 return status; 291 292 std::string web_view_id; 293 bool found = false; 294 if (WindowHandleToWebViewId(name, &web_view_id)) { 295 // Check if any web_view matches |web_view_id|. 296 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 297 it != web_view_ids.end(); ++it) { 298 if (*it == web_view_id) { 299 found = true; 300 break; 301 } 302 } 303 } else { 304 // Check if any of the tab window names match |name|. 305 const char* kGetWindowNameScript = "function() { return window.name; }"; 306 base::ListValue args; 307 for (std::list<std::string>::const_iterator it = web_view_ids.begin(); 308 it != web_view_ids.end(); ++it) { 309 scoped_ptr<base::Value> result; 310 WebView* web_view; 311 status = session->chrome->GetWebViewById(*it, &web_view); 312 if (status.IsError()) 313 return status; 314 status = web_view->ConnectIfNecessary(); 315 if (status.IsError()) 316 return status; 317 status = web_view->CallFunction( 318 std::string(), kGetWindowNameScript, args, &result); 319 if (status.IsError()) 320 return status; 321 std::string window_name; 322 if (!result->GetAsString(&window_name)) 323 return Status(kUnknownError, "failed to get window name"); 324 if (window_name == name) { 325 web_view_id = *it; 326 found = true; 327 break; 328 } 329 } 330 } 331 332 if (!found) 333 return Status(kNoSuchWindow); 334 335 if (session->overridden_geoposition) { 336 WebView* web_view; 337 status = session->chrome->GetWebViewById(web_view_id, &web_view); 338 if (status.IsError()) 339 return status; 340 status = web_view->ConnectIfNecessary(); 341 if (status.IsError()) 342 return status; 343 status = web_view->OverrideGeolocation(*session->overridden_geoposition); 344 if (status.IsError()) 345 return status; 346 } 347 348 session->window = web_view_id; 349 session->SwitchToTopFrame(); 350 session->mouse_position = WebPoint(0, 0); 351 return Status(kOk); 352 } 353 354 Status ExecuteSetTimeout( 355 Session* session, 356 const base::DictionaryValue& params, 357 scoped_ptr<base::Value>* value) { 358 double ms_double; 359 if (!params.GetDouble("ms", &ms_double)) 360 return Status(kUnknownError, "'ms' must be a double"); 361 std::string type; 362 if (!params.GetString("type", &type)) 363 return Status(kUnknownError, "'type' must be a string"); 364 365 base::TimeDelta timeout = 366 base::TimeDelta::FromMilliseconds(static_cast<int>(ms_double)); 367 // TODO(frankf): implicit and script timeout should be cleared 368 // if negative timeout is specified. 369 if (type == "implicit") { 370 session->implicit_wait = timeout; 371 } else if (type == "script") { 372 session->script_timeout = timeout; 373 } else if (type == "page load") { 374 session->page_load_timeout = 375 ((timeout < base::TimeDelta()) ? Session::kDefaultPageLoadTimeout 376 : timeout); 377 } else { 378 return Status(kUnknownError, "unknown type of timeout:" + type); 379 } 380 return Status(kOk); 381 } 382 383 Status ExecuteSetScriptTimeout( 384 Session* session, 385 const base::DictionaryValue& params, 386 scoped_ptr<base::Value>* value) { 387 double ms; 388 if (!params.GetDouble("ms", &ms) || ms < 0) 389 return Status(kUnknownError, "'ms' must be a non-negative number"); 390 session->script_timeout = 391 base::TimeDelta::FromMilliseconds(static_cast<int>(ms)); 392 return Status(kOk); 393 } 394 395 Status ExecuteImplicitlyWait( 396 Session* session, 397 const base::DictionaryValue& params, 398 scoped_ptr<base::Value>* value) { 399 double ms; 400 if (!params.GetDouble("ms", &ms) || ms < 0) 401 return Status(kUnknownError, "'ms' must be a non-negative number"); 402 session->implicit_wait = 403 base::TimeDelta::FromMilliseconds(static_cast<int>(ms)); 404 return Status(kOk); 405 } 406 407 Status ExecuteIsLoading( 408 Session* session, 409 const base::DictionaryValue& params, 410 scoped_ptr<base::Value>* value) { 411 WebView* web_view = NULL; 412 Status status = session->GetTargetWindow(&web_view); 413 if (status.IsError()) 414 return status; 415 416 status = web_view->ConnectIfNecessary(); 417 if (status.IsError()) 418 return status; 419 420 bool is_pending; 421 status = web_view->IsPendingNavigation( 422 session->GetCurrentFrameId(), &is_pending); 423 if (status.IsError()) 424 return status; 425 value->reset(new base::FundamentalValue(is_pending)); 426 return Status(kOk); 427 } 428 429 Status ExecuteGetLocation( 430 Session* session, 431 const base::DictionaryValue& params, 432 scoped_ptr<base::Value>* value) { 433 if (!session->overridden_geoposition) { 434 return Status(kUnknownError, 435 "Location must be set before it can be retrieved"); 436 } 437 base::DictionaryValue location; 438 location.SetDouble("latitude", session->overridden_geoposition->latitude); 439 location.SetDouble("longitude", session->overridden_geoposition->longitude); 440 location.SetDouble("accuracy", session->overridden_geoposition->accuracy); 441 // Set a dummy altitude to make WebDriver clients happy. 442 // https://code.google.com/p/chromedriver/issues/detail?id=281 443 location.SetDouble("altitude", 0); 444 value->reset(location.DeepCopy()); 445 return Status(kOk); 446 } 447 448 Status ExecuteGetWindowPosition( 449 Session* session, 450 const base::DictionaryValue& params, 451 scoped_ptr<base::Value>* value) { 452 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 453 if (!desktop) { 454 return Status( 455 kUnknownError, 456 "command only supported for desktop Chrome without debuggerAddress"); 457 } 458 459 AutomationExtension* extension = NULL; 460 Status status = desktop->GetAutomationExtension(&extension); 461 if (status.IsError()) 462 return status; 463 464 int x, y; 465 status = extension->GetWindowPosition(&x, &y); 466 if (status.IsError()) 467 return status; 468 469 base::DictionaryValue position; 470 position.SetInteger("x", x); 471 position.SetInteger("y", y); 472 value->reset(position.DeepCopy()); 473 return Status(kOk); 474 } 475 476 Status ExecuteSetWindowPosition( 477 Session* session, 478 const base::DictionaryValue& params, 479 scoped_ptr<base::Value>* value) { 480 double x, y; 481 if (!params.GetDouble("x", &x) || !params.GetDouble("y", &y)) 482 return Status(kUnknownError, "missing or invalid 'x' or 'y'"); 483 484 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 485 if (!desktop) { 486 return Status( 487 kUnknownError, 488 "command only supported for desktop Chrome without debuggerAddress"); 489 } 490 491 AutomationExtension* extension = NULL; 492 Status status = desktop->GetAutomationExtension(&extension); 493 if (status.IsError()) 494 return status; 495 496 return extension->SetWindowPosition(static_cast<int>(x), static_cast<int>(y)); 497 } 498 499 Status ExecuteGetWindowSize( 500 Session* session, 501 const base::DictionaryValue& params, 502 scoped_ptr<base::Value>* value) { 503 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 504 if (!desktop) { 505 return Status( 506 kUnknownError, 507 "command only supported for desktop Chrome without debuggerAddress"); 508 } 509 510 AutomationExtension* extension = NULL; 511 Status status = desktop->GetAutomationExtension(&extension); 512 if (status.IsError()) 513 return status; 514 515 int width, height; 516 status = extension->GetWindowSize(&width, &height); 517 if (status.IsError()) 518 return status; 519 520 base::DictionaryValue size; 521 size.SetInteger("width", width); 522 size.SetInteger("height", height); 523 value->reset(size.DeepCopy()); 524 return Status(kOk); 525 } 526 527 Status ExecuteSetWindowSize( 528 Session* session, 529 const base::DictionaryValue& params, 530 scoped_ptr<base::Value>* value) { 531 double width, height; 532 if (!params.GetDouble("width", &width) || 533 !params.GetDouble("height", &height)) 534 return Status(kUnknownError, "missing or invalid 'width' or 'height'"); 535 536 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 537 if (!desktop) { 538 return Status( 539 kUnknownError, 540 "command only supported for desktop Chrome without debuggerAddress"); 541 } 542 543 AutomationExtension* extension = NULL; 544 Status status = desktop->GetAutomationExtension(&extension); 545 if (status.IsError()) 546 return status; 547 548 return extension->SetWindowSize( 549 static_cast<int>(width), static_cast<int>(height)); 550 } 551 552 Status ExecuteMaximizeWindow( 553 Session* session, 554 const base::DictionaryValue& params, 555 scoped_ptr<base::Value>* value) { 556 ChromeDesktopImpl* desktop = session->chrome->GetAsDesktop(); 557 if (!desktop) { 558 return Status( 559 kUnknownError, 560 "command only supported for desktop Chrome without debuggerAddress"); 561 } 562 563 AutomationExtension* extension = NULL; 564 Status status = desktop->GetAutomationExtension(&extension); 565 if (status.IsError()) 566 return status; 567 568 return extension->MaximizeWindow(); 569 } 570 571 Status ExecuteGetAvailableLogTypes( 572 Session* session, 573 const base::DictionaryValue& params, 574 scoped_ptr<base::Value>* value) { 575 scoped_ptr<base::ListValue> types(new base::ListValue()); 576 std::vector<WebDriverLog*> logs = session->GetAllLogs(); 577 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin(); 578 log != logs.end(); 579 ++log) { 580 types->AppendString((*log)->type()); 581 } 582 *value = types.Pass(); 583 return Status(kOk); 584 } 585 586 Status ExecuteGetLog( 587 Session* session, 588 const base::DictionaryValue& params, 589 scoped_ptr<base::Value>* value) { 590 std::string log_type; 591 if (!params.GetString("type", &log_type)) { 592 return Status(kUnknownError, "missing or invalid 'type'"); 593 } 594 std::vector<WebDriverLog*> logs = session->GetAllLogs(); 595 for (std::vector<WebDriverLog*>::const_iterator log = logs.begin(); 596 log != logs.end(); 597 ++log) { 598 if (log_type == (*log)->type()) { 599 *value = (*log)->GetAndClearEntries(); 600 return Status(kOk); 601 } 602 } 603 return Status(kUnknownError, "log type '" + log_type + "' not found"); 604 } 605 606 Status ExecuteUploadFile( 607 Session* session, 608 const base::DictionaryValue& params, 609 scoped_ptr<base::Value>* value) { 610 std::string base64_zip_data; 611 if (!params.GetString("file", &base64_zip_data)) 612 return Status(kUnknownError, "missing or invalid 'file'"); 613 std::string zip_data; 614 if (!Base64Decode(base64_zip_data, &zip_data)) 615 return Status(kUnknownError, "unable to decode 'file'"); 616 617 if (!session->temp_dir.IsValid()) { 618 if (!session->temp_dir.CreateUniqueTempDir()) 619 return Status(kUnknownError, "unable to create temp dir"); 620 } 621 base::FilePath upload_dir; 622 if (!base::CreateTemporaryDirInDir(session->temp_dir.path(), 623 FILE_PATH_LITERAL("upload"), 624 &upload_dir)) { 625 return Status(kUnknownError, "unable to create temp dir"); 626 } 627 std::string error_msg; 628 base::FilePath upload; 629 Status status = UnzipSoleFile(upload_dir, zip_data, &upload); 630 if (status.IsError()) 631 return Status(kUnknownError, "unable to unzip 'file'", status); 632 633 value->reset(new base::StringValue(upload.value())); 634 return Status(kOk); 635 } 636 637 Status ExecuteIsAutoReporting( 638 Session* session, 639 const base::DictionaryValue& params, 640 scoped_ptr<base::Value>* value) { 641 value->reset(new base::FundamentalValue(session->auto_reporting_enabled)); 642 return Status(kOk); 643 } 644 645 Status ExecuteSetAutoReporting( 646 Session* session, 647 const base::DictionaryValue& params, 648 scoped_ptr<base::Value>* value) { 649 bool enabled; 650 if (!params.GetBoolean("enabled", &enabled)) 651 return Status(kUnknownError, "missing parameter 'enabled'"); 652 session->auto_reporting_enabled = enabled; 653 return Status(kOk); 654 } 655