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