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