Home | History | Annotate | Download | only in remoting
      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