1 // Copyright (c) 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 "base/command_line.h" 6 #include "base/path_service.h" 7 #include "chrome/browser/chrome_notification_types.h" 8 #include "chrome/browser/extensions/extension_browsertest.h" 9 #include "chrome/browser/extensions/extension_service.h" 10 #include "chrome/browser/extensions/unpacked_installer.h" 11 #include "chrome/browser/profiles/profile.h" 12 #include "chrome/browser/ui/tabs/tab_strip_model.h" 13 #include "chrome/common/chrome_paths.h" 14 #include "chrome/common/chrome_switches.cc" 15 #include "chrome/common/extensions/extension.h" 16 #include "chrome/common/extensions/extension_file_util.h" 17 #include "chrome/common/extensions/manifest.h" 18 #include "chrome/common/pref_names.h" 19 #include "chrome/test/base/in_process_browser_test.h" 20 #include "chrome/test/base/ui_test_utils.h" 21 #include "content/public/browser/navigation_controller.h" 22 #include "content/public/browser/navigation_entry.h" 23 #include "content/public/browser/notification_service.h" 24 #include "content/public/browser/render_view_host.h" 25 #include "content/public/browser/web_contents.h" 26 #include "content/public/test/browser_test_utils.h" 27 #include "content/public/test/test_utils.h" 28 #include "net/dns/mock_host_resolver.h" 29 30 using extensions::Extension; 31 32 namespace { 33 // Command line arguments specific to the chromoting browser tests. 34 const char kOverrideUserDataDir[] = "override-user-data-dir"; 35 const char kNoCleanup[] = "no-cleanup"; 36 const char kNoInstall[] = "no-install"; 37 const char kWebAppCrx[] = "webapp-crx"; 38 const char kUsername[] = "username"; 39 const char kkPassword[] = "password"; 40 41 // ASSERT_TRUE can only be used in void returning functions. 42 void _ASSERT_TRUE(bool condition) { 43 ASSERT_TRUE(condition); 44 return; 45 } 46 47 } 48 49 namespace remoting { 50 51 class RemoteDesktopBrowserTest : public ExtensionBrowserTest { 52 public: 53 virtual void SetUp() OVERRIDE { 54 ParseCommandLine(); 55 ExtensionBrowserTest::SetUp(); 56 } 57 58 protected: 59 // Override InProcessBrowserTest. Change behavior of the default host 60 // resolver to avoid DNS lookup errors, so we can make network calls. 61 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 62 // The resolver object lifetime is managed by sync_test_setup, not here. 63 EnableDNSLookupForThisTest( 64 new net::RuleBasedHostResolverProc(host_resolver())); 65 } 66 67 // Override InProcessBrowserTest. 68 virtual void TearDownInProcessBrowserTestFixture() OVERRIDE { 69 DisableDNSLookupForThisTest(); 70 } 71 72 // Install the chromoting extension from a crx file. 73 void InstallChromotingApp(); 74 75 // Uninstall the chromoting extension. 76 void UninstallChromotingApp(); 77 78 // Test whether the chromoting extension is installed. 79 void VerifyChromotingLoaded(bool expected); 80 81 // Launch the chromoting app. 82 void LaunchChromotingApp(); 83 84 // Verify the test has access to the internet (specifically google.com) 85 void VerifyInternetAccess(); 86 87 void Authorize(); 88 89 void Authenticate(); 90 91 // Whether to perform the cleanup tasks (uninstalling chromoting, etc). 92 // This is useful for diagnostic purposes. 93 bool NoCleanup() { return no_cleanup_; } 94 95 // Whether to install the chromoting extension before running the test cases. 96 // This is useful for diagnostic purposes. 97 bool NoInstall() { return no_install_; } 98 99 private: 100 void ParseCommandLine(); 101 102 // Change behavior of the default host resolver to allow DNS lookup 103 // to proceed instead of being blocked by the test infrastructure. 104 void EnableDNSLookupForThisTest( 105 net::RuleBasedHostResolverProc* host_resolver); 106 107 // We need to reset the DNS lookup when we finish, or the test will fail. 108 void DisableDNSLookupForThisTest(); 109 110 // Helper to get the path to the crx file of the webapp to be tested. 111 base::FilePath WebAppCrxPath() { return webapp_crx_; } 112 113 // Helper to get the extension ID of the installed chromoting webapp. 114 std::string ChromotingID() { return chromoting_id_; } 115 116 // Helper to retrieve the current URL of the active tab in the browser. 117 GURL GetCurrentURL() { 118 return browser()->tab_strip_model()->GetActiveWebContents()->GetURL(); 119 } 120 121 // Helper to execute a javascript code snippet on the current page. 122 void ExecuteScript(const std::string& script) { 123 ASSERT_TRUE(content::ExecuteScript( 124 browser()->tab_strip_model()->GetActiveWebContents(), script)); 125 } 126 127 // Helper to execute a javascript code snippet on the current page and 128 // wait for page load to complete. 129 void ExecuteScriptAndWait(const std::string& script) { 130 content::WindowedNotificationObserver observer( 131 content::NOTIFICATION_LOAD_STOP, 132 content::Source<content::NavigationController>( 133 &browser()->tab_strip_model()->GetActiveWebContents()-> 134 GetController())); 135 136 ExecuteScript(script); 137 138 observer.Wait(); 139 } 140 141 // Helper to execute a javascript code snippet on the current page and 142 // extract the boolean result. 143 bool ExecuteScriptAndExtractBool(const std::string& script) { 144 bool result; 145 // Using a private assert function because ASSERT_TRUE can only be used in 146 // void returning functions. 147 _ASSERT_TRUE(content::ExecuteScriptAndExtractBool( 148 browser()->tab_strip_model()->GetActiveWebContents(), 149 "window.domAutomationController.send(" + script + ");", 150 &result)); 151 152 return result; 153 } 154 155 // Helper to check whether a html element with the given name exists on 156 // the current page. 157 bool HtmlElementExists(const std::string& name) { 158 return ExecuteScriptAndExtractBool( 159 "document.getElementById(\"" + name + "\") != null"); 160 } 161 162 // Helper to navigate to a given url. 163 void NavigateToURLAndWait(const GURL& url) { 164 content::WindowedNotificationObserver observer( 165 content::NOTIFICATION_LOAD_STOP, 166 content::Source<content::NavigationController>( 167 &browser()->tab_strip_model()->GetActiveWebContents()-> 168 GetController())); 169 170 ui_test_utils::NavigateToURL(browser(), url); 171 observer.Wait(); 172 } 173 174 // This test needs to make live DNS requests for access to 175 // GAIA and sync server URLs under google.com. We use a scoped version 176 // to override the default resolver while the test is active. 177 scoped_ptr<net::ScopedDefaultHostResolverProc> mock_host_resolver_override_; 178 179 bool no_cleanup_; 180 bool no_install_; 181 std::string chromoting_id_; 182 base::FilePath webapp_crx_; 183 std::string username_; 184 std::string password_; 185 }; 186 187 void RemoteDesktopBrowserTest::ParseCommandLine() { 188 CommandLine* command_line = CommandLine::ForCurrentProcess(); 189 190 // The test framework overrides any command line user-data-dir 191 // argument with a /tmp/.org.chromium.Chromium.XXXXXX directory. 192 // That happens in the ChromeTestLauncherDelegate, and affects 193 // all unit tests (no opt out available). It intentionally erases 194 // any --user-data-dir switch if present and appends a new one. 195 // Re-override the default data dir if override-user-data-dir 196 // is specified. 197 if (command_line->HasSwitch(kOverrideUserDataDir)) { 198 const base::FilePath& override_user_data_dir = 199 command_line->GetSwitchValuePath(kOverrideUserDataDir); 200 201 ASSERT_FALSE(override_user_data_dir.empty()); 202 203 command_line->AppendSwitchPath(switches::kUserDataDir, 204 override_user_data_dir); 205 } 206 207 username_ = command_line->GetSwitchValueASCII(kUsername); 208 password_ = command_line->GetSwitchValueASCII(kkPassword); 209 210 no_cleanup_ = command_line->HasSwitch(kNoCleanup); 211 no_install_ = command_line->HasSwitch(kNoInstall); 212 213 if (!no_install_) { 214 webapp_crx_ = command_line->GetSwitchValuePath(kWebAppCrx); 215 ASSERT_FALSE(webapp_crx_.empty()); 216 } 217 } 218 219 void RemoteDesktopBrowserTest::EnableDNSLookupForThisTest( 220 net::RuleBasedHostResolverProc* host_resolver) { 221 // mock_host_resolver_override_ takes ownership of the resolver. 222 scoped_refptr<net::RuleBasedHostResolverProc> resolver = 223 new net::RuleBasedHostResolverProc(host_resolver); 224 resolver->AllowDirectLookup("*.google.com"); 225 // On Linux, we use Chromium's NSS implementation which uses the following 226 // hosts for certificate verification. Without these overrides, running the 227 // integration tests on Linux causes errors as we make external DNS lookups. 228 resolver->AllowDirectLookup("*.thawte.com"); 229 resolver->AllowDirectLookup("*.geotrust.com"); 230 resolver->AllowDirectLookup("*.gstatic.com"); 231 resolver->AllowDirectLookup("*.googleapis.com"); 232 mock_host_resolver_override_.reset( 233 new net::ScopedDefaultHostResolverProc(resolver.get())); 234 } 235 236 void RemoteDesktopBrowserTest::DisableDNSLookupForThisTest() { 237 mock_host_resolver_override_.reset(); 238 } 239 240 void RemoteDesktopBrowserTest::VerifyInternetAccess() { 241 GURL google_url("http://www.google.com"); 242 NavigateToURLAndWait(google_url); 243 244 EXPECT_EQ(GetCurrentURL().host(), "www.google.com"); 245 } 246 247 void RemoteDesktopBrowserTest::InstallChromotingApp() { 248 base::FilePath install_dir(WebAppCrxPath()); 249 scoped_refptr<const Extension> extension(InstallExtensionWithUIAutoConfirm( 250 install_dir, 1, browser())); 251 252 EXPECT_FALSE(extension.get() == NULL); 253 } 254 255 void RemoteDesktopBrowserTest::UninstallChromotingApp() { 256 UninstallExtension(ChromotingID()); 257 chromoting_id_.clear(); 258 } 259 260 void RemoteDesktopBrowserTest::LaunchChromotingApp() { 261 ASSERT_FALSE(ChromotingID().empty()); 262 263 std::string url = "chrome-extension://" + ChromotingID() + "/main.html"; 264 const GURL chromoting_main(url); 265 NavigateToURLAndWait(chromoting_main); 266 267 EXPECT_EQ(GetCurrentURL(), chromoting_main); 268 } 269 270 void RemoteDesktopBrowserTest::VerifyChromotingLoaded(bool expected) { 271 const ExtensionSet* extensions = extension_service()->extensions(); 272 scoped_refptr<const extensions::Extension> extension; 273 ExtensionSet::const_iterator iter; 274 bool installed = false; 275 276 for (iter = extensions->begin(); iter != extensions->end(); ++iter) { 277 extension = *iter; 278 // Is there a better way to recognize the chromoting extension 279 // than name comparison? 280 if (extension->name() == "Chromoting" || 281 extension->name() == "Chrome Remote Desktop") { 282 installed = true; 283 break; 284 } 285 } 286 287 if (installed) { 288 chromoting_id_ = extension->id(); 289 290 EXPECT_EQ(extension->GetType(), 291 extensions::Manifest::TYPE_LEGACY_PACKAGED_APP); 292 293 EXPECT_TRUE(extension->ShouldDisplayInAppLauncher()); 294 } 295 296 EXPECT_EQ(installed, expected); 297 } 298 299 void RemoteDesktopBrowserTest::Authorize() { 300 // The chromoting extension should be installed. 301 ASSERT_FALSE(ChromotingID().empty()); 302 303 // The chromoting main page should be loaded in the current tab 304 // and isAuthenticated() should be false (auth dialog visible). 305 std::string url = "chrome-extension://" + ChromotingID() + "/main.html"; 306 ASSERT_EQ(GetCurrentURL().spec(), url); 307 ASSERT_FALSE(ExecuteScriptAndExtractBool( 308 "remoting.OAuth2.prototype.isAuthenticated()")); 309 310 ExecuteScriptAndWait("remoting.OAuth2.prototype.doAuthRedirect();"); 311 312 // Verify the active tab is at the "Google Accounts" login page. 313 EXPECT_EQ(GetCurrentURL().host(), "accounts.google.com"); 314 EXPECT_TRUE(HtmlElementExists("Email")); 315 EXPECT_TRUE(HtmlElementExists("Passwd")); 316 } 317 318 void RemoteDesktopBrowserTest::Authenticate() { 319 // The chromoting extension should be installed. 320 ASSERT_FALSE(ChromotingID().empty()); 321 322 // The active tab should have the "Google Accounts" login page loaded. 323 ASSERT_EQ(GetCurrentURL().host(), "accounts.google.com"); 324 ASSERT_TRUE(HtmlElementExists("Email")); 325 ASSERT_TRUE(HtmlElementExists("Passwd")); 326 327 // Now log in using the username and password passed in from the command line. 328 ExecuteScriptAndWait( 329 "document.getElementById(\"Email\").value = \"" + username_ + "\";" + 330 "document.getElementById(\"Passwd\").value = \"" + password_ +"\";" + 331 "document.forms[\"gaia_loginform\"].submit();"); 332 333 EXPECT_EQ(GetCurrentURL().host(), "accounts.google.com"); 334 335 // TODO: Is there a better way to verify we are on the 336 // "Request for Permission" page? 337 EXPECT_TRUE(HtmlElementExists("submit_approve_access")); 338 } 339 340 IN_PROC_BROWSER_TEST_F(RemoteDesktopBrowserTest, MANUAL_Launch) { 341 VerifyInternetAccess(); 342 343 if (!NoInstall()) { 344 VerifyChromotingLoaded(false); 345 InstallChromotingApp(); 346 } 347 348 VerifyChromotingLoaded(true); 349 350 LaunchChromotingApp(); 351 352 // TODO: Remove this hack by blocking on the appropriate notification. 353 // The browser may still be loading images embedded in the webapp. If we 354 // uinstall it now those load will fail. Navigating away to avoid the load 355 // failures. 356 ui_test_utils::NavigateToURL(browser(), GURL("about:blank")); 357 358 if (!NoCleanup()) { 359 UninstallChromotingApp(); 360 VerifyChromotingLoaded(false); 361 } 362 } 363 364 IN_PROC_BROWSER_TEST_F(RemoteDesktopBrowserTest, MANUAL_Auth) { 365 VerifyInternetAccess(); 366 367 if (!NoInstall()) { 368 VerifyChromotingLoaded(false); 369 InstallChromotingApp(); 370 } 371 372 VerifyChromotingLoaded(true); 373 374 LaunchChromotingApp(); 375 376 Authorize(); 377 378 Authenticate(); 379 380 if (!NoCleanup()) { 381 UninstallChromotingApp(); 382 VerifyChromotingLoaded(false); 383 } 384 } 385 386 } // namespace remoting 387