| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/test/remoting/remote_desktop_browsertest.h" |
| |
| #include <stddef.h> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/macros.h" |
| #include "base/path_service.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/unpacked_installer.h" |
| #include "chrome/browser/ui/extensions/app_launch_params.h" |
| #include "chrome/browser/ui/extensions/application_launch.h" |
| #include "chrome/browser/ui/webui/signin/login_ui_test_utils.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "chrome/test/remoting/key_code_conv.h" |
| #include "chrome/test/remoting/page_load_notification_observer.h" |
| #include "chrome/test/remoting/remote_test_helper.h" |
| #include "chrome/test/remoting/waiter.h" |
| #include "content/public/browser/native_web_keyboard_event.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/test_extension_registry_observer.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/switches.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| |
| using extensions::Extension; |
| |
| namespace remoting { |
| |
| namespace { |
| |
| // Command line arguments specific to the chromoting browser tests. |
| const char kOverrideUserDataDir[] = "override-user-data-dir"; |
| const char kNoCleanup[] = "no-cleanup"; |
| const char kNoInstall[] = "no-install"; |
| const char kWebAppCrx[] = "webapp-crx"; |
| const char kWebAppUnpacked[] = "webapp-unpacked"; |
| const char kUserName[] = "username"; |
| const char kUserPassword[] = "password"; |
| const char kAccountsFile[] = "accounts-file"; |
| const char kAccountType[] = "account-type"; |
| const char kMe2MePin[] = "me2me-pin"; |
| const char kRemoteHostName[] = "remote-host-name"; |
| const char kExtensionName[] = "extension-name"; |
| const char kHttpServer[] = "http-server"; |
| |
| } // namespace |
| |
| RemoteDesktopBrowserTest::RemoteDesktopBrowserTest() |
| : remote_test_helper_(nullptr), extension_(nullptr) { |
| } |
| |
| RemoteDesktopBrowserTest::~RemoteDesktopBrowserTest() {} |
| |
| void RemoteDesktopBrowserTest::SetUp() { |
| ParseCommandLine(); |
| PlatformAppBrowserTest::SetUp(); |
| } |
| |
| void RemoteDesktopBrowserTest::SetUpOnMainThread() { |
| PlatformAppBrowserTest::SetUpOnMainThread(); |
| |
| // Pushing the initial WebContents instance onto the stack before |
| // RunTestOnMainThread() and after |InProcessBrowserTest::browser_| |
| // is initialized in InProcessBrowserTest::RunTestOnMainThreadLoop() |
| web_contents_stack_.push_back( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| } |
| |
| // Change behavior of the default host resolver to avoid DNS lookup errors, |
| // so we can make network calls. |
| void RemoteDesktopBrowserTest::SetUpInProcessBrowserTestFixture() { |
| // The resolver object lifetime is managed by sync_test_setup, not here. |
| EnableDNSLookupForThisTest( |
| new net::RuleBasedHostResolverProc(host_resolver())); |
| } |
| |
| void RemoteDesktopBrowserTest::TearDownInProcessBrowserTestFixture() { |
| DisableDNSLookupForThisTest(); |
| } |
| |
| void RemoteDesktopBrowserTest::VerifyInternetAccess() { |
| ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( |
| browser(), GURL("http://www.google.com"), 1); |
| |
| EXPECT_EQ(GetCurrentURL().host(), "www.google.com"); |
| } |
| |
| void RemoteDesktopBrowserTest::OpenClientBrowserPage() { |
| // Open the client browser page in a new tab |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), GURL(http_server() + "/client.html"), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB); |
| |
| // Save this web content for later reference |
| client_web_content_ = browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| // Go back to the previous tab that has chromoting opened |
| browser()->tab_strip_model()->SelectPreviousTab(); |
| |
| // Create the RemoteTestHelper object to use. |
| remote_test_helper_.reset(new RemoteTestHelper(client_web_content_)); |
| } |
| |
| bool RemoteDesktopBrowserTest::HtmlElementVisible(const std::string& name) { |
| _ASSERT_TRUE(HtmlElementExists(name)); |
| |
| ExecuteScript( |
| "function isElementVisible(name) {" |
| " var element = document.getElementById(name);" |
| " /* The existence of the element has already been ASSERTed. */" |
| " do {" |
| " if (element.hidden) {" |
| " return false;" |
| " }" |
| " element = element.parentNode;" |
| " } while (element != null);" |
| " return true;" |
| "};"); |
| |
| return ExecuteScriptAndExtractBool( |
| "isElementVisible(\"" + name + "\")"); |
| } |
| |
| void RemoteDesktopBrowserTest::InstallChromotingAppCrx() { |
| ASSERT_TRUE(!is_unpacked()); |
| |
| base::FilePath install_dir(WebAppCrxPath()); |
| scoped_refptr<const Extension> extension(InstallExtensionWithUIAutoConfirm( |
| install_dir, 1, browser())); |
| |
| EXPECT_FALSE(extension.get() == NULL); |
| |
| extension_ = extension.get(); |
| } |
| |
| void RemoteDesktopBrowserTest::InstallChromotingAppUnpacked() { |
| ASSERT_TRUE(is_unpacked()); |
| |
| scoped_refptr<extensions::UnpackedInstaller> installer = |
| extensions::UnpackedInstaller::Create(extension_service()); |
| |
| extensions::TestExtensionRegistryObserver observer( |
| extensions::ExtensionRegistry::Get(browser()->profile())); |
| |
| installer->Load(webapp_unpacked_); |
| |
| observer.WaitForExtensionLoaded(); |
| } |
| |
| void RemoteDesktopBrowserTest::UninstallChromotingApp() { |
| UninstallExtension(ChromotingID()); |
| extension_ = NULL; |
| } |
| |
| void RemoteDesktopBrowserTest::VerifyChromotingLoaded(bool expected) { |
| bool installed = false; |
| |
| for (const scoped_refptr<const extensions::Extension>& extension : |
| extensions::ExtensionRegistry::Get(profile())->enabled_extensions()) { |
| // Is there a better way to recognize the chromoting extension |
| // than name comparison? |
| if (extension->name() == extension_name_) { |
| if (extension_) { |
| EXPECT_EQ(extension.get(), extension_); |
| } else { |
| extension_ = extension.get(); |
| } |
| |
| installed = true; |
| break; |
| } |
| } |
| |
| if (installed) { |
| // Either a V1 (TYPE_LEGACY_PACKAGED_APP) or a V2 (TYPE_PLATFORM_APP ) app. |
| extensions::Manifest::Type type = extension_->GetType(); |
| EXPECT_TRUE(type == extensions::Manifest::TYPE_PLATFORM_APP || |
| type == extensions::Manifest::TYPE_LEGACY_PACKAGED_APP); |
| |
| EXPECT_TRUE(extension_->ShouldDisplayInAppLauncher()); |
| } |
| |
| ASSERT_EQ(installed, expected); |
| } |
| |
| content::WebContents* RemoteDesktopBrowserTest::LaunchChromotingApp( |
| bool defer_start, |
| WindowOpenDisposition window_open_disposition) { |
| _ASSERT_TRUE(extension_); |
| |
| GURL chromoting_main = Chromoting_Main_URL(); |
| // We cannot simply wait for any page load because the first page |
| // loaded could be the generated background page. We need to wait |
| // till the chromoting main page is loaded. |
| PageLoadNotificationObserver observer(chromoting_main); |
| observer.set_ignore_url_parameters(true); |
| |
| // If the app should be started in deferred mode, ensure that a "source" URL |
| // parameter is present; if not, ensure that no such parameter is present. |
| // The value of the parameter is determined by the AppLaunchParams ("test", |
| // in this case). |
| extensions::FeatureSwitch::ScopedOverride override_trace_app_source( |
| extensions::FeatureSwitch::trace_app_source(), |
| defer_start); |
| |
| if (is_platform_app()) { |
| window_open_disposition = WindowOpenDisposition::NEW_WINDOW; |
| } |
| |
| OpenApplication(AppLaunchParams(browser()->profile(), extension_, |
| is_platform_app() |
| ? extensions::LAUNCH_CONTAINER_NONE |
| : extensions::LAUNCH_CONTAINER_TAB, |
| window_open_disposition, |
| extensions::SOURCE_TEST)); |
| |
| observer.Wait(); |
| |
| |
| // The active WebContents instance should be the source of the LOAD_STOP |
| // notification. |
| content::NavigationController* controller = |
| content::Source<content::NavigationController>( |
| observer.matched_source()).ptr(); |
| |
| content::WebContents* web_contents = controller->GetWebContents(); |
| _ASSERT_TRUE(web_contents); |
| |
| if (web_contents != active_web_contents()) |
| web_contents_stack_.push_back(web_contents); |
| |
| if (is_platform_app()) { |
| EXPECT_EQ(GetFirstAppWindowWebContents(), active_web_contents()); |
| } else { |
| // For apps v1 only, the DOMOperationObserver is not ready at the LOAD_STOP |
| // event. A half second wait is necessary for the subsequent javascript |
| // injection to work. |
| // TODO(weitaosu): Find out whether there is a more appropriate notification |
| // to wait for so we can get rid of this wait. |
| _ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(5)).Wait()); |
| } |
| |
| EXPECT_EQ(Chromoting_Main_URL(), GetCurrentURL()); |
| return web_contents; |
| } |
| |
| content::WebContents* RemoteDesktopBrowserTest::LaunchChromotingApp( |
| bool defer_start) { |
| return LaunchChromotingApp(defer_start, WindowOpenDisposition::CURRENT_TAB); |
| } |
| |
| void RemoteDesktopBrowserTest::StartChromotingApp() { |
| ClickOnControl("browser-test-continue-init"); |
| } |
| |
| void RemoteDesktopBrowserTest::Authorize() { |
| // The chromoting extension should be installed. |
| ASSERT_TRUE(extension_); |
| |
| // The chromoting main page should be loaded in the current tab |
| // and isAuthenticated() should be false (auth dialog visible). |
| ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL()); |
| ASSERT_FALSE(IsAuthenticated()); |
| |
| // We cannot simply wait for any page load because the first page |
| // loaded will be chrome://chrome-signin in a packaged app. We need to wait |
| // for the Google login page to be loaded (inside an embedded iframe). |
| GURL google_login("https://accounts.google.com/ServiceLogin"); |
| PageLoadNotificationObserver observer(google_login); |
| observer.set_ignore_url_parameters(true); |
| |
| ClickOnControl("auth-button"); |
| |
| observer.Wait(); |
| |
| content::NavigationController* controller = |
| content::Source<content::NavigationController>( |
| observer.matched_source()).ptr(); |
| content::WebContents* web_contents = controller->GetWebContents(); |
| _ASSERT_TRUE(web_contents); |
| |
| if (web_contents != active_web_contents()) { |
| // Pushing the WebContents hosting the Google login page onto the stack. |
| // If this is a packaged app the Google login page will be loaded in an |
| // iframe embedded in the chrome://chrome-signin page. But we can ignore |
| // that WebContents because we never need to interact with it directly. |
| LOG(INFO) << "Pushing onto the stack: " << web_contents->GetURL(); |
| web_contents_stack_.push_back(web_contents); |
| } |
| |
| // Verify the active tab is at the "Google Accounts" login page. |
| EXPECT_EQ("accounts.google.com", GetCurrentURL().host()); |
| } |
| |
| void RemoteDesktopBrowserTest::Authenticate() { |
| // The chromoting extension should be installed. |
| ASSERT_TRUE(extension_); |
| |
| // The active WebContents should have the "Google Accounts" login page loaded. |
| ASSERT_EQ("accounts.google.com", GetCurrentURL().host()); |
| |
| // Sign-in by injecting JavaScript in the chrome://chrome-signin/ page. |
| login_ui_test_utils::ExecuteJsToSigninInSigninFrame(browser(), username_, |
| password_); |
| |
| // TODO(weitaosu): Is there a better way to verify we are on the |
| // "Request for Permission" page? |
| // V2 app won't ask for approval here because the chromoting test account |
| // has already been granted permissions. |
| if (!is_platform_app()) { |
| EXPECT_EQ(GetCurrentURL().host(), "accounts.google.com"); |
| EXPECT_TRUE(HtmlElementExists("submit_approve_access")); |
| } |
| } |
| |
| void RemoteDesktopBrowserTest::Approve() { |
| // The chromoting extension should be installed. |
| ASSERT_TRUE(extension_); |
| |
| if (is_platform_app()) { |
| // Popping the login window off the stack to return to the chromoting |
| // window. |
| web_contents_stack_.pop_back(); |
| |
| // There is nothing for the V2 app to approve because the chromoting test |
| // account has already been granted permissions. |
| // |
| // TODO(weitaosu): Revoke the permission at the beginning of the test so |
| // that we can test first-time experience here. |
| // |
| // TODO(jamiewalch): Remove the elapsed time logging once this has run a few |
| // times on the bots. |
| base::Time start = base::Time::Now(); |
| ConditionalTimeoutWaiter waiter( |
| base::TimeDelta::FromSeconds(10), |
| base::TimeDelta::FromSeconds(1), |
| base::Bind( |
| &RemoteDesktopBrowserTest::IsAuthenticatedInWindow, |
| active_web_contents())); |
| bool result = waiter.Wait(); |
| base::TimeDelta elapsed = base::Time::Now() - start; |
| LOG(INFO) << "*** IsAuthenticatedInWindow took " |
| << elapsed.InSeconds() << "s"; |
| ASSERT_TRUE(result); |
| } else { |
| ASSERT_EQ("accounts.google.com", GetCurrentURL().host()); |
| |
| // Is there a better way to verify we are on the "Request for Permission" |
| // page? |
| ASSERT_TRUE(HtmlElementExists("submit_approve_access")); |
| |
| const GURL chromoting_main = Chromoting_Main_URL(); |
| |
| // active_web_contents() is still the login window but the observer |
| // should be set up to observe the chromoting window because that is |
| // where we'll receive the message from the login window and reload the |
| // chromoting app. |
| content::WindowedNotificationObserver observer( |
| content::NOTIFICATION_LOAD_STOP, |
| base::Bind( |
| &RemoteDesktopBrowserTest::IsAuthenticatedInWindow, |
| browser()->tab_strip_model()->GetActiveWebContents())); |
| |
| // Click to Approve the web-app. |
| ClickOnControl("submit_approve_access"); |
| |
| observer.Wait(); |
| |
| // Popping the login window off the stack to return to the chromoting |
| // window. |
| web_contents_stack_.pop_back(); |
| } |
| |
| ASSERT_TRUE(GetCurrentURL() == Chromoting_Main_URL()); |
| |
| EXPECT_TRUE(IsAuthenticated()); |
| } |
| |
| void RemoteDesktopBrowserTest::ExpandMe2Me() { |
| // The chromoting extension should be installed. |
| ASSERT_TRUE(extension_); |
| |
| // The active tab should have the chromoting app loaded. |
| ASSERT_EQ(Chromoting_Main_URL(), GetCurrentURL()); |
| EXPECT_TRUE(IsAuthenticated()); |
| |
| // The Me2Me host list should be hidden. |
| ASSERT_FALSE(HtmlElementVisible("me2me-content")); |
| // The Me2Me "Get Start" button should be visible. |
| ASSERT_TRUE(HtmlElementVisible("get-started-me2me")); |
| |
| // Starting Me2Me. |
| ExecuteScript("remoting.showMe2MeUiAndSave();"); |
| |
| EXPECT_TRUE(HtmlElementVisible("me2me-content")); |
| EXPECT_FALSE(HtmlElementVisible("me2me-first-run")); |
| } |
| |
| void RemoteDesktopBrowserTest::DisconnectMe2Me() { |
| // The chromoting extension should be installed. |
| ASSERT_TRUE(extension_); |
| |
| ASSERT_TRUE(RemoteDesktopBrowserTest::IsSessionConnected()); |
| |
| ExecuteScript("remoting.app.getActivity().stop();"); |
| |
| EXPECT_TRUE(HtmlElementVisible("client-dialog")); |
| EXPECT_TRUE(HtmlElementVisible("client-reconnect-button")); |
| EXPECT_TRUE(HtmlElementVisible("client-finished-me2me-button")); |
| |
| ClickOnControl("client-finished-me2me-button"); |
| |
| EXPECT_FALSE(HtmlElementVisible("client-dialog")); |
| } |
| |
| void RemoteDesktopBrowserTest::SimulateKeyPressWithCode( |
| ui::KeyboardCode keyCode, |
| const std::string& codeStr) { |
| ui::DomCode code = ui::KeycodeConverter::CodeStringToDomCode(codeStr); |
| SimulateKeyPressWithCode(ui::DomKey(), code, keyCode, false, false, false, |
| false); |
| } |
| |
| void RemoteDesktopBrowserTest::SimulateKeyPressWithCode( |
| ui::DomKey key, |
| ui::DomCode code, |
| ui::KeyboardCode keyCode, |
| bool control, |
| bool shift, |
| bool alt, |
| bool command) { |
| content::SimulateKeyPress(active_web_contents(), key, code, keyCode, control, |
| shift, alt, command); |
| } |
| |
| void RemoteDesktopBrowserTest::SimulateCharInput(char c) { |
| const char* codeStr; |
| ui::KeyboardCode keyboard_code; |
| bool shift; |
| GetKeyValuesFromChar(c, &codeStr, &keyboard_code, &shift); |
| ui::DomKey key = ui::DomKey::FromCharacter(c); |
| ASSERT_TRUE(codeStr != NULL); |
| ui::DomCode code = ui::KeycodeConverter::CodeStringToDomCode(codeStr); |
| SimulateKeyPressWithCode(key, code, keyboard_code, false, shift, false, |
| false); |
| } |
| |
| void RemoteDesktopBrowserTest::SimulateStringInput(const std::string& input) { |
| for (size_t i = 0; i < input.length(); ++i) |
| SimulateCharInput(input[i]); |
| } |
| |
| void RemoteDesktopBrowserTest::SimulateMouseLeftClickAt(int x, int y) { |
| SimulateMouseClickAt(0, blink::WebMouseEvent::Button::kLeft, x, y); |
| } |
| |
| void RemoteDesktopBrowserTest::SimulateMouseClickAt( |
| int modifiers, blink::WebMouseEvent::Button button, int x, int y) { |
| // TODO(weitaosu): The coordinate translation doesn't work correctly for |
| // apps v2. |
| ExecuteScript( |
| "var clientPluginElement = " |
| "document.getElementById('session-client-plugin');" |
| "var clientPluginRect = clientPluginElement.getBoundingClientRect();"); |
| |
| int top = ExecuteScriptAndExtractInt("clientPluginRect.top"); |
| int left = ExecuteScriptAndExtractInt("clientPluginRect.left"); |
| int width = ExecuteScriptAndExtractInt("clientPluginRect.width"); |
| int height = ExecuteScriptAndExtractInt("clientPluginRect.height"); |
| |
| ASSERT_GT(x, 0); |
| ASSERT_LT(x, width); |
| ASSERT_GT(y, 0); |
| ASSERT_LT(y, height); |
| |
| content::SimulateMouseClickAt( |
| browser()->tab_strip_model()->GetActiveWebContents(), |
| modifiers, |
| button, |
| gfx::Point(left + x, top + y)); |
| } |
| |
| void RemoteDesktopBrowserTest::Install() { |
| if (!NoInstall()) { |
| VerifyChromotingLoaded(false); |
| if (is_unpacked()) |
| InstallChromotingAppUnpacked(); |
| else |
| InstallChromotingAppCrx(); |
| } |
| |
| VerifyChromotingLoaded(true); |
| } |
| |
| void RemoteDesktopBrowserTest::LoadBrowserTestJavaScript( |
| content::WebContents* content) { |
| LoadScript(content, FILE_PATH_LITERAL("browser_test.js")); |
| LoadScript(content, FILE_PATH_LITERAL("mock_client_plugin.js")); |
| LoadScript(content, FILE_PATH_LITERAL("mock_host_list_api.js")); |
| LoadScript(content, FILE_PATH_LITERAL("mock_identity.js")); |
| LoadScript(content, FILE_PATH_LITERAL("mock_oauth2_api.js")); |
| LoadScript(content, FILE_PATH_LITERAL("mock_session_connector.js")); |
| LoadScript(content, FILE_PATH_LITERAL("mock_signal_strategy.js")); |
| LoadScript(content, FILE_PATH_LITERAL("timeout_waiter.js")); |
| LoadScript(content, FILE_PATH_LITERAL("sinon.js")); |
| } |
| |
| void RemoteDesktopBrowserTest::Cleanup() { |
| // TODO(weitaosu): Remove this hack by blocking on the appropriate |
| // notification. |
| // The browser may still be loading images embedded in the webapp. If we |
| // uinstall it now those load will fail. |
| ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait()); |
| |
| if (!NoCleanup()) { |
| UninstallChromotingApp(); |
| VerifyChromotingLoaded(false); |
| } |
| |
| // TODO(chaitali): Remove this additional timeout after we figure out |
| // why this is needed for the v1 app to work. |
| // Without this timeout the test fail with a "CloseWebContents called for |
| // tab not in our strip" error for the v1 app. |
| ASSERT_TRUE(TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait()); |
| } |
| |
| content::WebContents* RemoteDesktopBrowserTest::SetUpTest() { |
| LOG(INFO) << "Starting Test Setup."; |
| VerifyInternetAccess(); |
| Install(); |
| content::WebContents* app_web_content = LaunchChromotingApp(false); |
| Auth(); |
| LoadBrowserTestJavaScript(app_web_content); |
| ExpandMe2Me(); |
| // The call to EnsureRemoteConnectionEnabled() does a PIN reset. |
| // This causes the test to fail because of a recent bug: |
| // crbug.com/430676 |
| // TODO(anandc): Reactivate this call after above bug is fixed. |
| // EnsureRemoteConnectionEnabled(app_web_content); |
| return app_web_content; |
| } |
| |
| void RemoteDesktopBrowserTest::Auth() { |
| // For this test, we must be given the user-name and password. |
| ASSERT_TRUE(!username_.empty() && !password_.empty()); |
| |
| Authorize(); |
| Authenticate(); |
| Approve(); |
| } |
| |
| void RemoteDesktopBrowserTest::EnsureRemoteConnectionEnabled( |
| content::WebContents* app_web_contents) { |
| // browser_test.ensureRemoteConnectionEnabled is defined in |
| // browser_test.js, which must be loaded before calling this function. |
| // TODO(kelvinp): This function currently only works on linux when the user is |
| // already part of the chrome-remote-desktop group. Extend this functionality |
| // to Mac (https://crbug.com/397576) and Windows (https://crbug.com/397575). |
| bool result; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| app_web_contents, |
| "browserTest.ensureRemoteConnectionEnabled(" + me2me_pin() + ")", |
| &result)); |
| EXPECT_TRUE(result) << "Cannot start the host with Pin:" << me2me_pin(); |
| } |
| |
| void RemoteDesktopBrowserTest::ConnectToLocalHost(bool remember_pin) { |
| // Wait for local-host to be ready. |
| ConditionalTimeoutWaiter waiter( |
| base::TimeDelta::FromSeconds(5), |
| base::TimeDelta::FromMilliseconds(500), |
| base::Bind(&RemoteDesktopBrowserTest::IsLocalHostReady, |
| base::Unretained(this))); |
| EXPECT_TRUE(waiter.Wait()); |
| |
| // Verify that the local host is online. |
| ASSERT_TRUE(ExecuteScriptAndExtractBool( |
| "remoting.hostList.localHostSection_.host_.hostName && " |
| "remoting.hostList.localHostSection_.host_.hostId && " |
| "remoting.hostList.localHostSection_.host_.status && " |
| "remoting.hostList.localHostSection_.host_.status == 'ONLINE'")); |
| |
| // Connect. |
| ClickOnControl("local-host-connect-button"); |
| |
| // Enter the pin # passed in from the command line. |
| EnterPin(me2me_pin(), remember_pin); |
| |
| WaitForConnection(); |
| } |
| |
| void RemoteDesktopBrowserTest::ConnectToRemoteHost( |
| const std::string& host_name, bool remember_pin) { |
| |
| // Wait for hosts list to be fetched. |
| // This test typically runs with a clean user-profile, with no host-list |
| // cached. Waiting for the host-list to be null is sufficient to proceed. |
| ConditionalTimeoutWaiter waiter( |
| base::TimeDelta::FromSeconds(5), |
| base::TimeDelta::FromMilliseconds(500), |
| base::Bind(&RemoteDesktopBrowserTest::IsHostListReady, |
| base::Unretained(this))); |
| EXPECT_TRUE(waiter.Wait()); |
| |
| std::string host_id = ExecuteScriptAndExtractString( |
| "remoting.hostList.getHostIdForName('" + host_name + "')"); |
| |
| EXPECT_FALSE(host_id.empty()); |
| std::string element_id = "host_" + host_id; |
| |
| // Wait for the hosts to be online. Try 3 times each spanning 20 seconds |
| // successively for 60 seconds. |
| ConditionalTimeoutWaiter hostOnlineWaiter( |
| base::TimeDelta::FromSeconds(60), base::TimeDelta::FromSeconds(20), |
| base::Bind(&RemoteDesktopBrowserTest::IsHostOnline, |
| base::Unretained(this), host_id)); |
| EXPECT_TRUE(hostOnlineWaiter.Wait()); |
| |
| ClickOnControl(element_id); |
| |
| // Enter the pin # passed in from the command line. |
| EnterPin(me2me_pin(), remember_pin); |
| |
| WaitForConnection(); |
| } |
| |
| void RemoteDesktopBrowserTest::EnableDNSLookupForThisTest( |
| net::RuleBasedHostResolverProc* host_resolver) { |
| // mock_host_resolver_override_ takes ownership of the resolver. |
| scoped_refptr<net::RuleBasedHostResolverProc> resolver = |
| new net::RuleBasedHostResolverProc(host_resolver); |
| resolver->AllowDirectLookup("*.google.com"); |
| // On Linux, we use Chromium's NSS implementation which uses the following |
| // hosts for certificate verification. Without these overrides, running the |
| // integration tests on Linux causes errors as we make external DNS lookups. |
| resolver->AllowDirectLookup("*.thawte.com"); |
| resolver->AllowDirectLookup("*.geotrust.com"); |
| resolver->AllowDirectLookup("*.gstatic.com"); |
| resolver->AllowDirectLookup("*.googleapis.com"); |
| mock_host_resolver_override_.reset( |
| new net::ScopedDefaultHostResolverProc(resolver.get())); |
| } |
| |
| void RemoteDesktopBrowserTest::DisableDNSLookupForThisTest() { |
| mock_host_resolver_override_.reset(); |
| } |
| |
| void RemoteDesktopBrowserTest::ParseCommandLine() { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| |
| // The test framework overrides any command line user-data-dir |
| // argument with a /tmp/.org.chromium.Chromium.XXXXXX directory. |
| // That happens in the ChromeTestLauncherDelegate, and affects |
| // all unit tests (no opt out available). It intentionally erases |
| // any --user-data-dir switch if present and appends a new one. |
| // Re-override the default data dir if override-user-data-dir |
| // is specified. |
| if (command_line->HasSwitch(kOverrideUserDataDir)) { |
| const base::FilePath& override_user_data_dir = |
| command_line->GetSwitchValuePath(kOverrideUserDataDir); |
| |
| ASSERT_FALSE(override_user_data_dir.empty()); |
| |
| command_line->AppendSwitchPath(switches::kUserDataDir, |
| override_user_data_dir); |
| } |
| |
| base::CommandLine::StringType accounts_file = |
| command_line->GetSwitchValueNative(kAccountsFile); |
| std::string account_type = command_line->GetSwitchValueASCII(kAccountType); |
| if (!accounts_file.empty()) { |
| // We've been passed in a file containing accounts information. |
| // In this case, we'll obtain the user-name and password information from |
| // the specified file, even if user-name and password have been specified |
| // on the command-line. |
| base::FilePath accounts_file_path((base::FilePath(accounts_file))); |
| ASSERT_FALSE(account_type.empty()); |
| ASSERT_TRUE(base::PathExists((base::FilePath(accounts_file)))); |
| SetUserNameAndPassword((base::FilePath(accounts_file)), account_type); |
| } else { |
| // No file for accounts specified. Read user-name and password from command |
| // line. |
| username_ = command_line->GetSwitchValueASCII(kUserName); |
| password_ = command_line->GetSwitchValueASCII(kUserPassword); |
| } |
| |
| me2me_pin_ = command_line->GetSwitchValueASCII(kMe2MePin); |
| remote_host_name_ = command_line->GetSwitchValueASCII(kRemoteHostName); |
| extension_name_ = command_line->GetSwitchValueASCII(kExtensionName); |
| http_server_ = command_line->GetSwitchValueASCII(kHttpServer); |
| |
| no_cleanup_ = command_line->HasSwitch(kNoCleanup); |
| no_install_ = command_line->HasSwitch(kNoInstall); |
| |
| if (!no_install_) { |
| webapp_crx_ = command_line->GetSwitchValuePath(kWebAppCrx); |
| webapp_unpacked_ = command_line->GetSwitchValuePath(kWebAppUnpacked); |
| // One and only one of these two arguments should be provided. |
| ASSERT_NE(webapp_crx_.empty(), webapp_unpacked_.empty()); |
| } |
| |
| // Enable experimental extensions; this is to allow adding the LG extensions |
| command_line->AppendSwitch( |
| extensions::switches::kEnableExperimentalExtensionApis); |
| } |
| |
| void RemoteDesktopBrowserTest::ExecuteScript(const std::string& script) { |
| ASSERT_TRUE(content::ExecuteScript(active_web_contents(), script)); |
| } |
| |
| void RemoteDesktopBrowserTest::ExecuteScriptAndWaitForAnyPageLoad( |
| const std::string& script) { |
| content::WindowedNotificationObserver observer( |
| content::NOTIFICATION_LOAD_STOP, |
| content::Source<content::NavigationController>( |
| &active_web_contents()-> |
| GetController())); |
| |
| ExecuteScript(script); |
| |
| observer.Wait(); |
| } |
| |
| bool RemoteDesktopBrowserTest::ExecuteScriptAndExtractBool( |
| const std::string& script) { |
| return RemoteTestHelper::ExecuteScriptAndExtractBool(active_web_contents(), |
| script); |
| } |
| |
| // Helper to execute a JavaScript code snippet in the active WebContents |
| // and extract the int result. |
| int RemoteDesktopBrowserTest::ExecuteScriptAndExtractInt( |
| const std::string& script) { |
| return RemoteTestHelper::ExecuteScriptAndExtractInt(active_web_contents(), |
| script); |
| } |
| |
| // Helper to execute a JavaScript code snippet in the active WebContents |
| // and extract the string result. |
| std::string RemoteDesktopBrowserTest::ExecuteScriptAndExtractString( |
| const std::string& script) { |
| return RemoteTestHelper::ExecuteScriptAndExtractString(active_web_contents(), |
| script); |
| } |
| |
| // static |
| bool RemoteDesktopBrowserTest::LoadScript( |
| content::WebContents* web_contents, |
| const base::FilePath::StringType& path) { |
| std::string script; |
| base::FilePath src_dir; |
| _ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &src_dir)); |
| base::FilePath script_path = |
| src_dir.Append(FILE_PATH_LITERAL("remoting/browser_test_resources/")); |
| script_path = script_path.Append(path); |
| |
| if (!base::ReadFileToString(script_path, &script)) { |
| LOG(ERROR) << "Failed to load script " << script_path.value(); |
| return false; |
| } |
| |
| return content::ExecuteScript(web_contents, script); |
| } |
| |
| // static |
| void RemoteDesktopBrowserTest::RunJavaScriptTest( |
| content::WebContents* web_contents, |
| const std::string& testName, |
| const std::string& testData) { |
| std::string result; |
| std::string script = "browserTest.runTest(browserTest." + testName + ", " + |
| testData + ");"; |
| |
| DVLOG(1) << "Executing " << script; |
| |
| ASSERT_TRUE( |
| content::ExecuteScriptAndExtractString(web_contents, script, &result)); |
| |
| // Read in the JSON |
| base::Optional<base::Value> value = |
| base::JSONReader::Read(result, base::JSON_ALLOW_TRAILING_COMMAS); |
| ASSERT_TRUE(value); |
| |
| // Convert to dictionary |
| base::DictionaryValue* dict_value = NULL; |
| ASSERT_TRUE(value->GetAsDictionary(&dict_value)); |
| |
| bool succeeded; |
| std::string error_message; |
| std::string stack_trace; |
| |
| // Extract the fields |
| ASSERT_TRUE(dict_value->GetBoolean("succeeded", &succeeded)); |
| ASSERT_TRUE(dict_value->GetString("error_message", &error_message)); |
| ASSERT_TRUE(dict_value->GetString("stack_trace", &stack_trace)); |
| |
| EXPECT_TRUE(succeeded) << error_message << "\n" << stack_trace; |
| } |
| |
| void RemoteDesktopBrowserTest::ClickOnControl(const std::string& name) { |
| ASSERT_TRUE(HtmlElementVisible(name)); |
| |
| std::string has_disabled_attribute = |
| "document.getElementById('" + name + "').hasAttribute('disabled')"; |
| |
| if (RemoteTestHelper::ExecuteScriptAndExtractBool(active_web_contents(), |
| has_disabled_attribute)) { |
| // This element has a disabled attribute. Wait for it become enabled. |
| ConditionalTimeoutWaiter waiter( |
| base::TimeDelta::FromSeconds(5), |
| base::TimeDelta::FromMilliseconds(500), |
| base::Bind(&RemoteDesktopBrowserTest::IsEnabled, |
| active_web_contents(), name)); |
| ASSERT_TRUE(waiter.Wait()); |
| } |
| |
| ExecuteScript("document.getElementById(\"" + name + "\").click();"); |
| } |
| |
| void RemoteDesktopBrowserTest::EnterPin(const std::string& pin, |
| bool remember_pin) { |
| // Wait for the pin-form to be displayed. This can take a while. |
| // We also need to dismiss the host-needs-update dialog if it comes up. |
| // TODO(weitaosu) 1: Instead of polling, can we register a callback to be |
| // called when the pin-form is ready? |
| // TODO(weitaosu) 2: Instead of blindly dismiss the host-needs-update dialog, |
| // we should verify that it only pops up at the right circumstance. That |
| // probably belongs in a separate test case though. |
| ConditionalTimeoutWaiter waiter( |
| base::TimeDelta::FromSeconds(30), |
| base::TimeDelta::FromSeconds(1), |
| base::Bind(&RemoteDesktopBrowserTest::IsPinFormVisible, |
| base::Unretained(this))); |
| EXPECT_TRUE(waiter.Wait()); |
| |
| ExecuteScript( |
| "document.getElementById(\"pin-entry\").value = \"" + pin + "\";"); |
| |
| if (remember_pin) { |
| EXPECT_TRUE(HtmlElementVisible("remember-pin")); |
| EXPECT_FALSE(ExecuteScriptAndExtractBool( |
| "document.getElementById('remember-pin-checkbox').checked")); |
| ClickOnControl("remember-pin"); |
| EXPECT_TRUE(ExecuteScriptAndExtractBool( |
| "document.getElementById('remember-pin-checkbox').checked")); |
| } |
| |
| ClickOnControl("pin-connect-button"); |
| } |
| |
| void RemoteDesktopBrowserTest::WaitForConnection() { |
| // Wait until the client has connected to the server. |
| // This can take a while. |
| // TODO(weitaosu): Instead of polling, can we register a callback to |
| // remoting.clientSession.onStageChange_? |
| ConditionalTimeoutWaiter waiter( |
| base::TimeDelta::FromSeconds(30), |
| base::TimeDelta::FromSeconds(1), |
| base::Bind(&RemoteDesktopBrowserTest::IsSessionConnected, |
| base::Unretained(this))); |
| EXPECT_TRUE(waiter.Wait()); |
| |
| // The client is not yet ready to take input when the session state becomes |
| // CONNECTED. Wait for 2 seconds for the client to become ready. |
| // TODO(weitaosu): Find a way to detect when the client is truly ready. |
| TimeoutWaiter(base::TimeDelta::FromSeconds(2)).Wait(); |
| } |
| |
| bool RemoteDesktopBrowserTest::IsHostOnline(const std::string& host_id) { |
| ExecuteScript("remoting.hostList.refreshAndDisplay()"); |
| |
| // Verify the host is online. |
| std::string element_id = "host_" + host_id; |
| std::string host_div_class = ExecuteScriptAndExtractString( |
| "document.getElementById('" + element_id + "').parentNode.className"); |
| |
| return (std::string::npos != host_div_class.find("host-online")); |
| } |
| |
| bool RemoteDesktopBrowserTest::IsLocalHostReady() { |
| // TODO(weitaosu): Instead of polling, can we register a callback to |
| // remoting.hostList.setLocalHost_? |
| return ExecuteScriptAndExtractBool( |
| "remoting.hostList.localHostSection_.host_ != null"); |
| } |
| |
| bool RemoteDesktopBrowserTest::IsHostListReady() { |
| // Wait until hostList is not null. |
| // The connect-to-host tests are run on the waterfall using a new profile-dir. |
| // No hosts will be cached. |
| return ExecuteScriptAndExtractBool( |
| "remoting.hostList != null && remoting.hostList.hosts_ != null"); |
| } |
| |
| bool RemoteDesktopBrowserTest::IsSessionConnected() { |
| // If some form of PINless authentication is enabled, the host version |
| // warning may appear while waiting for the session to connect. |
| DismissHostVersionWarningIfVisible(); |
| |
| return ExecuteScriptAndExtractBool( |
| "remoting.currentMode === remoting.AppMode.IN_SESSION"); |
| } |
| |
| bool RemoteDesktopBrowserTest::IsPinFormVisible() { |
| DismissHostVersionWarningIfVisible(); |
| return HtmlElementVisible("pin-form"); |
| } |
| |
| void RemoteDesktopBrowserTest::DismissHostVersionWarningIfVisible() { |
| if (HtmlElementVisible("host-needs-update-connect-button")) |
| ClickOnControl("host-needs-update-connect-button"); |
| } |
| |
| void RemoteDesktopBrowserTest::SetUserNameAndPassword( |
| const base::FilePath& accounts_file_path, |
| const std::string& account_type) { |
| // Read contents of accounts file, using its absolute path. |
| base::FilePath absolute_path = base::MakeAbsoluteFilePath(accounts_file_path); |
| std::string accounts_info; |
| ASSERT_TRUE(base::ReadFileToString(absolute_path, &accounts_info)); |
| |
| // Get the root dictionary from the input json file contents. |
| base::Optional<base::Value> root = |
| base::JSONReader::Read(accounts_info, base::JSON_ALLOW_TRAILING_COMMAS); |
| |
| const base::DictionaryValue* root_dict = NULL; |
| ASSERT_TRUE(root); |
| ASSERT_TRUE(root->GetAsDictionary(&root_dict)); |
| |
| // Now get the dictionary for the specified account type. |
| const base::DictionaryValue* account_dict = NULL; |
| ASSERT_TRUE(root_dict->GetDictionary(account_type, &account_dict)); |
| ASSERT_TRUE(account_dict->GetString(kUserName, &username_)); |
| ASSERT_TRUE(account_dict->GetString(kUserPassword, &password_)); |
| } |
| |
| // static |
| bool RemoteDesktopBrowserTest::IsAuthenticatedInWindow( |
| content::WebContents* web_contents) { |
| return RemoteTestHelper::ExecuteScriptAndExtractBool( |
| web_contents, "remoting.identity.isAuthenticated()"); |
| } |
| |
| // static |
| bool RemoteDesktopBrowserTest::IsHostActionComplete( |
| content::WebContents* client_web_content, |
| std::string host_action_var) { |
| return RemoteTestHelper::ExecuteScriptAndExtractBool( |
| client_web_content, |
| host_action_var); |
| } |
| |
| // static |
| bool RemoteDesktopBrowserTest::IsEnabled( |
| content::WebContents* client_web_content, |
| const std::string& element_name) { |
| return !RemoteTestHelper::ExecuteScriptAndExtractBool( |
| client_web_content, |
| "document.getElementById(\"" + element_name + "\").disabled"); |
| } |
| |
| bool RemoteDesktopBrowserTest::IsAppModeEqualTo(const std::string& mode) { |
| return ExecuteScriptAndExtractBool( |
| "remoting.currentMode == " + mode); |
| } |
| |
| void RemoteDesktopBrowserTest::DisableRemoteConnection() { |
| ConditionalTimeoutWaiter hostReadyWaiter( |
| base::TimeDelta::FromSeconds(5), |
| base::TimeDelta::FromMilliseconds(500), |
| base::Bind(&RemoteDesktopBrowserTest::IsLocalHostReady, |
| base::Unretained(this))); |
| EXPECT_TRUE(hostReadyWaiter.Wait()); |
| |
| ClickOnControl("stop-daemon"); |
| |
| ConditionalTimeoutWaiter setupDoneWaiter( |
| base::TimeDelta::FromSeconds(30), |
| base::TimeDelta::FromMilliseconds(500), |
| base::Bind(&RemoteDesktopBrowserTest::IsAppModeEqualTo, |
| base::Unretained(this), |
| "remoting.AppMode.HOST_SETUP_DONE")); |
| EXPECT_TRUE(setupDoneWaiter.Wait()); |
| |
| ClickOnControl("host-config-done-dismiss"); |
| |
| ConditionalTimeoutWaiter homeWaiter( |
| base::TimeDelta::FromSeconds(5), |
| base::TimeDelta::FromMilliseconds(500), |
| base::Bind(&RemoteDesktopBrowserTest::IsAppModeEqualTo, |
| base::Unretained(this), |
| "remoting.AppMode.HOME")); |
| EXPECT_TRUE(homeWaiter.Wait()); |
| } |
| |
| } // namespace remoting |