1 // Copyright 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/remoting/remote_desktop_browsertest.h" 6 7 #include "base/command_line.h" 8 #include "base/files/file_util.h" 9 #include "base/json/json_reader.h" 10 #include "base/path_service.h" 11 #include "chrome/browser/extensions/extension_service.h" 12 #include "chrome/browser/extensions/unpacked_installer.h" 13 #include "chrome/browser/ui/extensions/application_launch.h" 14 #include "chrome/common/chrome_switches.h" 15 #include "chrome/test/remoting/key_code_conv.h" 16 #include "chrome/test/remoting/page_load_notification_observer.h" 17 #include "chrome/test/remoting/waiter.h" 18 #include "content/public/browser/native_web_keyboard_event.h" 19 #include "content/public/browser/render_view_host.h" 20 #include "content/public/test/test_utils.h" 21 #include "extensions/common/constants.h" 22 #include "extensions/common/extension.h" 23 #include "extensions/common/extension_set.h" 24 #include "extensions/common/switches.h" 25 #include "ui/base/window_open_disposition.h" 26 27 namespace remoting { 28 29 RemoteDesktopBrowserTest::RemoteDesktopBrowserTest() 30 : extension_(NULL) { 31 } 32 33 RemoteDesktopBrowserTest::~RemoteDesktopBrowserTest() {} 34 35 void RemoteDesktopBrowserTest::SetUp() { 36 ParseCommandLine(); 37 PlatformAppBrowserTest::SetUp(); 38 } 39 40 void RemoteDesktopBrowserTest::SetUpOnMainThread() { 41 PlatformAppBrowserTest::SetUpOnMainThread(); 42 43 // Pushing the initial WebContents instance onto the stack before 44 // RunTestOnMainThread() and after |InProcessBrowserTest::browser_| 45 // is initialized in InProcessBrowserTest::RunTestOnMainThreadLoop() 46 web_contents_stack_.push_back( 47 browser()->tab_strip_model()->GetActiveWebContents()); 48 } 49 50 // Change behavior of the default host resolver to avoid DNS lookup errors, 51 // so we can make network calls. 52 void RemoteDesktopBrowserTest::SetUpInProcessBrowserTestFixture() { 53 // The resolver object lifetime is managed by sync_test_setup, not here. 54 EnableDNSLookupForThisTest( 55 new net::RuleBasedHostResolverProc(host_resolver())); 56 } 57 58 void RemoteDesktopBrowserTest::TearDownInProcessBrowserTestFixture() { 59 DisableDNSLookupForThisTest(); 60 } 61 62 void RemoteDesktopBrowserTest::VerifyInternetAccess() { 63 ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( 64 browser(), GURL("http://www.google.com"), 1); 65 66 EXPECT_EQ(GetCurrentURL().host(), "www.google.com"); 67 } 68 69 void RemoteDesktopBrowserTest::OpenClientBrowserPage() { 70 // Open the client browser page in a new tab 71 ui_test_utils::NavigateToURLWithDisposition( 72 browser(), 73 GURL(http_server() + "/clientpage.html"), 74 NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB); 75 76 // Save this web content for later reference 77 client_web_content_ = browser()->tab_strip_model()->GetActiveWebContents(); 78 79 // Go back to the previous tab that has chromoting opened 80 browser()->tab_strip_model()->SelectPreviousTab(); 81 } 82 83 bool RemoteDesktopBrowserTest::HtmlElementVisible(const std::string& name) { 84 _ASSERT_TRUE(HtmlElementExists(name)); 85 86 ExecuteScript( 87 "function isElementVisible(name) {" 88 " var element = document.getElementById(name);" 89 " /* The existence of the element has already been ASSERTed. */" 90 " do {" 91 " if (element.hidden) {" 92 " return false;" 93 " }" 94 " element = element.parentNode;" 95 " } while (element != null);" 96 " return true;" 97 "};"); 98 99 return ExecuteScriptAndExtractBool( 100 "isElementVisible(\"" + name + "\")"); 101 } 102 103 void RemoteDesktopBrowserTest::InstallChromotingAppCrx() { 104 ASSERT_TRUE(!is_unpacked()); 105 106 base::FilePath install_dir(WebAppCrxPath()); 107 scoped_refptr<const Extension> extension(InstallExtensionWithUIAutoConfirm( 108 install_dir, 1, browser())); 109 110 EXPECT_FALSE(extension.get() == NULL); 111 112 extension_ = extension.get(); 113 } 114 115 void RemoteDesktopBrowserTest::InstallChromotingAppUnpacked() { 116 ASSERT_TRUE(is_unpacked()); 117 118 scoped_refptr<extensions::UnpackedInstaller> installer = 119 extensions::UnpackedInstaller::Create(extension_service()); 120 installer->set_prompt_for_plugins(false); 121 122 content::WindowedNotificationObserver observer( 123 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED, 124 content::NotificationService::AllSources()); 125 126 installer->Load(webapp_unpacked_); 127 128 observer.Wait(); 129 } 130 131 void RemoteDesktopBrowserTest::UninstallChromotingApp() { 132 UninstallExtension(ChromotingID()); 133 extension_ = NULL; 134 } 135 136 void RemoteDesktopBrowserTest::VerifyChromotingLoaded(bool expected) { 137 const extensions::ExtensionSet* extensions = 138 extension_service()->extensions(); 139 scoped_refptr<const extensions::Extension> extension; 140 bool installed = false; 141 142 for (extensions::ExtensionSet::const_iterator iter = extensions->begin(); 143 iter != extensions->end(); ++iter) { 144 extension = *iter; 145 // Is there a better way to recognize the chromoting extension 146 // than name comparison? 147 if (extension->name() == extension_name_) { 148 installed = true; 149 break; 150 } 151 } 152 153 if (installed) { 154 if (extension_) 155 EXPECT_EQ(extension.get(), extension_); 156 else 157 extension_ = extension.get(); 158 159 // Either a V1 (TYPE_LEGACY_PACKAGED_APP) or a V2 (TYPE_PLATFORM_APP ) app. 160 extensions::Manifest::Type type = extension->GetType(); 161 EXPECT_TRUE(type == extensions::Manifest::TYPE_PLATFORM_APP || 162 type == extensions::Manifest::TYPE_LEGACY_PACKAGED_APP); 163 164 EXPECT_TRUE(extension->ShouldDisplayInAppLauncher()); 165 } 166 167 ASSERT_EQ(installed, expected); 168 } 169 170 void RemoteDesktopBrowserTest::LaunchChromotingApp() { 171 ASSERT_TRUE(extension_); 172 173 GURL chromoting_main = Chromoting_Main_URL(); 174 // We cannot simply wait for any page load because the first page 175 // loaded could be the generated background page. We need to wait 176 // till the chromoting main page is loaded. 177 PageLoadNotificationObserver observer(chromoting_main); 178 179 OpenApplication(AppLaunchParams( 180 browser()->profile(), 181 extension_, 182 is_platform_app() ? extensions::LAUNCH_CONTAINER_NONE : 183 extensions::LAUNCH_CONTAINER_TAB, 184 is_platform_app() ? NEW_WINDOW : CURRENT_TAB)); 185 186 observer.Wait(); 187 188 189 // The active WebContents instance should be the source of the LOAD_STOP 190 // notification. 191 content::NavigationController* controller = 192 content::Source<content::NavigationController>(observer.source()).ptr(); 193 194 content::WebContents* web_contents = controller->GetWebContents(); 195 if (web_contents != active_web_contents()) 196 web_contents_stack_.push_back(web_contents); 197 198 app_web_content_ = web_contents; 199 200 if (is_platform_app()) { 201 EXPECT_EQ(GetFirstAppWindowWebContents(), active_web_contents()); 202 } else { 203 // For apps v1 only, the DOMOperationObserver is not ready at the LOAD_STOP 204 // event. A half second wait is necessary for the subsequent javascript 205 // injection to work. 206 // TODO(weitaosu): Find out whether there is a more appropriate notification 207 // to wait for so we can get rid of this wait. 208 ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(5)).Wait()); 209 } 210 211 EXPECT_EQ(Chromoting_Main_URL(), GetCurrentURL()); 212 } 213 214 void RemoteDesktopBrowserTest::Authorize() { 215 // The chromoting extension should be installed. 216 ASSERT_TRUE(extension_); 217 218 // The chromoting main page should be loaded in the current tab 219 // and isAuthenticated() should be false (auth dialog visible). 220 ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL()); 221 ASSERT_FALSE(IsAuthenticated()); 222 223 // The second observer monitors the loading of the Google login page. 224 // Unfortunately we cannot specify a source in this observer because 225 // we can't get a handle of the new window until the first observer 226 // has finished waiting. But we will assert that the source of the 227 // load stop event is indeed the newly created browser window. 228 content::WindowedNotificationObserver observer( 229 content::NOTIFICATION_LOAD_STOP, 230 content::NotificationService::AllSources()); 231 232 ClickOnControl("auth-button"); 233 234 observer.Wait(); 235 236 content::NavigationController* controller = 237 content::Source<content::NavigationController>(observer.source()).ptr(); 238 239 web_contents_stack_.push_back(controller->GetWebContents()); 240 241 // Verify the active tab is at the "Google Accounts" login page. 242 EXPECT_EQ("accounts.google.com", GetCurrentURL().host()); 243 EXPECT_TRUE(HtmlElementExists("Email")); 244 EXPECT_TRUE(HtmlElementExists("Passwd")); 245 } 246 247 void RemoteDesktopBrowserTest::Authenticate() { 248 // The chromoting extension should be installed. 249 ASSERT_TRUE(extension_); 250 251 // The active tab should have the "Google Accounts" login page loaded. 252 ASSERT_EQ("accounts.google.com", GetCurrentURL().host()); 253 ASSERT_TRUE(HtmlElementExists("Email")); 254 ASSERT_TRUE(HtmlElementExists("Passwd")); 255 256 // Now log in using the username and password passed in from the command line. 257 ExecuteScriptAndWaitForAnyPageLoad( 258 "document.getElementById(\"Email\").value = \"" + username_ + "\";" + 259 "document.getElementById(\"Passwd\").value = \"" + password_ +"\";" + 260 "document.forms[\"gaia_loginform\"].submit();"); 261 262 // TODO(weitaosu): Is there a better way to verify we are on the 263 // "Request for Permission" page? 264 // V2 app won't ask for approval here because the chromoting test account 265 // has already been granted permissions. 266 if (!is_platform_app()) { 267 EXPECT_EQ(GetCurrentURL().host(), "accounts.google.com"); 268 EXPECT_TRUE(HtmlElementExists("submit_approve_access")); 269 } 270 } 271 272 void RemoteDesktopBrowserTest::Approve() { 273 // The chromoting extension should be installed. 274 ASSERT_TRUE(extension_); 275 276 if (is_platform_app()) { 277 // Popping the login window off the stack to return to the chromoting 278 // window. 279 web_contents_stack_.pop_back(); 280 281 // There is nothing for the V2 app to approve because the chromoting test 282 // account has already been granted permissions. 283 // TODO(weitaosu): Revoke the permission at the beginning of the test so 284 // that we can test first-time experience here. 285 ConditionalTimeoutWaiter waiter( 286 base::TimeDelta::FromSeconds(7), 287 base::TimeDelta::FromSeconds(1), 288 base::Bind( 289 &RemoteDesktopBrowserTest::IsAuthenticatedInWindow, 290 active_web_contents())); 291 292 ASSERT_TRUE(waiter.Wait()); 293 } else { 294 ASSERT_EQ("accounts.google.com", GetCurrentURL().host()); 295 296 // Is there a better way to verify we are on the "Request for Permission" 297 // page? 298 ASSERT_TRUE(HtmlElementExists("submit_approve_access")); 299 300 const GURL chromoting_main = Chromoting_Main_URL(); 301 302 // active_web_contents() is still the login window but the observer 303 // should be set up to observe the chromoting window because that is 304 // where we'll receive the message from the login window and reload the 305 // chromoting app. 306 content::WindowedNotificationObserver observer( 307 content::NOTIFICATION_LOAD_STOP, 308 base::Bind( 309 &RemoteDesktopBrowserTest::IsAuthenticatedInWindow, 310 browser()->tab_strip_model()->GetActiveWebContents())); 311 312 // Click to Approve the web-app. 313 ClickOnControl("submit_approve_access"); 314 315 observer.Wait(); 316 317 // Popping the login window off the stack to return to the chromoting 318 // window. 319 web_contents_stack_.pop_back(); 320 } 321 322 ASSERT_TRUE(GetCurrentURL() == Chromoting_Main_URL()); 323 324 EXPECT_TRUE(IsAuthenticated()); 325 } 326 327 void RemoteDesktopBrowserTest::ExpandMe2Me() { 328 // The chromoting extension should be installed. 329 ASSERT_TRUE(extension_); 330 331 // The active tab should have the chromoting app loaded. 332 ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL()); 333 EXPECT_TRUE(IsAuthenticated()); 334 335 // The Me2Me host list should be hidden. 336 ASSERT_FALSE(HtmlElementVisible("me2me-content")); 337 // The Me2Me "Get Start" button should be visible. 338 ASSERT_TRUE(HtmlElementVisible("get-started-me2me")); 339 340 // Starting Me2Me. 341 ExecuteScript("remoting.showMe2MeUiAndSave();"); 342 343 EXPECT_TRUE(HtmlElementVisible("me2me-content")); 344 EXPECT_FALSE(HtmlElementVisible("me2me-first-run")); 345 } 346 347 void RemoteDesktopBrowserTest::DisconnectMe2Me() { 348 // The chromoting extension should be installed. 349 ASSERT_TRUE(extension_); 350 351 ASSERT_TRUE(RemoteDesktopBrowserTest::IsSessionConnected()); 352 353 ClickOnControl("toolbar-stub"); 354 355 EXPECT_TRUE(HtmlElementVisible("session-toolbar")); 356 357 ClickOnControl("toolbar-disconnect"); 358 359 EXPECT_TRUE(HtmlElementVisible("client-dialog")); 360 EXPECT_TRUE(HtmlElementVisible("client-reconnect-button")); 361 EXPECT_TRUE(HtmlElementVisible("client-finished-me2me-button")); 362 363 ClickOnControl("client-finished-me2me-button"); 364 365 EXPECT_FALSE(HtmlElementVisible("client-dialog")); 366 } 367 368 void RemoteDesktopBrowserTest::SimulateKeyPressWithCode( 369 ui::KeyboardCode keyCode, 370 const char* code) { 371 SimulateKeyPressWithCode(keyCode, code, false, false, false, false); 372 } 373 374 void RemoteDesktopBrowserTest::SimulateKeyPressWithCode( 375 ui::KeyboardCode keyCode, 376 const char* code, 377 bool control, 378 bool shift, 379 bool alt, 380 bool command) { 381 content::SimulateKeyPressWithCode( 382 active_web_contents(), 383 keyCode, 384 code, 385 control, 386 shift, 387 alt, 388 command); 389 } 390 391 void RemoteDesktopBrowserTest::SimulateCharInput(char c) { 392 const char* code; 393 ui::KeyboardCode keyboard_code; 394 bool shift; 395 GetKeyValuesFromChar(c, &code, &keyboard_code, &shift); 396 ASSERT_TRUE(code != NULL); 397 SimulateKeyPressWithCode(keyboard_code, code, false, shift, false, false); 398 } 399 400 void RemoteDesktopBrowserTest::SimulateStringInput(const std::string& input) { 401 for (size_t i = 0; i < input.length(); ++i) 402 SimulateCharInput(input[i]); 403 } 404 405 void RemoteDesktopBrowserTest::SimulateMouseLeftClickAt(int x, int y) { 406 SimulateMouseClickAt(0, blink::WebMouseEvent::ButtonLeft, x, y); 407 } 408 409 void RemoteDesktopBrowserTest::SimulateMouseClickAt( 410 int modifiers, blink::WebMouseEvent::Button button, int x, int y) { 411 // TODO(weitaosu): The coordinate translation doesn't work correctly for 412 // apps v2. 413 ExecuteScript( 414 "var clientPluginElement = " 415 "document.getElementById('session-client-plugin');" 416 "var clientPluginRect = clientPluginElement.getBoundingClientRect();"); 417 418 int top = ExecuteScriptAndExtractInt("clientPluginRect.top"); 419 int left = ExecuteScriptAndExtractInt("clientPluginRect.left"); 420 int width = ExecuteScriptAndExtractInt("clientPluginRect.width"); 421 int height = ExecuteScriptAndExtractInt("clientPluginRect.height"); 422 423 ASSERT_GT(x, 0); 424 ASSERT_LT(x, width); 425 ASSERT_GT(y, 0); 426 ASSERT_LT(y, height); 427 428 content::SimulateMouseClickAt( 429 browser()->tab_strip_model()->GetActiveWebContents(), 430 modifiers, 431 button, 432 gfx::Point(left + x, top + y)); 433 } 434 435 void RemoteDesktopBrowserTest::Install() { 436 if (!NoInstall()) { 437 VerifyChromotingLoaded(false); 438 if (is_unpacked()) 439 InstallChromotingAppUnpacked(); 440 else 441 InstallChromotingAppCrx(); 442 } 443 444 VerifyChromotingLoaded(true); 445 } 446 447 void RemoteDesktopBrowserTest::Cleanup() { 448 // TODO(weitaosu): Remove this hack by blocking on the appropriate 449 // notification. 450 // The browser may still be loading images embedded in the webapp. If we 451 // uinstall it now those load will fail. 452 ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait()); 453 454 if (!NoCleanup()) { 455 UninstallChromotingApp(); 456 VerifyChromotingLoaded(false); 457 } 458 459 // TODO(chaitali): Remove this additional timeout after we figure out 460 // why this is needed for the v1 app to work. 461 // Without this timeout the test fail with a "CloseWebContents called for 462 // tab not in our strip" error for the v1 app. 463 ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait()); 464 } 465 466 void RemoteDesktopBrowserTest::SetUpTestForMe2Me() { 467 VerifyInternetAccess(); 468 Install(); 469 LaunchChromotingApp(); 470 Auth(); 471 LoadScript(app_web_content(), FILE_PATH_LITERAL("browser_test.js")); 472 ExpandMe2Me(); 473 EnsureRemoteConnectionEnabled(); 474 } 475 476 void RemoteDesktopBrowserTest::Auth() { 477 Authorize(); 478 Authenticate(); 479 Approve(); 480 } 481 482 void RemoteDesktopBrowserTest::EnsureRemoteConnectionEnabled() { 483 // browser_test.ensureRemoteConnectionEnabled is defined in 484 // browser_test.js, which must be loaded before calling this function. 485 // TODO(kelvinp): This function currently only works on linux when the user is 486 // already part of the chrome-remote-desktop group. Extend this functionality 487 // to Mac (https://crbug.com/397576) and Windows (https://crbug.com/397575). 488 bool result; 489 EXPECT_TRUE(content::ExecuteScriptAndExtractBool( 490 app_web_content(), 491 "browserTest.ensureRemoteConnectionEnabled(" + me2me_pin() + ")", 492 &result)); 493 EXPECT_TRUE(result) << "Cannot start the host with Pin:" << me2me_pin(); 494 } 495 496 void RemoteDesktopBrowserTest::ConnectToLocalHost(bool remember_pin) { 497 // Verify that the local host is online. 498 ASSERT_TRUE(ExecuteScriptAndExtractBool( 499 "remoting.hostList.localHost_.hostName && " 500 "remoting.hostList.localHost_.hostId && " 501 "remoting.hostList.localHost_.status && " 502 "remoting.hostList.localHost_.status == 'ONLINE'")); 503 504 // Connect. 505 ClickOnControl("this-host-connect"); 506 507 // Enter the pin # passed in from the command line. 508 EnterPin(me2me_pin(), remember_pin); 509 510 WaitForConnection(); 511 } 512 513 void RemoteDesktopBrowserTest::ConnectToRemoteHost( 514 const std::string& host_name, bool remember_pin) { 515 std::string host_id = ExecuteScriptAndExtractString( 516 "remoting.hostList.getHostIdForName('" + host_name + "')"); 517 518 EXPECT_FALSE(host_id.empty()); 519 std::string element_id = "host_" + host_id; 520 521 // Verify the host is online. 522 std::string host_div_class = ExecuteScriptAndExtractString( 523 "document.getElementById('" + element_id + "').parentNode.className"); 524 EXPECT_NE(std::string::npos, host_div_class.find("host-online")); 525 526 ClickOnControl(element_id); 527 528 // Enter the pin # passed in from the command line. 529 EnterPin(me2me_pin(), remember_pin); 530 531 WaitForConnection(); 532 } 533 534 void RemoteDesktopBrowserTest::EnableDNSLookupForThisTest( 535 net::RuleBasedHostResolverProc* host_resolver) { 536 // mock_host_resolver_override_ takes ownership of the resolver. 537 scoped_refptr<net::RuleBasedHostResolverProc> resolver = 538 new net::RuleBasedHostResolverProc(host_resolver); 539 resolver->AllowDirectLookup("*.google.com"); 540 // On Linux, we use Chromium's NSS implementation which uses the following 541 // hosts for certificate verification. Without these overrides, running the 542 // integration tests on Linux causes errors as we make external DNS lookups. 543 resolver->AllowDirectLookup("*.thawte.com"); 544 resolver->AllowDirectLookup("*.geotrust.com"); 545 resolver->AllowDirectLookup("*.gstatic.com"); 546 resolver->AllowDirectLookup("*.googleapis.com"); 547 mock_host_resolver_override_.reset( 548 new net::ScopedDefaultHostResolverProc(resolver.get())); 549 } 550 551 void RemoteDesktopBrowserTest::DisableDNSLookupForThisTest() { 552 mock_host_resolver_override_.reset(); 553 } 554 555 void RemoteDesktopBrowserTest::ParseCommandLine() { 556 CommandLine* command_line = CommandLine::ForCurrentProcess(); 557 558 // The test framework overrides any command line user-data-dir 559 // argument with a /tmp/.org.chromium.Chromium.XXXXXX directory. 560 // That happens in the ChromeTestLauncherDelegate, and affects 561 // all unit tests (no opt out available). It intentionally erases 562 // any --user-data-dir switch if present and appends a new one. 563 // Re-override the default data dir if override-user-data-dir 564 // is specified. 565 if (command_line->HasSwitch(kOverrideUserDataDir)) { 566 const base::FilePath& override_user_data_dir = 567 command_line->GetSwitchValuePath(kOverrideUserDataDir); 568 569 ASSERT_FALSE(override_user_data_dir.empty()); 570 571 command_line->AppendSwitchPath(switches::kUserDataDir, 572 override_user_data_dir); 573 } 574 575 username_ = command_line->GetSwitchValueASCII(kUsername); 576 password_ = command_line->GetSwitchValueASCII(kkPassword); 577 me2me_pin_ = command_line->GetSwitchValueASCII(kMe2MePin); 578 remote_host_name_ = command_line->GetSwitchValueASCII(kRemoteHostName); 579 extension_name_ = command_line->GetSwitchValueASCII(kExtensionName); 580 http_server_ = command_line->GetSwitchValueASCII(kHttpServer); 581 582 no_cleanup_ = command_line->HasSwitch(kNoCleanup); 583 no_install_ = command_line->HasSwitch(kNoInstall); 584 585 if (!no_install_) { 586 webapp_crx_ = command_line->GetSwitchValuePath(kWebAppCrx); 587 webapp_unpacked_ = command_line->GetSwitchValuePath(kWebAppUnpacked); 588 // One and only one of these two arguments should be provided. 589 ASSERT_NE(webapp_crx_.empty(), webapp_unpacked_.empty()); 590 } 591 592 // Run with "enable-web-based-signin" flag to enforce web-based sign-in, 593 // rather than inline signin. This ensures we use the same authentication 594 // page, regardless of whether we are testing the v1 or v2 web-app. 595 command_line->AppendSwitch(switches::kEnableWebBasedSignin); 596 597 // Enable experimental extensions; this is to allow adding the LG extensions 598 command_line->AppendSwitch( 599 extensions::switches::kEnableExperimentalExtensionApis); 600 } 601 602 void RemoteDesktopBrowserTest::ExecuteScript(const std::string& script) { 603 ASSERT_TRUE(content::ExecuteScript(active_web_contents(), script)); 604 } 605 606 void RemoteDesktopBrowserTest::ExecuteScriptAndWaitForAnyPageLoad( 607 const std::string& script) { 608 content::WindowedNotificationObserver observer( 609 content::NOTIFICATION_LOAD_STOP, 610 content::Source<content::NavigationController>( 611 &active_web_contents()-> 612 GetController())); 613 614 ExecuteScript(script); 615 616 observer.Wait(); 617 } 618 619 // static 620 bool RemoteDesktopBrowserTest::ExecuteScriptAndExtractBool( 621 content::WebContents* web_contents, const std::string& script) { 622 bool result; 623 EXPECT_TRUE(content::ExecuteScriptAndExtractBool( 624 web_contents, 625 "window.domAutomationController.send(" + script + ");", 626 &result)); 627 628 return result; 629 } 630 631 // static 632 int RemoteDesktopBrowserTest::ExecuteScriptAndExtractInt( 633 content::WebContents* web_contents, const std::string& script) { 634 int result; 635 _ASSERT_TRUE(content::ExecuteScriptAndExtractInt( 636 web_contents, 637 "window.domAutomationController.send(" + script + ");", 638 &result)); 639 640 return result; 641 } 642 643 // static 644 std::string RemoteDesktopBrowserTest::ExecuteScriptAndExtractString( 645 content::WebContents* web_contents, const std::string& script) { 646 std::string result; 647 _ASSERT_TRUE(content::ExecuteScriptAndExtractString( 648 web_contents, 649 "window.domAutomationController.send(" + script + ");", 650 &result)); 651 652 return result; 653 } 654 655 // static 656 bool RemoteDesktopBrowserTest::LoadScript( 657 content::WebContents* web_contents, 658 const base::FilePath::StringType& path) { 659 std::string script; 660 base::FilePath src_dir; 661 _ASSERT_TRUE(PathService::Get(base::DIR_EXE, &src_dir)); 662 base::FilePath script_path = src_dir.Append(path); 663 664 if (!base::ReadFileToString(script_path, &script)) { 665 LOG(ERROR) << "Failed to load script " << script_path.value(); 666 return false; 667 } 668 669 return content::ExecuteScript(web_contents, script); 670 } 671 672 // static 673 void RemoteDesktopBrowserTest::RunJavaScriptTest( 674 content::WebContents* web_contents, 675 const std::string& testName, 676 const std::string& testData) { 677 std::string result; 678 std::string script = "browserTest.runTest(browserTest." + testName + ", " + 679 testData + ");"; 680 681 LOG(INFO) << "Executing " << script; 682 683 ASSERT_TRUE( 684 content::ExecuteScriptAndExtractString(web_contents, script, &result)); 685 686 // Read in the JSON 687 base::JSONReader reader; 688 scoped_ptr<base::Value> value; 689 value.reset(reader.Read(result, base::JSON_ALLOW_TRAILING_COMMAS)); 690 691 // Convert to dictionary 692 base::DictionaryValue* dict_value = NULL; 693 ASSERT_TRUE(value->GetAsDictionary(&dict_value)); 694 695 bool succeeded; 696 std::string error_message; 697 std::string stack_trace; 698 699 // Extract the fields 700 ASSERT_TRUE(dict_value->GetBoolean("succeeded", &succeeded)); 701 ASSERT_TRUE(dict_value->GetString("error_message", &error_message)); 702 ASSERT_TRUE(dict_value->GetString("stack_trace", &stack_trace)); 703 704 EXPECT_TRUE(succeeded) << error_message << "\n" << stack_trace; 705 } 706 707 void RemoteDesktopBrowserTest::ClickOnControl(const std::string& name) { 708 ASSERT_TRUE(HtmlElementVisible(name)); 709 710 std::string has_disabled_attribute = 711 "document.getElementById('" + name + "').hasAttribute('disabled')"; 712 713 if (ExecuteScriptAndExtractBool(active_web_contents(), 714 has_disabled_attribute)) { 715 // This element has a disabled attribute. Wait for it become enabled. 716 ConditionalTimeoutWaiter waiter( 717 base::TimeDelta::FromSeconds(5), 718 base::TimeDelta::FromMilliseconds(500), 719 base::Bind(&RemoteDesktopBrowserTest::IsEnabled, 720 active_web_contents(), name)); 721 ASSERT_TRUE(waiter.Wait()); 722 } 723 724 ExecuteScript("document.getElementById(\"" + name + "\").click();"); 725 } 726 727 void RemoteDesktopBrowserTest::EnterPin(const std::string& pin, 728 bool remember_pin) { 729 // Wait for the pin-form to be displayed. This can take a while. 730 // We also need to dismiss the host-needs-update dialog if it comes up. 731 // TODO(weitaosu) 1: Instead of polling, can we register a callback to be 732 // called when the pin-form is ready? 733 // TODO(weitaosu) 2: Instead of blindly dismiss the host-needs-update dialog, 734 // we should verify that it only pops up at the right circumstance. That 735 // probably belongs in a separate test case though. 736 ConditionalTimeoutWaiter waiter( 737 base::TimeDelta::FromSeconds(30), 738 base::TimeDelta::FromSeconds(1), 739 base::Bind(&RemoteDesktopBrowserTest::IsPinFormVisible, this)); 740 EXPECT_TRUE(waiter.Wait()); 741 742 ExecuteScript( 743 "document.getElementById(\"pin-entry\").value = \"" + pin + "\";"); 744 745 if (remember_pin) { 746 EXPECT_TRUE(HtmlElementVisible("remember-pin")); 747 EXPECT_FALSE(ExecuteScriptAndExtractBool( 748 "document.getElementById('remember-pin-checkbox').checked")); 749 ClickOnControl("remember-pin"); 750 EXPECT_TRUE(ExecuteScriptAndExtractBool( 751 "document.getElementById('remember-pin-checkbox').checked")); 752 } 753 754 ClickOnControl("pin-connect-button"); 755 } 756 757 void RemoteDesktopBrowserTest::WaitForConnection() { 758 // Wait until the client has connected to the server. 759 // This can take a while. 760 // TODO(weitaosu): Instead of polling, can we register a callback to 761 // remoting.clientSession.onStageChange_? 762 ConditionalTimeoutWaiter waiter( 763 base::TimeDelta::FromSeconds(30), 764 base::TimeDelta::FromSeconds(1), 765 base::Bind(&RemoteDesktopBrowserTest::IsSessionConnected, this)); 766 EXPECT_TRUE(waiter.Wait()); 767 768 // The client is not yet ready to take input when the session state becomes 769 // CONNECTED. Wait for 2 seconds for the client to become ready. 770 // TODO(weitaosu): Find a way to detect when the client is truly ready. 771 TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait(); 772 } 773 774 bool RemoteDesktopBrowserTest::IsLocalHostReady() { 775 // TODO(weitaosu): Instead of polling, can we register a callback to 776 // remoting.hostList.setLocalHost_? 777 return ExecuteScriptAndExtractBool("remoting.hostList.localHost_ != null"); 778 } 779 780 bool RemoteDesktopBrowserTest::IsSessionConnected() { 781 // If some form of PINless authentication is enabled, the host version 782 // warning may appear while waiting for the session to connect. 783 DismissHostVersionWarningIfVisible(); 784 785 return ExecuteScriptAndExtractBool( 786 "remoting.clientSession != null && " 787 "remoting.clientSession.getState() == " 788 "remoting.ClientSession.State.CONNECTED"); 789 } 790 791 bool RemoteDesktopBrowserTest::IsPinFormVisible() { 792 DismissHostVersionWarningIfVisible(); 793 return HtmlElementVisible("pin-form"); 794 } 795 796 void RemoteDesktopBrowserTest::DismissHostVersionWarningIfVisible() { 797 if (HtmlElementVisible("host-needs-update-connect-button")) 798 ClickOnControl("host-needs-update-connect-button"); 799 } 800 801 // static 802 bool RemoteDesktopBrowserTest::IsAuthenticatedInWindow( 803 content::WebContents* web_contents) { 804 return ExecuteScriptAndExtractBool( 805 web_contents, "remoting.identity.isAuthenticated()"); 806 } 807 808 // static 809 bool RemoteDesktopBrowserTest::IsHostActionComplete( 810 content::WebContents* client_web_content, 811 std::string host_action_var) { 812 return ExecuteScriptAndExtractBool( 813 client_web_content, 814 host_action_var); 815 } 816 817 // static 818 bool RemoteDesktopBrowserTest::IsEnabled( 819 content::WebContents* client_web_content, 820 const std::string& element_name) { 821 return !ExecuteScriptAndExtractBool( 822 client_web_content, 823 "document.getElementById(\"" + element_name + "\").disabled"); 824 } 825 826 } // namespace remoting 827