• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/files/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 
27 namespace remoting {
28 
RemoteDesktopBrowserTest()29 RemoteDesktopBrowserTest::RemoteDesktopBrowserTest()
30     : extension_(NULL) {
31 }
32 
~RemoteDesktopBrowserTest()33 RemoteDesktopBrowserTest::~RemoteDesktopBrowserTest() {}
34 
SetUp()35 void RemoteDesktopBrowserTest::SetUp() {
36   ParseCommandLine();
37   PlatformAppBrowserTest::SetUp();
38 }
39 
SetUpOnMainThread()40 void 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.
SetUpInProcessBrowserTestFixture()52 void 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 
TearDownInProcessBrowserTestFixture()58 void RemoteDesktopBrowserTest::TearDownInProcessBrowserTestFixture() {
59   DisableDNSLookupForThisTest();
60 }
61 
VerifyInternetAccess()62 void 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 
OpenClientBrowserPage()69 void 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 
HtmlElementVisible(const std::string & name)83 bool 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 
InstallChromotingAppCrx()103 void 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 
InstallChromotingAppUnpacked()115 void 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 
UninstallChromotingApp()131 void RemoteDesktopBrowserTest::UninstallChromotingApp() {
132   UninstallExtension(ChromotingID());
133   extension_ = NULL;
134 }
135 
VerifyChromotingLoaded(bool expected)136 void 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.get(), 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 
LaunchChromotingApp()170 void 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 
Authorize()214 void 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 
Authenticate()247 void 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 
Approve()272 void 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     // Click to Approve the web-app.
313     ClickOnControl("submit_approve_access");
314 
315     observer.Wait();
316 
317     // Popping the login window off the stack to return to the chromoting
318     // window.
319     web_contents_stack_.pop_back();
320   }
321 
322   ASSERT_TRUE(GetCurrentURL() == Chromoting_Main_URL());
323 
324   EXPECT_TRUE(IsAuthenticated());
325 }
326 
ExpandMe2Me()327 void RemoteDesktopBrowserTest::ExpandMe2Me() {
328   // The chromoting extension should be installed.
329   ASSERT_TRUE(extension_);
330 
331   // The active tab should have the chromoting app loaded.
332   ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL());
333   EXPECT_TRUE(IsAuthenticated());
334 
335   // The Me2Me host list should be hidden.
336   ASSERT_FALSE(HtmlElementVisible("me2me-content"));
337   // The Me2Me "Get Start" button should be visible.
338   ASSERT_TRUE(HtmlElementVisible("get-started-me2me"));
339 
340   // Starting Me2Me.
341   ExecuteScript("remoting.showMe2MeUiAndSave();");
342 
343   EXPECT_TRUE(HtmlElementVisible("me2me-content"));
344   EXPECT_FALSE(HtmlElementVisible("me2me-first-run"));
345 }
346 
DisconnectMe2Me()347 void RemoteDesktopBrowserTest::DisconnectMe2Me() {
348   // The chromoting extension should be installed.
349   ASSERT_TRUE(extension_);
350 
351   ASSERT_TRUE(RemoteDesktopBrowserTest::IsSessionConnected());
352 
353   ClickOnControl("toolbar-stub");
354 
355   EXPECT_TRUE(HtmlElementVisible("session-toolbar"));
356 
357   ClickOnControl("toolbar-disconnect");
358 
359   EXPECT_TRUE(HtmlElementVisible("client-dialog"));
360   EXPECT_TRUE(HtmlElementVisible("client-reconnect-button"));
361   EXPECT_TRUE(HtmlElementVisible("client-finished-me2me-button"));
362 
363   ClickOnControl("client-finished-me2me-button");
364 
365   EXPECT_FALSE(HtmlElementVisible("client-dialog"));
366 }
367 
SimulateKeyPressWithCode(ui::KeyboardCode keyCode,const char * code)368 void RemoteDesktopBrowserTest::SimulateKeyPressWithCode(
369     ui::KeyboardCode keyCode,
370     const char* code) {
371   SimulateKeyPressWithCode(keyCode, code, false, false, false, false);
372 }
373 
SimulateKeyPressWithCode(ui::KeyboardCode keyCode,const char * code,bool control,bool shift,bool alt,bool command)374 void RemoteDesktopBrowserTest::SimulateKeyPressWithCode(
375     ui::KeyboardCode keyCode,
376     const char* code,
377     bool control,
378     bool shift,
379     bool alt,
380     bool command) {
381   content::SimulateKeyPressWithCode(
382       active_web_contents(),
383       keyCode,
384       code,
385       control,
386       shift,
387       alt,
388       command);
389 }
390 
SimulateCharInput(char c)391 void RemoteDesktopBrowserTest::SimulateCharInput(char c) {
392   const char* code;
393   ui::KeyboardCode keyboard_code;
394   bool shift;
395   GetKeyValuesFromChar(c, &code, &keyboard_code, &shift);
396   ASSERT_TRUE(code != NULL);
397   SimulateKeyPressWithCode(keyboard_code, code, false, shift, false, false);
398 }
399 
SimulateStringInput(const std::string & input)400 void RemoteDesktopBrowserTest::SimulateStringInput(const std::string& input) {
401   for (size_t i = 0; i < input.length(); ++i)
402     SimulateCharInput(input[i]);
403 }
404 
SimulateMouseLeftClickAt(int x,int y)405 void RemoteDesktopBrowserTest::SimulateMouseLeftClickAt(int x, int y) {
406   SimulateMouseClickAt(0, blink::WebMouseEvent::ButtonLeft, x, y);
407 }
408 
SimulateMouseClickAt(int modifiers,blink::WebMouseEvent::Button button,int x,int y)409 void RemoteDesktopBrowserTest::SimulateMouseClickAt(
410     int modifiers, blink::WebMouseEvent::Button button, int x, int y) {
411   // TODO(weitaosu): The coordinate translation doesn't work correctly for
412   // apps v2.
413   ExecuteScript(
414       "var clientPluginElement = "
415            "document.getElementById('session-client-plugin');"
416       "var clientPluginRect = clientPluginElement.getBoundingClientRect();");
417 
418   int top = ExecuteScriptAndExtractInt("clientPluginRect.top");
419   int left = ExecuteScriptAndExtractInt("clientPluginRect.left");
420   int width = ExecuteScriptAndExtractInt("clientPluginRect.width");
421   int height = ExecuteScriptAndExtractInt("clientPluginRect.height");
422 
423   ASSERT_GT(x, 0);
424   ASSERT_LT(x, width);
425   ASSERT_GT(y, 0);
426   ASSERT_LT(y, height);
427 
428   content::SimulateMouseClickAt(
429       browser()->tab_strip_model()->GetActiveWebContents(),
430       modifiers,
431       button,
432       gfx::Point(left + x, top + y));
433 }
434 
Install()435 void RemoteDesktopBrowserTest::Install() {
436   if (!NoInstall()) {
437     VerifyChromotingLoaded(false);
438     if (is_unpacked())
439       InstallChromotingAppUnpacked();
440     else
441       InstallChromotingAppCrx();
442   }
443 
444   VerifyChromotingLoaded(true);
445 }
446 
Cleanup()447 void RemoteDesktopBrowserTest::Cleanup() {
448   // TODO(weitaosu): Remove this hack by blocking on the appropriate
449   // notification.
450   // The browser may still be loading images embedded in the webapp. If we
451   // uinstall it now those load will fail.
452   ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait());
453 
454   if (!NoCleanup()) {
455     UninstallChromotingApp();
456     VerifyChromotingLoaded(false);
457   }
458 
459   // TODO(chaitali): Remove this additional timeout after we figure out
460   // why this is needed for the v1 app to work.
461   // Without this timeout the test fail with a "CloseWebContents called for
462   // tab not in our strip" error for the v1 app.
463   ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait());
464 }
465 
SetUpTestForMe2Me()466 void RemoteDesktopBrowserTest::SetUpTestForMe2Me() {
467   VerifyInternetAccess();
468   Install();
469   LaunchChromotingApp();
470   Auth();
471   LoadScript(app_web_content(), FILE_PATH_LITERAL("browser_test.js"));
472   ExpandMe2Me();
473   EnsureRemoteConnectionEnabled();
474 }
475 
Auth()476 void RemoteDesktopBrowserTest::Auth() {
477   Authorize();
478   Authenticate();
479   Approve();
480 }
481 
EnsureRemoteConnectionEnabled()482 void RemoteDesktopBrowserTest::EnsureRemoteConnectionEnabled() {
483   // browser_test.ensureRemoteConnectionEnabled is defined in
484   // browser_test.js, which must be loaded before calling this function.
485   // TODO(kelvinp): This function currently only works on linux when the user is
486   // already part of the chrome-remote-desktop group.  Extend this functionality
487   // to Mac (https://crbug.com/397576) and Windows (https://crbug.com/397575).
488   bool result;
489   EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
490       app_web_content(),
491       "browserTest.ensureRemoteConnectionEnabled(" + me2me_pin() + ")",
492       &result));
493   EXPECT_TRUE(result) << "Cannot start the host with Pin:" << me2me_pin();
494 }
495 
ConnectToLocalHost(bool remember_pin)496 void RemoteDesktopBrowserTest::ConnectToLocalHost(bool remember_pin) {
497   // Verify that the local host is online.
498   ASSERT_TRUE(ExecuteScriptAndExtractBool(
499       "remoting.hostList.localHost_.hostName && "
500       "remoting.hostList.localHost_.hostId && "
501       "remoting.hostList.localHost_.status && "
502       "remoting.hostList.localHost_.status == 'ONLINE'"));
503 
504   // Connect.
505   ClickOnControl("this-host-connect");
506 
507   // Enter the pin # passed in from the command line.
508   EnterPin(me2me_pin(), remember_pin);
509 
510   WaitForConnection();
511 }
512 
ConnectToRemoteHost(const std::string & host_name,bool remember_pin)513 void RemoteDesktopBrowserTest::ConnectToRemoteHost(
514     const std::string& host_name, bool remember_pin) {
515   std::string host_id = ExecuteScriptAndExtractString(
516       "remoting.hostList.getHostIdForName('" + host_name + "')");
517 
518   EXPECT_FALSE(host_id.empty());
519   std::string element_id = "host_" + host_id;
520 
521   // Verify the host is online.
522   std::string host_div_class = ExecuteScriptAndExtractString(
523       "document.getElementById('" + element_id + "').parentNode.className");
524   EXPECT_NE(std::string::npos, host_div_class.find("host-online"));
525 
526   ClickOnControl(element_id);
527 
528   // Enter the pin # passed in from the command line.
529   EnterPin(me2me_pin(), remember_pin);
530 
531   WaitForConnection();
532 }
533 
EnableDNSLookupForThisTest(net::RuleBasedHostResolverProc * host_resolver)534 void RemoteDesktopBrowserTest::EnableDNSLookupForThisTest(
535     net::RuleBasedHostResolverProc* host_resolver) {
536   // mock_host_resolver_override_ takes ownership of the resolver.
537   scoped_refptr<net::RuleBasedHostResolverProc> resolver =
538       new net::RuleBasedHostResolverProc(host_resolver);
539   resolver->AllowDirectLookup("*.google.com");
540   // On Linux, we use Chromium's NSS implementation which uses the following
541   // hosts for certificate verification. Without these overrides, running the
542   // integration tests on Linux causes errors as we make external DNS lookups.
543   resolver->AllowDirectLookup("*.thawte.com");
544   resolver->AllowDirectLookup("*.geotrust.com");
545   resolver->AllowDirectLookup("*.gstatic.com");
546   resolver->AllowDirectLookup("*.googleapis.com");
547   mock_host_resolver_override_.reset(
548       new net::ScopedDefaultHostResolverProc(resolver.get()));
549 }
550 
DisableDNSLookupForThisTest()551 void RemoteDesktopBrowserTest::DisableDNSLookupForThisTest() {
552   mock_host_resolver_override_.reset();
553 }
554 
ParseCommandLine()555 void RemoteDesktopBrowserTest::ParseCommandLine() {
556   CommandLine* command_line = CommandLine::ForCurrentProcess();
557 
558   // The test framework overrides any command line user-data-dir
559   // argument with a /tmp/.org.chromium.Chromium.XXXXXX directory.
560   // That happens in the ChromeTestLauncherDelegate, and affects
561   // all unit tests (no opt out available). It intentionally erases
562   // any --user-data-dir switch if present and appends a new one.
563   // Re-override the default data dir if override-user-data-dir
564   // is specified.
565   if (command_line->HasSwitch(kOverrideUserDataDir)) {
566     const base::FilePath& override_user_data_dir =
567         command_line->GetSwitchValuePath(kOverrideUserDataDir);
568 
569     ASSERT_FALSE(override_user_data_dir.empty());
570 
571     command_line->AppendSwitchPath(switches::kUserDataDir,
572                                    override_user_data_dir);
573   }
574 
575   username_ = command_line->GetSwitchValueASCII(kUsername);
576   password_ = command_line->GetSwitchValueASCII(kkPassword);
577   me2me_pin_ = command_line->GetSwitchValueASCII(kMe2MePin);
578   remote_host_name_ = command_line->GetSwitchValueASCII(kRemoteHostName);
579   extension_name_ = command_line->GetSwitchValueASCII(kExtensionName);
580   http_server_ = command_line->GetSwitchValueASCII(kHttpServer);
581 
582   no_cleanup_ = command_line->HasSwitch(kNoCleanup);
583   no_install_ = command_line->HasSwitch(kNoInstall);
584 
585   if (!no_install_) {
586     webapp_crx_ = command_line->GetSwitchValuePath(kWebAppCrx);
587     webapp_unpacked_ = command_line->GetSwitchValuePath(kWebAppUnpacked);
588     // One and only one of these two arguments should be provided.
589     ASSERT_NE(webapp_crx_.empty(), webapp_unpacked_.empty());
590   }
591 
592   // Run with "enable-web-based-signin" flag to enforce web-based sign-in,
593   // rather than inline signin. This ensures we use the same authentication
594   // page, regardless of whether we are testing the v1 or v2 web-app.
595   command_line->AppendSwitch(switches::kEnableWebBasedSignin);
596 
597   // Enable experimental extensions; this is to allow adding the LG extensions
598   command_line->AppendSwitch(
599     extensions::switches::kEnableExperimentalExtensionApis);
600 }
601 
ExecuteScript(const std::string & script)602 void RemoteDesktopBrowserTest::ExecuteScript(const std::string& script) {
603   ASSERT_TRUE(content::ExecuteScript(active_web_contents(), script));
604 }
605 
ExecuteScriptAndWaitForAnyPageLoad(const std::string & script)606 void RemoteDesktopBrowserTest::ExecuteScriptAndWaitForAnyPageLoad(
607     const std::string& script) {
608   content::WindowedNotificationObserver observer(
609       content::NOTIFICATION_LOAD_STOP,
610       content::Source<content::NavigationController>(
611           &active_web_contents()->
612               GetController()));
613 
614   ExecuteScript(script);
615 
616   observer.Wait();
617 }
618 
619 // static
ExecuteScriptAndExtractBool(content::WebContents * web_contents,const std::string & script)620 bool RemoteDesktopBrowserTest::ExecuteScriptAndExtractBool(
621     content::WebContents* web_contents, const std::string& script) {
622   bool result;
623   EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
624       web_contents,
625       "window.domAutomationController.send(" + script + ");",
626       &result));
627 
628   return result;
629 }
630 
631 // static
ExecuteScriptAndExtractInt(content::WebContents * web_contents,const std::string & script)632 int RemoteDesktopBrowserTest::ExecuteScriptAndExtractInt(
633     content::WebContents* web_contents, const std::string& script) {
634   int result;
635   _ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
636       web_contents,
637       "window.domAutomationController.send(" + script + ");",
638       &result));
639 
640   return result;
641 }
642 
643 // static
ExecuteScriptAndExtractString(content::WebContents * web_contents,const std::string & script)644 std::string RemoteDesktopBrowserTest::ExecuteScriptAndExtractString(
645     content::WebContents* web_contents, const std::string& script) {
646   std::string result;
647   _ASSERT_TRUE(content::ExecuteScriptAndExtractString(
648       web_contents,
649       "window.domAutomationController.send(" + script + ");",
650       &result));
651 
652   return result;
653 }
654 
655 // static
LoadScript(content::WebContents * web_contents,const base::FilePath::StringType & path)656 bool RemoteDesktopBrowserTest::LoadScript(
657     content::WebContents* web_contents,
658     const base::FilePath::StringType& path) {
659   std::string script;
660   base::FilePath src_dir;
661   _ASSERT_TRUE(PathService::Get(base::DIR_EXE, &src_dir));
662   base::FilePath script_path = src_dir.Append(path);
663 
664   if (!base::ReadFileToString(script_path, &script)) {
665     LOG(ERROR) << "Failed to load script " << script_path.value();
666     return false;
667   }
668 
669   return content::ExecuteScript(web_contents, script);
670 }
671 
672 // static
RunJavaScriptTest(content::WebContents * web_contents,const std::string & testName,const std::string & testData)673 void RemoteDesktopBrowserTest::RunJavaScriptTest(
674     content::WebContents* web_contents,
675     const std::string& testName,
676     const std::string& testData) {
677   std::string result;
678   std::string script = "browserTest.runTest(browserTest." + testName + ", " +
679                        testData + ");";
680 
681   LOG(INFO) << "Executing " << script;
682 
683   ASSERT_TRUE(
684       content::ExecuteScriptAndExtractString(web_contents, script, &result));
685 
686   // Read in the JSON
687   base::JSONReader reader;
688   scoped_ptr<base::Value> value;
689   value.reset(reader.Read(result, base::JSON_ALLOW_TRAILING_COMMAS));
690 
691   // Convert to dictionary
692   base::DictionaryValue* dict_value = NULL;
693   ASSERT_TRUE(value->GetAsDictionary(&dict_value));
694 
695   bool succeeded;
696   std::string error_message;
697   std::string stack_trace;
698 
699   // Extract the fields
700   ASSERT_TRUE(dict_value->GetBoolean("succeeded", &succeeded));
701   ASSERT_TRUE(dict_value->GetString("error_message", &error_message));
702   ASSERT_TRUE(dict_value->GetString("stack_trace", &stack_trace));
703 
704   EXPECT_TRUE(succeeded) << error_message << "\n" << stack_trace;
705 }
706 
ClickOnControl(const std::string & name)707 void RemoteDesktopBrowserTest::ClickOnControl(const std::string& name) {
708   ASSERT_TRUE(HtmlElementVisible(name));
709 
710   std::string has_disabled_attribute =
711     "document.getElementById('" + name + "').hasAttribute('disabled')";
712 
713   if (ExecuteScriptAndExtractBool(active_web_contents(),
714                                   has_disabled_attribute)) {
715     // This element has a disabled attribute. Wait for it become enabled.
716     ConditionalTimeoutWaiter waiter(
717           base::TimeDelta::FromSeconds(5),
718           base::TimeDelta::FromMilliseconds(500),
719           base::Bind(&RemoteDesktopBrowserTest::IsEnabled,
720             active_web_contents(), name));
721     ASSERT_TRUE(waiter.Wait());
722   }
723 
724   ExecuteScript("document.getElementById(\"" + name + "\").click();");
725 }
726 
EnterPin(const std::string & pin,bool remember_pin)727 void RemoteDesktopBrowserTest::EnterPin(const std::string& pin,
728                                         bool remember_pin) {
729   // Wait for the pin-form to be displayed. This can take a while.
730   // We also need to dismiss the host-needs-update dialog if it comes up.
731   // TODO(weitaosu) 1: Instead of polling, can we register a callback to be
732   // called when the pin-form is ready?
733   // TODO(weitaosu) 2: Instead of blindly dismiss the host-needs-update dialog,
734   // we should verify that it only pops up at the right circumstance. That
735   // probably belongs in a separate test case though.
736   ConditionalTimeoutWaiter waiter(
737       base::TimeDelta::FromSeconds(30),
738       base::TimeDelta::FromSeconds(1),
739       base::Bind(&RemoteDesktopBrowserTest::IsPinFormVisible, this));
740   EXPECT_TRUE(waiter.Wait());
741 
742   ExecuteScript(
743       "document.getElementById(\"pin-entry\").value = \"" + pin + "\";");
744 
745   if (remember_pin) {
746     EXPECT_TRUE(HtmlElementVisible("remember-pin"));
747     EXPECT_FALSE(ExecuteScriptAndExtractBool(
748         "document.getElementById('remember-pin-checkbox').checked"));
749     ClickOnControl("remember-pin");
750     EXPECT_TRUE(ExecuteScriptAndExtractBool(
751         "document.getElementById('remember-pin-checkbox').checked"));
752   }
753 
754   ClickOnControl("pin-connect-button");
755 }
756 
WaitForConnection()757 void RemoteDesktopBrowserTest::WaitForConnection() {
758   // Wait until the client has connected to the server.
759   // This can take a while.
760   // TODO(weitaosu): Instead of polling, can we register a callback to
761   // remoting.clientSession.onStageChange_?
762   ConditionalTimeoutWaiter waiter(
763       base::TimeDelta::FromSeconds(30),
764       base::TimeDelta::FromSeconds(1),
765       base::Bind(&RemoteDesktopBrowserTest::IsSessionConnected, this));
766   EXPECT_TRUE(waiter.Wait());
767 
768   // The client is not yet ready to take input when the session state becomes
769   // CONNECTED. Wait for 2 seconds for the client to become ready.
770   // TODO(weitaosu): Find a way to detect when the client is truly ready.
771   TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait();
772 }
773 
IsLocalHostReady()774 bool RemoteDesktopBrowserTest::IsLocalHostReady() {
775   // TODO(weitaosu): Instead of polling, can we register a callback to
776   // remoting.hostList.setLocalHost_?
777   return ExecuteScriptAndExtractBool("remoting.hostList.localHost_ != null");
778 }
779 
IsSessionConnected()780 bool RemoteDesktopBrowserTest::IsSessionConnected() {
781   // If some form of PINless authentication is enabled, the host version
782   // warning may appear while waiting for the session to connect.
783   DismissHostVersionWarningIfVisible();
784 
785   return ExecuteScriptAndExtractBool(
786       "remoting.clientSession != null && "
787       "remoting.clientSession.getState() == "
788       "remoting.ClientSession.State.CONNECTED");
789 }
790 
IsPinFormVisible()791 bool RemoteDesktopBrowserTest::IsPinFormVisible() {
792   DismissHostVersionWarningIfVisible();
793   return HtmlElementVisible("pin-form");
794 }
795 
DismissHostVersionWarningIfVisible()796 void RemoteDesktopBrowserTest::DismissHostVersionWarningIfVisible() {
797   if (HtmlElementVisible("host-needs-update-connect-button"))
798     ClickOnControl("host-needs-update-connect-button");
799 }
800 
801 // static
IsAuthenticatedInWindow(content::WebContents * web_contents)802 bool RemoteDesktopBrowserTest::IsAuthenticatedInWindow(
803     content::WebContents* web_contents) {
804   return ExecuteScriptAndExtractBool(
805       web_contents, "remoting.identity.isAuthenticated()");
806 }
807 
808 // static
IsHostActionComplete(content::WebContents * client_web_content,std::string host_action_var)809 bool RemoteDesktopBrowserTest::IsHostActionComplete(
810     content::WebContents* client_web_content,
811     std::string host_action_var) {
812   return ExecuteScriptAndExtractBool(
813       client_web_content,
814       host_action_var);
815 }
816 
817 // static
IsEnabled(content::WebContents * client_web_content,const std::string & element_name)818 bool RemoteDesktopBrowserTest::IsEnabled(
819     content::WebContents* client_web_content,
820     const std::string& element_name) {
821   return !ExecuteScriptAndExtractBool(
822     client_web_content,
823     "document.getElementById(\"" + element_name + "\").disabled");
824 }
825 
826 }  // namespace remoting
827