// Copyright (c) 2011 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/webdriver/webdriver_automation.h" #if defined(OS_WIN) #include <windows.h> #endif #include "base/base_paths.h" #include "base/basictypes.h" #include "base/callback.h" #include "base/environment.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/path_service.h" #include "base/string_number_conversions.h" #include "base/string_split.h" #include "base/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/common/automation_constants.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/url_constants.h" #include "chrome/test/automation/automation_json_requests.h" #include "chrome/test/automation/automation_proxy.h" #include "chrome/test/automation/browser_proxy.h" #include "chrome/test/automation/extension_proxy.h" #include "chrome/test/automation/proxy_launcher.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome/test/webdriver/frame_path.h" #include "chrome/test/webdriver/webdriver_basic_types.h" #include "chrome/test/webdriver/webdriver_error.h" #include "chrome/test/webdriver/webdriver_util.h" #if defined(OS_WIN) #include "base/win/registry.h" #include "base/win/windows_version.h" #endif namespace { // Iterates through each browser executable path, and checks if the path exists // in any of the given locations. If found, returns true and sets |browser_exe|. bool CheckForChromeExe(const std::vector<FilePath>& browser_exes, const std::vector<FilePath>& locations, FilePath* browser_exe) { for (size_t i = 0; i < browser_exes.size(); ++i) { for (size_t j = 0; j < locations.size(); ++j) { FilePath path = locations[j].Append(browser_exes[i]); if (file_util::PathExists(path)) { *browser_exe = path; return true; } } } return false; } // Gets the path to the default Chrome executable. Returns true on success. bool GetDefaultChromeExe(FilePath* browser_exe) { // Instead of using chrome constants, we hardcode these constants here so // that we can locate chrome or chromium regardless of the branding // chromedriver is built with. It may be argued that then we need to keep // these in sync with chrome constants. However, if chrome constants changes, // we need to look for the previous and new versions to support some // backwards compatibility. #if defined(OS_WIN) FilePath browser_exes_array[] = { FilePath(L"chrome.exe") }; #elif defined(OS_MACOSX) FilePath browser_exes_array[] = { FilePath("Google Chrome.app/Contents/MacOS/Google Chrome"), FilePath("Chromium.app/Contents/MacOS/Chromium") }; #elif defined(OS_LINUX) FilePath browser_exes_array[] = { FilePath("google-chrome"), FilePath("chrome"), FilePath("chromium"), FilePath("chromium-browser") }; #endif std::vector<FilePath> browser_exes( browser_exes_array, browser_exes_array + arraysize(browser_exes_array)); // Step 1: Check the directory this module resides in. This is done // before all else so that the tests will pickup the built chrome. FilePath module_dir; if (PathService::Get(base::DIR_MODULE, &module_dir)) { for (size_t j = 0; j < browser_exes.size(); ++j) { FilePath path = module_dir.Append(browser_exes[j]); if (file_util::PathExists(path)) { *browser_exe = path; return true; } } } // Step 2: Add all possible install locations, in order they should be // searched. If a location can only hold a chromium install, add it to // |chromium_locations|. Since on some platforms we cannot tell by the binary // name whether it is chrome or chromium, we search these locations last. // We attempt to run chrome before chromium, if any install can be found. std::vector<FilePath> locations; std::vector<FilePath> chromium_locations; #if defined(OS_WIN) // Add the App Paths registry key location. const wchar_t kSubKey[] = L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe"; base::win::RegKey key(HKEY_CURRENT_USER, kSubKey, KEY_READ); std::wstring path; if (key.ReadValue(L"path", &path) == ERROR_SUCCESS) locations.push_back(FilePath(path)); base::win::RegKey sys_key(HKEY_LOCAL_MACHINE, kSubKey, KEY_READ); if (sys_key.ReadValue(L"path", &path) == ERROR_SUCCESS) locations.push_back(FilePath(path)); // Add the user-level location for Chrome. FilePath app_from_google(L"Google\\Chrome\\Application"); FilePath app_from_chromium(L"Chromium\\Application"); scoped_ptr<base::Environment> env(base::Environment::Create()); std::string home_dir; if (env->GetVar("userprofile", &home_dir)) { FilePath default_location(UTF8ToWide(home_dir)); if (base::win::GetVersion() < base::win::VERSION_VISTA) { default_location = default_location.Append( L"Local Settings\\Application Data"); } else { default_location = default_location.Append(L"AppData\\Local"); } locations.push_back(default_location.Append(app_from_google)); chromium_locations.push_back(default_location.Append(app_from_chromium)); } // Add the system-level location for Chrome. std::string program_dir; if (env->GetVar("ProgramFiles", &program_dir)) { locations.push_back(FilePath(UTF8ToWide(program_dir)) .Append(app_from_google)); chromium_locations.push_back(FilePath(UTF8ToWide(program_dir)) .Append(app_from_chromium)); } if (env->GetVar("ProgramFiles(x86)", &program_dir)) { locations.push_back(FilePath(UTF8ToWide(program_dir)) .Append(app_from_google)); chromium_locations.push_back(FilePath(UTF8ToWide(program_dir)) .Append(app_from_chromium)); } #elif defined(OS_MACOSX) std::vector<FilePath> app_dirs; webdriver::GetApplicationDirs(&app_dirs); locations.insert(locations.end(), app_dirs.begin(), app_dirs.end()); #elif defined(OS_LINUX) locations.push_back(FilePath("/opt/google/chrome")); locations.push_back(FilePath("/usr/local/bin")); locations.push_back(FilePath("/usr/local/sbin")); locations.push_back(FilePath("/usr/bin")); locations.push_back(FilePath("/usr/sbin")); locations.push_back(FilePath("/bin")); locations.push_back(FilePath("/sbin")); #endif // Add the current directory. FilePath current_dir; if (file_util::GetCurrentDirectory(¤t_dir)) locations.push_back(current_dir); // Step 3: For each browser exe path, check each location to see if the // browser is installed there. Check the chromium locations lastly. return CheckForChromeExe(browser_exes, locations, browser_exe) || CheckForChromeExe(browser_exes, chromium_locations, browser_exe); } } // namespace namespace webdriver { Automation::BrowserOptions::BrowserOptions() : command(CommandLine::NO_PROGRAM), detach_process(false) {} Automation::BrowserOptions::~BrowserOptions() {} Automation::Automation() {} Automation::~Automation() {} void Automation::Init(const BrowserOptions& options, Error** error) { // Prepare Chrome's command line. CommandLine command(CommandLine::NO_PROGRAM); command.AppendSwitch(switches::kDisableHangMonitor); command.AppendSwitch(switches::kDisablePromptOnRepost); command.AppendSwitch(switches::kDomAutomationController); command.AppendSwitch(switches::kFullMemoryCrashReport); command.AppendSwitch(switches::kNoDefaultBrowserCheck); command.AppendSwitch(switches::kNoFirstRun); if (options.detach_process) command.AppendSwitch(switches::kAutomationReinitializeOnChannelError); if (options.user_data_dir.empty()) command.AppendSwitchASCII(switches::kHomePage, chrome::kAboutBlankURL); command.AppendArguments(options.command, true /* include_program */); // Find the Chrome binary. if (command.GetProgram().empty()) { FilePath browser_exe; if (!GetDefaultChromeExe(&browser_exe)) { *error = new Error(kUnknownError, "Could not find default Chrome binary"); return; } command.SetProgram(browser_exe); } if (!file_util::PathExists(command.GetProgram())) { std::string message = base::StringPrintf( "Could not find Chrome binary at: %" PRFilePath, command.GetProgram().value().c_str()); *error = new Error(kUnknownError, message); return; } std::string chrome_details = base::StringPrintf( "Using Chrome binary at: %" PRFilePath, command.GetProgram().value().c_str()); LOG(INFO) << chrome_details; // Create the ProxyLauncher and launch Chrome. // In Chrome 13/14, the only way to detach the browser process is to use a // named proxy launcher. // TODO(kkania): Remove this when Chrome 15 is stable. std::string channel_id = options.channel_id; bool launch_browser = false; if (options.detach_process) { launch_browser = true; if (!channel_id.empty()) { *error = new Error( kUnknownError, "Cannot detach an already running browser process"); return; } #if defined(OS_WIN) channel_id = "chromedriver" + GenerateRandomID(); #elif defined(OS_POSIX) FilePath temp_file; if (!file_util::CreateTemporaryFile(&temp_file) || !file_util::Delete(temp_file, false /* recursive */)) { *error = new Error(kUnknownError, "Could not create temporary filename"); return; } channel_id = temp_file.value(); #endif } if (channel_id.empty()) { launcher_.reset(new AnonymousProxyLauncher(false)); } else { LOG(INFO) << "Using named testing interface"; launcher_.reset(new NamedProxyLauncher(channel_id, launch_browser, false)); } ProxyLauncher::LaunchState launch_props = { false, // clear_profile options.user_data_dir, // template_user_data base::Closure(), command, true, // include_testing_id true // show_window }; if (!launcher_->InitializeConnection(launch_props, true)) { LOG(ERROR) << "Failed to initialize connection"; *error = new Error( kUnknownError, "Unable to either launch or connect to Chrome. Please check that " "ChromeDriver is up-to-date. " + chrome_details); return; } launcher_->automation()->set_action_timeout_ms(base::kNoTimeout); LOG(INFO) << "Chrome launched successfully. Version: " << automation()->server_version(); // Check the version of Chrome is compatible with this ChromeDriver. chrome_details += ", version (" + automation()->server_version() + ")"; int version = 0; std::string error_msg; if (!SendGetChromeDriverAutomationVersion( automation(), &version, &error_msg)) { *error = new Error(kUnknownError, error_msg + " " + chrome_details); return; } if (version > automation::kChromeDriverAutomationVersion) { *error = new Error( kUnknownError, "ChromeDriver is not compatible with this version of Chrome. " + chrome_details); return; } } void Automation::Terminate() { if (launcher_.get() && launcher_->process() != base::kNullProcessHandle) { launcher_->QuitBrowser(); } } void Automation::ExecuteScript(int tab_id, const FramePath& frame_path, const std::string& script, std::string* result, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; Value* unscoped_value; std::string error_msg; if (!SendExecuteJavascriptJSONRequest(automation(), windex, tab_index, frame_path.value(), script, &unscoped_value, &error_msg)) { *error = new Error(kUnknownError, error_msg); return; } scoped_ptr<Value> value(unscoped_value); if (!value->GetAsString(result)) *error = new Error(kUnknownError, "Execute script did not return string"); } void Automation::MouseMove(int tab_id, const Point& p, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendMouseMoveJSONRequest( automation(), windex, tab_index, p.rounded_x(), p.rounded_y(), &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::MouseClick(int tab_id, const Point& p, automation::MouseButton button, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendMouseClickJSONRequest( automation(), windex, tab_index, button, p.rounded_x(), p.rounded_y(), &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::MouseDrag(int tab_id, const Point& start, const Point& end, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendMouseDragJSONRequest( automation(), windex, tab_index, start.rounded_x(), start.rounded_y(), end.rounded_x(), end.rounded_y(), &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::MouseButtonUp(int tab_id, const Point& p, Error** error) { *error = CheckAdvancedInteractionsSupported(); if (*error) return; int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendMouseButtonUpJSONRequest( automation(), windex, tab_index, p.rounded_x(), p.rounded_y(), &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::MouseButtonDown(int tab_id, const Point& p, Error** error) { *error = CheckAdvancedInteractionsSupported(); if (*error) return; int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendMouseButtonDownJSONRequest( automation(), windex, tab_index, p.rounded_x(), p.rounded_y(), &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::MouseDoubleClick(int tab_id, const Point& p, Error** error) { *error = CheckAdvancedInteractionsSupported(); if (*error) return; int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendMouseDoubleClickJSONRequest( automation(), windex, tab_index, p.rounded_x(), p.rounded_y(), &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::DragAndDropFilePaths( int tab_id, const Point& location, const std::vector<FilePath::StringType>& paths, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) { return; } std::string error_msg; if (!SendDragAndDropFilePathsJSONRequest( automation(), windex, tab_index, location.rounded_x(), location.rounded_y(), paths, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::SendWebKeyEvent(int tab_id, const WebKeyEvent& key_event, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendWebKeyEventJSONRequest( automation(), windex, tab_index, key_event, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::SendNativeKeyEvent(int tab_id, ui::KeyboardCode key_code, int modifiers, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendNativeKeyEventJSONRequest( automation(), windex, tab_index, key_code, modifiers, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::CaptureEntirePageAsPNG(int tab_id, const FilePath& path, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendCaptureEntirePageJSONRequest( automation(), windex, tab_index, path, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::NavigateToURL(int tab_id, const std::string& url, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; AutomationMsg_NavigationResponseValues navigate_response; std::string error_msg; if (!SendNavigateToURLJSONRequest(automation(), windex, tab_index, url, 1, &navigate_response, &error_msg)) { *error = new Error(kUnknownError, error_msg); return; } // TODO(kkania): Do not rely on this enum. if (navigate_response == AUTOMATION_MSG_NAVIGATION_ERROR) *error = new Error(kUnknownError, "Navigation error occurred"); } void Automation::NavigateToURLAsync(int tab_id, const std::string& url, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; scoped_refptr<BrowserProxy> browser = automation()->GetBrowserWindow(windex); if (!browser) { *error = new Error(kUnknownError, "Couldn't obtain browser proxy"); return; } scoped_refptr<TabProxy> tab = browser->GetTab(tab_index); if (!tab) { *error = new Error(kUnknownError, "Couldn't obtain tab proxy"); return; } if (!tab->NavigateToURLAsync(GURL(url))) { *error = new Error(kUnknownError, "Unable to navigate to url"); return; } } void Automation::GoForward(int tab_id, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendGoForwardJSONRequest( automation(), windex, tab_index, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::GoBack(int tab_id, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendGoBackJSONRequest(automation(), windex, tab_index, &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::Reload(int tab_id, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendReloadJSONRequest(automation(), windex, tab_index, &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::GetCookies(const std::string& url, ListValue** cookies, Error** error) { std::string error_msg; if (!SendGetCookiesJSONRequest(automation(), url, cookies, &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::DeleteCookie(const std::string& url, const std::string& cookie_name, Error** error) { std::string error_msg; if (!SendDeleteCookieJSONRequest( automation(), url, cookie_name, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::SetCookie(const std::string& url, DictionaryValue* cookie_dict, Error** error) { std::string error_msg; if (!SendSetCookieJSONRequest(automation(), url, cookie_dict, &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::GetTabIds(std::vector<int>* tab_ids, Error** error) { std::string error_msg; if (!SendGetTabIdsJSONRequest(automation(), tab_ids, &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::DoesTabExist(int tab_id, bool* does_exist, Error** error) { std::string error_msg; if (!SendIsTabIdValidJSONRequest( automation(), tab_id, does_exist, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::CloseTab(int tab_id, Error** error) { int windex = 0, tab_index = 0; *error = GetIndicesForTab(tab_id, &windex, &tab_index); if (*error) return; std::string error_msg; if (!SendCloseTabJSONRequest(automation(), windex, tab_index, &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::GetAppModalDialogMessage(std::string* message, Error** error) { *error = CheckAlertsSupported(); if (*error) return; std::string error_msg; if (!SendGetAppModalDialogMessageJSONRequest( automation(), message, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::AcceptOrDismissAppModalDialog(bool accept, Error** error) { *error = CheckAlertsSupported(); if (*error) return; std::string error_msg; if (!SendAcceptOrDismissAppModalDialogJSONRequest( automation(), accept, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::AcceptPromptAppModalDialog(const std::string& prompt_text, Error** error) { *error = CheckAlertsSupported(); if (*error) return; std::string error_msg; if (!SendAcceptPromptAppModalDialogJSONRequest( automation(), prompt_text, &error_msg)) { *error = new Error(kUnknownError, error_msg); } } void Automation::GetBrowserVersion(std::string* version) { *version = automation()->server_version(); } void Automation::GetChromeDriverAutomationVersion(int* version, Error** error) { std::string error_msg; if (!SendGetChromeDriverAutomationVersion(automation(), version, &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::WaitForAllTabsToStopLoading(Error** error) { std::string error_msg; if (!SendWaitForAllTabsToStopLoadingJSONRequest(automation(), &error_msg)) *error = new Error(kUnknownError, error_msg); } void Automation::InstallExtensionDeprecated( const FilePath& path, Error** error) { if (!launcher_->automation()->InstallExtension(path, false).get()) *error = new Error(kUnknownError, "Failed to install extension"); } void Automation::GetInstalledExtensions( std::vector<std::string>* extension_ids, Error** error) { *error = CheckNewExtensionInterfaceSupported(); if (*error) return; std::string error_msg; base::ListValue extensions_list; if (!SendGetExtensionsInfoJSONRequest( automation(), &extensions_list, &error_msg)) { *error = new Error(kUnknownError, error_msg); return; } for (size_t i = 0; i < extensions_list.GetSize(); i++) { DictionaryValue* extension_dict; if (!extensions_list.GetDictionary(i, &extension_dict)) { *error = new Error(kUnknownError, "Invalid extension dictionary"); return; } bool is_component; if (!extension_dict->GetBoolean("is_component", &is_component)) { *error = new Error(kUnknownError, "Missing or invalid 'is_component_extension'"); return; } if (is_component) continue; std::string extension_id; if (!extension_dict->GetString("id", &extension_id)) { *error = new Error(kUnknownError, "Missing or invalid 'id'"); return; } extension_ids->push_back(extension_id); } } void Automation::InstallExtension( const FilePath& path, std::string* extension_id, Error** error) { *error = CheckNewExtensionInterfaceSupported(); if (*error) return; std::string error_msg; if (!SendInstallExtensionJSONRequest( automation(), path, false /* with_ui */, extension_id, &error_msg)) *error = new Error(kUnknownError, error_msg); } AutomationProxy* Automation::automation() const { return launcher_->automation(); } Error* Automation::GetIndicesForTab( int tab_id, int* browser_index, int* tab_index) { std::string error_msg; if (!SendGetIndicesFromTabIdJSONRequest( automation(), tab_id, browser_index, tab_index, &error_msg)) { return new Error(kUnknownError, error_msg); } return NULL; } Error* Automation::CompareVersion(int client_build_no, int client_patch_no, bool* is_newer_or_equal) { std::string version = automation()->server_version(); std::vector<std::string> split_version; base::SplitString(version, '.', &split_version); if (split_version.size() != 4) { return new Error( kUnknownError, "Browser version has unrecognized format: " + version); } int build_no, patch_no; if (!base::StringToInt(split_version[2], &build_no) || !base::StringToInt(split_version[3], &patch_no)) { return new Error( kUnknownError, "Browser version has unrecognized format: " + version); } if (build_no < client_build_no) *is_newer_or_equal = false; else if (build_no > client_build_no) *is_newer_or_equal = true; else *is_newer_or_equal = patch_no >= client_patch_no; return NULL; } Error* Automation::CheckVersion(int client_build_no, int client_patch_no, const std::string& error_msg) { bool version_is_ok = false; Error* error = CompareVersion( client_build_no, client_patch_no, &version_is_ok); if (error) return error; if (!version_is_ok) return new Error(kUnknownError, error_msg); return NULL; } Error* Automation::CheckAlertsSupported() { return CheckVersion( 768, 0, "Alerts are not supported for this version of Chrome"); } Error* Automation::CheckAdvancedInteractionsSupported() { const char* message = "Advanced user interactions are not supported for this version of Chrome"; return CheckVersion(750, 0, message); } Error* Automation::CheckNewExtensionInterfaceSupported() { const char* message = "Extension interface is not supported for this version of Chrome"; return CheckVersion(947, 0, message); } } // namespace webdriver