// Copyright (c) 2012 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/ui/ui_test.h"
#if defined(OS_POSIX)
#include <signal.h>
#include <sys/types.h>
#endif
#include <set>
#include <vector>
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/file_util.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_file_value_serializer.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_file_util.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/profiles/profile_impl.h"
#include "chrome/common/automation_messages.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/logging_chrome.h"
#include "chrome/common/net/url_fixer_upper.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/automation/automation_proxy.h"
#include "chrome/test/automation/browser_proxy.h"
#include "chrome/test/automation/proxy_launcher.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/automation/window_proxy.h"
#include "chrome/test/base/chrome_process_util.h"
#include "chrome/test/base/test_launcher_utils.h"
#include "chrome/test/base/test_switches.h"
#include "chrome/test/base/testing_profile.h"
#include "extensions/common/switches.h"
#include "net/base/net_util.h"
#include "ui/gl/gl_implementation.h"
#include "url/gurl.h"
#if defined(OS_WIN)
#include "base/win/windows_version.h"
#endif
using base::Time;
using base::TimeDelta;
using base::TimeTicks;
const wchar_t UITestBase::kFailedNoCrashService[] =
#if defined(OS_WIN)
L"NOTE: This test is expected to fail if crash_service.exe is not "
L"running. Start it manually before running this test (see the build "
L"output directory).";
#elif defined(OS_LINUX)
L"NOTE: This test is expected to fail if breakpad is not built in "
L"or if chromium is not running headless (try CHROME_HEADLESS=1).";
#else
L"NOTE: Crash service not ported to this platform!";
#endif
UITestBase::UITestBase()
: launch_arguments_(CommandLine::NO_PROGRAM),
expected_errors_(0),
expected_crashes_(0),
homepage_(content::kAboutBlankURL),
wait_for_initial_loads_(true),
dom_automation_enabled_(false),
stats_collection_controller_enabled_(false),
show_window_(false),
clear_profile_(true),
include_testing_id_(true),
enable_file_cookies_(true) {
PathService::Get(chrome::DIR_APP, &browser_directory_);
PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory_);
}
UITestBase::UITestBase(base::MessageLoop::Type msg_loop_type)
: launch_arguments_(CommandLine::NO_PROGRAM),
expected_errors_(0),
expected_crashes_(0),
wait_for_initial_loads_(true),
dom_automation_enabled_(false),
stats_collection_controller_enabled_(false),
show_window_(false),
clear_profile_(true),
include_testing_id_(true),
enable_file_cookies_(true) {
PathService::Get(chrome::DIR_APP, &browser_directory_);
PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory_);
}
UITestBase::~UITestBase() {}
void UITestBase::SetUp() {
// Tests that do a session restore (e.g. SessionRestoreUITest, StartupTest)
// call SetUp() multiple times because they restart the browser mid-test.
// We don't want to reset the ProxyLauncher's state in those cases.
if (!launcher_.get())
launcher_.reset(CreateProxyLauncher());
launcher_->AssertAppNotRunning("Please close any other instances "
"of the app before testing.");
test_start_time_ = Time::NowFromSystemTime();
SetLaunchSwitches();
ASSERT_TRUE(launcher_->InitializeConnection(DefaultLaunchState(),
wait_for_initial_loads_));
}
void UITestBase::TearDown() {
if (launcher_.get())
launcher_->TerminateConnection();
CheckErrorsAndCrashes();
}
AutomationProxy* UITestBase::automation() const {
return launcher_->automation();
}
base::TimeDelta UITestBase::action_timeout() {
return automation()->action_timeout();
}
int UITestBase::action_timeout_ms() {
return action_timeout().InMilliseconds();
}
void UITestBase::set_action_timeout(base::TimeDelta timeout) {
automation()->set_action_timeout(timeout);
VLOG(1) << "Automation action timeout set to "
<< timeout.InMilliseconds() << " ms";
}
void UITestBase::set_action_timeout_ms(int timeout) {
set_action_timeout(base::TimeDelta::FromMilliseconds(timeout));
}
ProxyLauncher* UITestBase::CreateProxyLauncher() {
return new AnonymousProxyLauncher(false);
}
ProxyLauncher::LaunchState UITestBase::DefaultLaunchState() {
base::FilePath browser_executable =
browser_directory_.Append(GetExecutablePath());
CommandLine command(browser_executable);
command.AppendArguments(launch_arguments_, false);
base::Closure setup_profile_callback = base::Bind(&UITestBase::SetUpProfile,
base::Unretained(this));
ProxyLauncher::LaunchState state =
{ clear_profile_, template_user_data_, setup_profile_callback,
command, include_testing_id_, show_window_ };
return state;
}
void UITestBase::SetLaunchSwitches() {
// All flags added here should also be added in ExtraChromeFlags() in
// chrome/test/pyautolib/pyauto.py as well to take effect for all tests
// on chromeos.
// Propagate commandline settings from test_launcher_utils.
test_launcher_utils::PrepareBrowserCommandLineForTests(&launch_arguments_);
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kWaitForDebugger))
launch_arguments_.AppendSwitch(switches::kWaitForDebugger);
// We need cookies on file:// for things like the page cycler.
if (enable_file_cookies_)
launch_arguments_.AppendSwitch(switches::kEnableFileCookies);
if (dom_automation_enabled_)
launch_arguments_.AppendSwitch(switches::kDomAutomationController);
if (stats_collection_controller_enabled_)
launch_arguments_.AppendSwitch(switches::kStatsCollectionController);
// Allow off-store extension installs.
launch_arguments_.AppendSwitchASCII(
extensions::switches::kEasyOffStoreExtensionInstall, "1");
if (!homepage_.empty()) {
// Pass |homepage_| both as an arg (so that it opens on startup) and to the
// homepage switch (so that the homepage is set).
if (!launch_arguments_.HasSwitch(switches::kHomePage))
launch_arguments_.AppendSwitchASCII(switches::kHomePage, homepage_);
if (launch_arguments_.GetArgs().empty() &&
!launch_arguments_.HasSwitch(switches::kRestoreLastSession)) {
launch_arguments_.AppendArg(homepage_);
}
}
if (!test_name_.empty())
launch_arguments_.AppendSwitchASCII(switches::kTestName, test_name_);
}
void UITestBase::SetUpProfile() {
}
void UITestBase::LaunchBrowser() {
LaunchBrowser(launch_arguments_, clear_profile_);
}
void UITestBase::LaunchBrowserAndServer() {
ASSERT_TRUE(launcher_->LaunchBrowserAndServer(DefaultLaunchState(),
wait_for_initial_loads_));
}
void UITestBase::ConnectToRunningBrowser() {
ASSERT_TRUE(launcher_->ConnectToRunningBrowser(wait_for_initial_loads_));
}
void UITestBase::CloseBrowserAndServer() {
if (launcher_.get())
launcher_->CloseBrowserAndServer();
}
void UITestBase::LaunchBrowser(const CommandLine& arguments,
bool clear_profile) {
ProxyLauncher::LaunchState state = DefaultLaunchState();
state.clear_profile = clear_profile;
ASSERT_TRUE(launcher_->LaunchBrowser(state));
}
void UITestBase::QuitBrowser() {
launcher_->QuitBrowser();
}
scoped_refptr<TabProxy> UITestBase::GetActiveTab(int window_index) {
EXPECT_GE(window_index, 0);
int window_count = -1;
// We have to use EXPECT rather than ASSERT here because ASSERT_* only works
// in functions that return void.
EXPECT_TRUE(automation()->GetBrowserWindowCount(&window_count));
if (window_count == -1)
return NULL;
EXPECT_GT(window_count, window_index);
scoped_refptr<BrowserProxy> window_proxy(automation()->
GetBrowserWindow(window_index));
EXPECT_TRUE(window_proxy.get());
if (!window_proxy.get())
return NULL;
int active_tab_index = -1;
EXPECT_TRUE(window_proxy->GetActiveTabIndex(&active_tab_index));
if (active_tab_index == -1)
return NULL;
return window_proxy->GetTab(active_tab_index);
}
scoped_refptr<TabProxy> UITestBase::GetActiveTab() {
scoped_refptr<BrowserProxy> window_proxy(automation()->
GetBrowserWindow(0));
EXPECT_TRUE(window_proxy.get());
if (!window_proxy.get())
return NULL;
scoped_refptr<TabProxy> tab_proxy = window_proxy->GetActiveTab();
EXPECT_TRUE(tab_proxy.get());
return tab_proxy;
}
void UITestBase::NavigateToURL(const GURL& url) {
NavigateToURL(url, 0, GetActiveTabIndex(0));
}
void UITestBase::NavigateToURL(const GURL& url, int window_index) {
NavigateToURL(url, window_index, GetActiveTabIndex(window_index));
}
void UITestBase::NavigateToURL(const GURL& url, int window_index, int
tab_index) {
NavigateToURLBlockUntilNavigationsComplete(url, 1, window_index, tab_index);
}
void UITestBase::NavigateToURLBlockUntilNavigationsComplete(
const GURL& url, int number_of_navigations) {
scoped_refptr<TabProxy> tab_proxy(GetActiveTab());
ASSERT_TRUE(tab_proxy.get());
EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS,
tab_proxy->NavigateToURLBlockUntilNavigationsComplete(
url, number_of_navigations)) << url.spec();
}
void UITestBase::NavigateToURLBlockUntilNavigationsComplete(
const GURL& url, int number_of_navigations, int window_index,
int tab_index) {
scoped_refptr<BrowserProxy> window =
automation()->GetBrowserWindow(window_index);
ASSERT_TRUE(window.get());
scoped_refptr<TabProxy> tab_proxy(window->GetTab(tab_index));
ASSERT_TRUE(tab_proxy.get());
EXPECT_EQ(AUTOMATION_MSG_NAVIGATION_SUCCESS,
tab_proxy->NavigateToURLBlockUntilNavigationsComplete(
url, number_of_navigations)) << url.spec();
}
GURL UITestBase::GetActiveTabURL(int window_index) {
scoped_refptr<TabProxy> tab_proxy(GetActiveTab(window_index));
EXPECT_TRUE(tab_proxy.get());
if (!tab_proxy.get())
return GURL();
GURL url;
bool success = tab_proxy->GetCurrentURL(&url);
EXPECT_TRUE(success);
if (!success)
return GURL();
return url;
}
std::wstring UITestBase::GetActiveTabTitle(int window_index) {
std::wstring title;
scoped_refptr<TabProxy> tab_proxy(GetActiveTab(window_index));
EXPECT_TRUE(tab_proxy.get());
if (!tab_proxy.get())
return title;
EXPECT_TRUE(tab_proxy->GetTabTitle(&title));
return title;
}
int UITestBase::GetActiveTabIndex(int window_index) {
scoped_refptr<BrowserProxy> window_proxy(automation()->
GetBrowserWindow(window_index));
EXPECT_TRUE(window_proxy.get());
if (!window_proxy.get())
return -1;
int active_tab_index = -1;
EXPECT_TRUE(window_proxy->GetActiveTabIndex(&active_tab_index));
return active_tab_index;
}
int UITestBase::GetTabCount() {
return GetTabCount(0);
}
int UITestBase::GetTabCount(int window_index) {
scoped_refptr<BrowserProxy> window(
automation()->GetBrowserWindow(window_index));
EXPECT_TRUE(window.get());
if (!window.get())
return 0;
int result = 0;
EXPECT_TRUE(window->GetTabCount(&result));
return result;
}
void UITestBase::WaitUntilTabCount(int tab_count) {
const int kMaxIntervals = 10;
const TimeDelta kDelay = TestTimeouts::action_timeout() / kMaxIntervals;
for (int i = 0; i < kMaxIntervals; ++i) {
if (GetTabCount() == tab_count)
return;
base::PlatformThread::Sleep(kDelay);
}
ADD_FAILURE() << "Timeout reached in WaitUntilTabCount";
}
const base::FilePath::CharType* UITestBase::GetExecutablePath() {
if (launch_arguments_.HasSwitch(switches::kEnableChromiumBranding))
return chrome::kBrowserProcessExecutablePathChromium;
return chrome::kBrowserProcessExecutablePath;
}
bool UITestBase::CloseBrowser(BrowserProxy* browser,
bool* application_closed) const {
DCHECK(application_closed);
if (!browser->is_valid() || !browser->handle())
return false;
bool result = true;
ChromeProcessList processes = GetRunningChromeProcesses(
browser_process_id());
bool succeeded = automation()->Send(new AutomationMsg_CloseBrowser(
browser->handle(), &result, application_closed));
if (!succeeded)
return false;
if (*application_closed) {
int exit_code = -1;
EXPECT_TRUE(launcher_->WaitForBrowserProcessToQuit(
TestTimeouts::action_max_timeout(), &exit_code));
EXPECT_EQ(0, exit_code); // Expect a clean shutown.
// Ensure no child processes are left dangling.
TerminateAllChromeProcesses(processes);
}
return result;
}
int UITestBase::GetCrashCount() const {
base::FilePath crash_dump_path;
PathService::Get(chrome::DIR_CRASH_DUMPS, &crash_dump_path);
int files_found = 0;
base::FileEnumerator en(crash_dump_path, false, base::FileEnumerator::FILES);
while (!en.Next().empty()) {
if (en.GetInfo().GetLastModifiedTime() > test_start_time_)
files_found++;
}
#if defined(OS_WIN)
// Each crash creates two dump files on Windows.
return files_found / 2;
#else
return files_found;
#endif
}
std::string UITestBase::CheckErrorsAndCrashes() const {
// Make sure that we didn't encounter any assertion failures
logging::AssertionList assertions;
logging::GetFatalAssertions(&assertions);
// If there were errors, get all the error strings for display.
std::wstring failures =
L"The following error(s) occurred in the application during this test:";
if (assertions.size() > expected_errors_) {
logging::AssertionList::const_iterator iter = assertions.begin();
for (; iter != assertions.end(); ++iter) {
failures += L"\n\n";
failures += *iter;
}
}
EXPECT_EQ(expected_errors_, assertions.size()) << failures;
int actual_crashes = GetCrashCount();
std::wstring error_msg =
L"Encountered an unexpected crash in the program during this test.";
if (expected_crashes_ > 0 && actual_crashes == 0) {
error_msg += L" ";
error_msg += kFailedNoCrashService;
}
EXPECT_EQ(expected_crashes_, actual_crashes) << error_msg;
std::wstring wide_result;
if (expected_errors_ != assertions.size()) {
wide_result += failures;
wide_result += L"\n\n";
}
if (expected_crashes_ != actual_crashes)
wide_result += error_msg;
return std::string(wide_result.begin(), wide_result.end());
}
void UITestBase::SetBrowserDirectory(const base::FilePath& dir) {
browser_directory_ = dir;
}
void UITestBase::AppendBrowserLaunchSwitch(const char* name) {
launch_arguments_.AppendSwitch(name);
}
void UITestBase::AppendBrowserLaunchSwitch(const char* name,
const char* value) {
launch_arguments_.AppendSwitchASCII(name, value);
}
bool UITestBase::BeginTracing(const std::string& category_patterns) {
return automation()->BeginTracing(category_patterns);
}
std::string UITestBase::EndTracing() {
std::string json_trace_output;
if (!automation()->EndTracing(&json_trace_output))
return std::string();
return json_trace_output;
}
// UITest methods
void UITest::SetUp() {
// Pass the test case name to chrome.exe on the command line to help with
// parsing Purify output.
const testing::TestInfo* const test_info =
testing::UnitTest::GetInstance()->current_test_info();
if (test_info) {
set_test_name(test_info->test_case_name() + std::string(".") +
test_info->name());
}
UITestBase::SetUp();
PlatformTest::SetUp();
}
void UITest::TearDown() {
UITestBase::TearDown();
PlatformTest::TearDown();
}
ProxyLauncher* UITest::CreateProxyLauncher() {
// Make the AutomationProxy disconnect the channel on the first error,
// so that we avoid spending a lot of time in timeouts. The browser is likely
// hosed if we hit those errors.
return new AnonymousProxyLauncher(true);
}
bool UITest::GetBrowserProcessCount(int* count) {
*count = 0;
if (!automation()->WaitForProcessLauncherThreadToGoIdle())
return false;
*count = GetRunningChromeProcesses(browser_process_id()).size();
return true;
}
static DictionaryValue* LoadDictionaryValueFromPath(
const base::FilePath& path) {
if (path.empty())
return NULL;
JSONFileValueSerializer serializer(path);
scoped_ptr<Value> root_value(serializer.Deserialize(NULL, NULL));
if (!root_value.get() || root_value->GetType() != Value::TYPE_DICTIONARY)
return NULL;
return static_cast<DictionaryValue*>(root_value.release());
}
DictionaryValue* UITest::GetLocalState() {
base::FilePath local_state_path;
PathService::Get(chrome::FILE_LOCAL_STATE, &local_state_path);
return LoadDictionaryValueFromPath(local_state_path);
}
DictionaryValue* UITest::GetDefaultProfilePreferences() {
base::FilePath path;
PathService::Get(chrome::DIR_USER_DATA, &path);
path = path.AppendASCII(TestingProfile::kTestUserProfileDir);
return LoadDictionaryValueFromPath(path.Append(chrome::kPreferencesFilename));
}
void UITest::WaitForFinish(const std::string &name,
const std::string &id,
const GURL &url,
const std::string& test_complete_cookie,
const std::string& expected_cookie_value,
const base::TimeDelta wait_time) {
// The webpage being tested has javascript which sets a cookie
// which signals completion of the test. The cookie name is
// a concatenation of the test name and the test id. This allows
// us to run multiple tests within a single webpage and test
// that they all c
std::string cookie_name = name;
cookie_name.append(".");
cookie_name.append(id);
cookie_name.append(".");
cookie_name.append(test_complete_cookie);
scoped_refptr<TabProxy> tab(GetActiveTab());
ASSERT_TRUE(tab.get());
std::string cookie_value = WaitUntilCookieNonEmpty(tab.get(), url,
cookie_name.c_str(),
wait_time);
EXPECT_EQ(expected_cookie_value, cookie_value);
}
bool UITest::WaitUntilJavaScriptCondition(TabProxy* tab,
const std::wstring& frame_xpath,
const std::wstring& jscript,
base::TimeDelta timeout) {
const TimeDelta kDelay = TimeDelta::FromMilliseconds(250);
const int kMaxDelays = timeout / kDelay;
// Wait until the test signals it has completed.
for (int i = 0; i < kMaxDelays; ++i) {
bool done_value = false;
bool success = tab->ExecuteAndExtractBool(frame_xpath, jscript,
&done_value);
EXPECT_TRUE(success);
if (!success)
return false;
if (done_value)
return true;
base::PlatformThread::Sleep(kDelay);
}
ADD_FAILURE() << "Timeout reached in WaitUntilJavaScriptCondition";
return false;
}
bool UITest::WaitUntilCookieValue(TabProxy* tab,
const GURL& url,
const char* cookie_name,
base::TimeDelta timeout,
const char* expected_value) {
const TimeDelta kDelay = TimeDelta::FromMilliseconds(250);
const int kMaxDelays = timeout / kDelay;
std::string cookie_value;
for (int i = 0; i < kMaxDelays; ++i) {
EXPECT_TRUE(tab->GetCookieByName(url, cookie_name, &cookie_value));
if (cookie_value == expected_value)
return true;
base::PlatformThread::Sleep(kDelay);
}
ADD_FAILURE() << "Timeout reached in WaitUntilCookieValue";
return false;
}
std::string UITest::WaitUntilCookieNonEmpty(TabProxy* tab,
const GURL& url,
const char* cookie_name,
base::TimeDelta timeout) {
const TimeDelta kDelay = TimeDelta::FromMilliseconds(250);
const int kMaxDelays = timeout / kDelay;
for (int i = 0; i < kMaxDelays; ++i) {
std::string cookie_value;
EXPECT_TRUE(tab->GetCookieByName(url, cookie_name, &cookie_value));
if (!cookie_value.empty())
return cookie_value;
base::PlatformThread::Sleep(kDelay);
}
ADD_FAILURE() << "Timeout reached in WaitUntilCookieNonEmpty";
return std::string();
}
bool UITest::WaitForFindWindowVisibilityChange(BrowserProxy* browser,
bool wait_for_open) {
const int kCycles = 10;
const TimeDelta kDelay = TestTimeouts::action_timeout() / kCycles;
for (int i = 0; i < kCycles; i++) {
bool visible = false;
if (!browser->IsFindWindowFullyVisible(&visible))
return false; // Some error.
if (visible == wait_for_open)
return true; // Find window visibility change complete.
// Give it a chance to catch up.
base::PlatformThread::Sleep(kDelay);
}
ADD_FAILURE() << "Timeout reached in WaitForFindWindowVisibilityChange";
return false;
}
void UITest::TerminateBrowser() {
launcher_->TerminateBrowser();
// Make sure the UMA metrics say we didn't crash.
scoped_ptr<DictionaryValue> local_prefs(GetLocalState());
bool exited_cleanly;
ASSERT_TRUE(local_prefs.get());
ASSERT_TRUE(local_prefs->GetBoolean(prefs::kStabilityExitedCleanly,
&exited_cleanly));
ASSERT_TRUE(exited_cleanly);
// And that session end was successful.
bool session_end_completed;
ASSERT_TRUE(local_prefs->GetBoolean(prefs::kStabilitySessionEndCompleted,
&session_end_completed));
ASSERT_TRUE(session_end_completed);
// Make sure session restore says we didn't crash.
scoped_ptr<DictionaryValue> profile_prefs(GetDefaultProfilePreferences());
ASSERT_TRUE(profile_prefs.get());
std::string exit_type;
ASSERT_TRUE(profile_prefs->GetString(prefs::kSessionExitedCleanly,
&exit_type));
EXPECT_EQ(ProfileImpl::kPrefExitTypeNormal, exit_type);
}