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