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