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