blob: 2cf56e3dec0b6702c04eeb86697dcbd5514b1a91 [file] [log] [blame]
// 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