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