| // 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/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_util.h" |
| #include "base/files/file_path.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/values.h" |
| #include "chrome/common/automation_constants.h" |
| #include "chrome/common/automation_messages.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/proxy_launcher.h" |
| #include "chrome/test/automation/tab_proxy.h" |
| #include "chrome/test/base/chrome_process_util.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<base::FilePath>& browser_exes, |
| const std::vector<base::FilePath>& locations, |
| base::FilePath* browser_exe) { |
| for (size_t i = 0; i < browser_exes.size(); ++i) { |
| for (size_t j = 0; j < locations.size(); ++j) { |
| base::FilePath path = locations[j].Append(browser_exes[i]); |
| if (base::PathExists(path)) { |
| *browser_exe = path; |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| // Gets the path to the default Chrome executable. Returns true on success. |
| bool GetDefaultChromeExe(base::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) |
| base::FilePath browser_exes_array[] = { |
| base::FilePath(L"chrome.exe") |
| }; |
| #elif defined(OS_MACOSX) |
| base::FilePath browser_exes_array[] = { |
| base::FilePath("Google Chrome.app/Contents/MacOS/Google Chrome"), |
| base::FilePath("Chromium.app/Contents/MacOS/Chromium") |
| }; |
| #elif defined(OS_LINUX) |
| base::FilePath browser_exes_array[] = { |
| base::FilePath("google-chrome"), |
| base::FilePath("chrome"), |
| base::FilePath("chromium"), |
| base::FilePath("chromium-browser") |
| }; |
| #endif |
| std::vector<base::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. |
| base::FilePath module_dir; |
| if (PathService::Get(base::DIR_MODULE, &module_dir)) { |
| for (size_t j = 0; j < browser_exes.size(); ++j) { |
| base::FilePath path = module_dir.Append(browser_exes[j]); |
| if (base::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<base::FilePath> locations; |
| std::vector<base::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(base::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(base::FilePath(path)); |
| |
| // Add the user-level location for Chrome. |
| base::FilePath app_from_google(L"Google\\Chrome\\Application"); |
| base::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)) { |
| base::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(base::FilePath(UTF8ToWide(program_dir)) |
| .Append(app_from_google)); |
| chromium_locations.push_back(base::FilePath(UTF8ToWide(program_dir)) |
| .Append(app_from_chromium)); |
| } |
| if (env->GetVar("ProgramFiles(x86)", &program_dir)) { |
| locations.push_back(base::FilePath(UTF8ToWide(program_dir)) |
| .Append(app_from_google)); |
| chromium_locations.push_back(base::FilePath(UTF8ToWide(program_dir)) |
| .Append(app_from_chromium)); |
| } |
| #elif defined(OS_MACOSX) |
| std::vector<base::FilePath> app_dirs; |
| webdriver::GetApplicationDirs(&app_dirs); |
| locations.insert(locations.end(), app_dirs.begin(), app_dirs.end()); |
| #elif defined(OS_LINUX) |
| locations.push_back(base::FilePath("/opt/google/chrome")); |
| locations.push_back(base::FilePath("/usr/local/bin")); |
| locations.push_back(base::FilePath("/usr/local/sbin")); |
| locations.push_back(base::FilePath("/usr/bin")); |
| locations.push_back(base::FilePath("/usr/sbin")); |
| locations.push_back(base::FilePath("/bin")); |
| locations.push_back(base::FilePath("/sbin")); |
| #endif |
| |
| // Add the current directory. |
| base::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); |
| } |
| |
| // Message that duplicates a given message but uses a different type. |
| class MessageWithAlternateType : public IPC::Message { |
| public: |
| MessageWithAlternateType(const IPC::Message& msg, int type) |
| : IPC::Message(msg) { |
| header()->type = type; |
| } |
| virtual ~MessageWithAlternateType() {} |
| }; |
| |
| // Filters incoming and outgoing messages on the IO thread. Translates messages |
| // from old Chrome versions to the new version. This is needed so that new |
| // ChromeDriver releases support the current stable and beta channels of Chrome. |
| // TODO(kkania): Delete this when Chrome v21 is stable. |
| class BackwardsCompatAutomationMessageFilter |
| : public IPC::ChannelProxy::MessageFilter, |
| public IPC::ChannelProxy::OutgoingMessageFilter { |
| public: |
| explicit BackwardsCompatAutomationMessageFilter(AutomationProxy* server); |
| |
| // Overriden from IPC::ChannelProxy::MessageFiler. |
| virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; |
| |
| // Overriden from IPC::ChannelProxy::OutgoingMessageFiler. |
| virtual IPC::Message* Rewrite(IPC::Message* message) OVERRIDE; |
| |
| private: |
| virtual ~BackwardsCompatAutomationMessageFilter(); |
| |
| // The first version of Chrome using the new IPC automation message set, |
| // after r137672 changed most of the message IDs. |
| static const int kNewAutomationVersion = 1142; |
| |
| // The first version of Chrome using the new JSON interface which takes a |
| // browser index instead of a browser handle. |
| static const int kNewJSONInterfaceVersion = 1195; |
| |
| AutomationProxy* server_; |
| bool old_version_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BackwardsCompatAutomationMessageFilter); |
| }; |
| |
| BackwardsCompatAutomationMessageFilter::BackwardsCompatAutomationMessageFilter( |
| AutomationProxy* server) |
| : server_(server), old_version_(false) { |
| } |
| |
| bool BackwardsCompatAutomationMessageFilter::OnMessageReceived( |
| const IPC::Message& message) { |
| const uint32 kOldHelloType = 44, |
| kOldInitialLoadsCompleteType = 47, |
| kOldInitialNewTabUILoadCompleteType = 267; |
| if (message.type() == kOldHelloType) { |
| old_version_ = true; |
| std::string server_version; |
| PickleIterator iter(message); |
| CHECK(message.ReadString(&iter, &server_version)); |
| server_->SignalAppLaunch(server_version); |
| return true; |
| } |
| if (!old_version_) |
| return false; |
| |
| switch (message.type()) { |
| case kOldInitialLoadsCompleteType: |
| server_->SignalInitialLoads(); |
| break; |
| case kOldInitialNewTabUILoadCompleteType: |
| server_->SignalNewTabUITab(-1); |
| break; |
| default: |
| return false; |
| } |
| return true; |
| } |
| |
| IPC::Message* BackwardsCompatAutomationMessageFilter::Rewrite( |
| IPC::Message* message) { |
| int build_no = -1; |
| std::string version = server_->server_version(); |
| std::vector<std::string> version_parts; |
| base::SplitString(version, '.', &version_parts); |
| CHECK(version_parts.size() == 4 && |
| base::StringToInt(version_parts[2], &build_no)) |
| << "Can't rewrite message (type: " << message->type() |
| << ") because unknown server (version: " << version << ")"; |
| CHECK_EQ(static_cast<uint32>(AutomationMsg_SendJSONRequest::ID), |
| message->type()); |
| int type = AutomationMsg_SendJSONRequest::ID; |
| // These old message types are determined by inspecting the line number |
| // of the SendJSONRequest message in older versions of |
| // automation_messages_internal.h. |
| if (build_no < kNewAutomationVersion) |
| type = 1301; |
| else if (build_no < kNewJSONInterfaceVersion) |
| type = 863; |
| IPC::Message* new_message = new MessageWithAlternateType(*message, type); |
| delete message; |
| return new_message; |
| } |
| |
| BackwardsCompatAutomationMessageFilter:: |
| ~BackwardsCompatAutomationMessageFilter() { |
| } |
| |
| void AddBackwardsCompatFilter(AutomationProxy* proxy) { |
| // The filter is ref-counted in AddFilter. |
| BackwardsCompatAutomationMessageFilter* filter = |
| new BackwardsCompatAutomationMessageFilter(proxy); |
| proxy->channel()->AddFilter(filter); |
| proxy->channel()->set_outgoing_message_filter(filter); |
| } |
| |
| class WebDriverAnonymousProxyLauncher : public AnonymousProxyLauncher { |
| public: |
| WebDriverAnonymousProxyLauncher() |
| : AnonymousProxyLauncher(false) {} |
| virtual ~WebDriverAnonymousProxyLauncher() {} |
| |
| virtual AutomationProxy* CreateAutomationProxy( |
| base::TimeDelta execution_timeout) OVERRIDE { |
| AutomationProxy* proxy = |
| AnonymousProxyLauncher::CreateAutomationProxy(execution_timeout); |
| AddBackwardsCompatFilter(proxy); |
| return proxy; |
| } |
| }; |
| |
| class WebDriverNamedProxyLauncher : public NamedProxyLauncher { |
| public: |
| WebDriverNamedProxyLauncher(const std::string& channel_id, |
| bool launch_browser) |
| : NamedProxyLauncher(channel_id, launch_browser, false) {} |
| virtual ~WebDriverNamedProxyLauncher() {} |
| |
| virtual AutomationProxy* CreateAutomationProxy( |
| base::TimeDelta execution_timeout) OVERRIDE { |
| AutomationProxy* proxy = |
| NamedProxyLauncher::CreateAutomationProxy(execution_timeout); |
| // We can only add the filter here if the browser has not already been |
| // started. Otherwise the filter is not guaranteed to receive the |
| // first message. The only occasion where we don't launch the browser is |
| // in PyAuto, in which case the backwards compat filter isn't needed |
| // anyways because ChromeDriver and Chrome are at the same version there. |
| if (launch_browser_) |
| AddBackwardsCompatFilter(proxy); |
| return proxy; |
| } |
| }; |
| |
| } // namespace |
| |
| namespace webdriver { |
| |
| Automation::BrowserOptions::BrowserOptions() |
| : command(CommandLine::NO_PROGRAM), |
| detach_process(false), |
| ignore_certificate_errors(false) {} |
| |
| Automation::BrowserOptions::~BrowserOptions() {} |
| |
| Automation::Automation(const Logger& logger) |
| : logger_(logger), |
| build_no_(0) {} |
| |
| Automation::~Automation() {} |
| |
| void Automation::Init( |
| const BrowserOptions& options, |
| int* build_no, |
| Error** error) { |
| // Prepare Chrome's command line. |
| CommandLine command(CommandLine::NO_PROGRAM); |
| if (CommandLine::ForCurrentProcess()->HasSwitch("no-sandbox")) { |
| command.AppendSwitch(switches::kNoSandbox); |
| } |
| |
| const char* excludable_switches[] = { |
| switches::kDisableHangMonitor, |
| switches::kDisablePromptOnRepost, |
| switches::kDomAutomationController, |
| switches::kFullMemoryCrashReport, |
| switches::kNoDefaultBrowserCheck, |
| switches::kNoFirstRun, |
| switches::kDisableBackgroundNetworking, |
| switches::kDisableSync, |
| switches::kDisableTranslate, |
| switches::kDisableWebResources, |
| switches::kSbDisableAutoUpdate, |
| switches::kSbDisableDownloadProtection, |
| switches::kDisableClientSidePhishingDetection, |
| switches::kDisableComponentUpdate, |
| switches::kDisableDefaultApps |
| }; |
| std::vector<std::string> switches(excludable_switches, |
| excludable_switches + arraysize(excludable_switches)); |
| for (size_t i = 0; i < switches.size(); ++i) { |
| const std::string& switch_name = switches[i]; |
| if (options.exclude_switches.find(switch_name) == |
| options.exclude_switches.end()) { |
| command.AppendSwitch(switch_name); |
| } |
| } |
| |
| command.AppendSwitch(switches::kEnableLogging); |
| command.AppendSwitchASCII(switches::kLoggingLevel, "1"); |
| |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| command.AppendSwitchASCII(switches::kPasswordStore, "basic"); |
| #endif |
| #if defined(OS_MACOSX) |
| command.AppendSwitch(switches::kUseMockKeychain); |
| #endif |
| if (options.detach_process) |
| command.AppendSwitch(switches::kAutomationReinitializeOnChannelError); |
| if (options.ignore_certificate_errors) |
| command.AppendSwitch(switches::kIgnoreCertificateErrors); |
| if (options.user_data_dir.empty()) |
| command.AppendArg(content::kAboutBlankURL); |
| |
| command.AppendArguments(options.command, true /* include_program */); |
| |
| // Find the Chrome binary. |
| if (command.GetProgram().empty()) { |
| base::FilePath browser_exe; |
| if (!GetDefaultChromeExe(&browser_exe)) { |
| *error = new Error(kUnknownError, "Could not find default Chrome binary"); |
| return; |
| } |
| command.SetProgram(browser_exe); |
| } |
| if (!base::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()); |
| |
| // 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) |
| base::FilePath temp_file; |
| if (!file_util::CreateTemporaryFile(&temp_file) || |
| !base::DeleteFile(temp_file, false /* recursive */)) { |
| *error = new Error(kUnknownError, "Could not create temporary filename"); |
| return; |
| } |
| channel_id = temp_file.value(); |
| #endif |
| } |
| if (channel_id.empty()) { |
| std::string command_line_str; |
| #if defined(OS_WIN) |
| command_line_str = WideToUTF8(command.GetCommandLineString()); |
| #elif defined(OS_POSIX) |
| command_line_str = command.GetCommandLineString(); |
| #endif |
| logger_.Log(kInfoLogLevel, "Launching chrome: " + command_line_str); |
| launcher_.reset(new WebDriverAnonymousProxyLauncher()); |
| } else { |
| logger_.Log(kInfoLogLevel, "Using named testing interface"); |
| launcher_.reset(new WebDriverNamedProxyLauncher( |
| channel_id, launch_browser)); |
| } |
| 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)) { |
| logger_.Log(kSevereLogLevel, "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( |
| base::TimeDelta::FromMilliseconds(base::kNoTimeout)); |
| logger_.Log(kInfoLogLevel, "Connected to Chrome successfully. Version: " + |
| automation()->server_version()); |
| |
| *error = DetermineBuildNumber(); |
| if (*error) |
| return; |
| *build_no = build_no_; |
| |
| // Check the version of Chrome is compatible with this ChromeDriver. |
| chrome_details += ", version (" + automation()->server_version() + ")"; |
| int version = 0; |
| automation::Error auto_error; |
| if (!SendGetChromeDriverAutomationVersion( |
| automation(), &version, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| 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) { |
| #if defined(OS_MACOSX) |
| // There's currently no way to shutdown gracefully with mac chrome. |
| // An alert could be open or (open before shutdown is started) and stop |
| // the whole process. Close any current dialog, try to kill gently, and |
| // if all else fails, kill it hard. |
| Error* error = NULL; |
| AcceptOrDismissAppModalDialog(true /* accept */, &error); |
| scoped_ptr<Error> scoped_error(error); |
| |
| kill(launcher_->process(), SIGTERM); |
| #else |
| automation()->Disconnect(); |
| #endif |
| int exit_code = -1; |
| if (!launcher_->WaitForBrowserProcessToQuit( |
| base::TimeDelta::FromSeconds(10), &exit_code)) { |
| logger_.Log(kWarningLogLevel, "Chrome still running, terminating..."); |
| ChromeProcessList process_pids(GetRunningChromeProcesses( |
| launcher_->process_id())); |
| TerminateAllChromeProcesses(process_pids); |
| } |
| base::CloseProcessHandle(launcher_->process()); |
| logger_.Log(kInfoLogLevel, "Chrome shutdown"); |
| } |
| } |
| |
| void Automation::ExecuteScript(const WebViewId& view_id, |
| const FramePath& frame_path, |
| const std::string& script, |
| std::string* result, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| scoped_ptr<Value> value; |
| if (!SendExecuteJavascriptJSONRequest(automation(), view_locator, |
| frame_path.value(), script, |
| &value, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| return; |
| } |
| if (!value->GetAsString(result)) |
| *error = new Error(kUnknownError, "Execute script did not return string"); |
| } |
| |
| void Automation::MouseMoveDeprecated( |
| const WebViewId& view_id, |
| const Point& p, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendMouseMoveJSONRequestDeprecated( |
| automation(), view_locator, p.rounded_x(), p.rounded_y(), |
| &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::MouseClickDeprecated( |
| const WebViewId& view_id, |
| const Point& p, |
| automation::MouseButton button, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendMouseClickJSONRequestDeprecated( |
| automation(), view_locator, button, p.rounded_x(), |
| p.rounded_y(), &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::MouseDragDeprecated( |
| const WebViewId& view_id, |
| const Point& start, |
| const Point& end, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendMouseDragJSONRequestDeprecated( |
| automation(), view_locator, start.rounded_x(), start.rounded_y(), |
| end.rounded_x(), end.rounded_y(), &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::MouseButtonUpDeprecated( |
| const WebViewId& view_id, |
| const Point& p, |
| Error** error) { |
| *error = CheckAdvancedInteractionsSupported(); |
| if (*error) |
| return; |
| |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendMouseButtonUpJSONRequestDeprecated( |
| automation(), view_locator, p.rounded_x(), p.rounded_y(), |
| &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::MouseButtonDownDeprecated( |
| const WebViewId& view_id, |
| const Point& p, |
| Error** error) { |
| *error = CheckAdvancedInteractionsSupported(); |
| if (*error) |
| return; |
| |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendMouseButtonDownJSONRequestDeprecated( |
| automation(), view_locator, p.rounded_x(), p.rounded_y(), |
| &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::MouseDoubleClickDeprecated( |
| const WebViewId& view_id, |
| const Point& p, |
| Error** error) { |
| *error = CheckAdvancedInteractionsSupported(); |
| if (*error) |
| return; |
| |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendMouseDoubleClickJSONRequestDeprecated( |
| automation(), view_locator, p.rounded_x(), p.rounded_y(), |
| &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::DragAndDropFilePaths( |
| const WebViewId& view_id, const Point& location, |
| const std::vector<base::FilePath::StringType>& paths, Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) { |
| return; |
| } |
| |
| automation::Error auto_error; |
| if (!SendDragAndDropFilePathsJSONRequest( |
| automation(), view_locator, location.rounded_x(), |
| location.rounded_y(), paths, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::SendWebKeyEvent(const WebViewId& view_id, |
| const WebKeyEvent& key_event, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendWebKeyEventJSONRequest( |
| automation(), view_locator, key_event, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::SendWebMouseEvent(const WebViewId& view_id, |
| const WebMouseEvent& event, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendWebMouseEventJSONRequestDeprecated( |
| automation(), view_locator, event, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::CaptureEntirePageAsPNG(const WebViewId& view_id, |
| const base::FilePath& path, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendCaptureEntirePageJSONRequestDeprecated( |
| automation(), view_locator, path, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| #if !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS)) |
| void Automation::HeapProfilerDump(const WebViewId& view_id, |
| const std::string& reason, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendHeapProfilerDumpJSONRequestDeprecated( |
| automation(), view_locator, reason, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| #endif // !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS)) |
| |
| void Automation::NavigateToURL(const WebViewId& view_id, |
| const std::string& url, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| AutomationMsg_NavigationResponseValues navigate_response; |
| automation::Error auto_error; |
| if (!SendNavigateToURLJSONRequest(automation(), view_locator, |
| url, 1, &navigate_response, |
| &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| 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(const WebViewId& view_id, |
| const std::string& url, |
| Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!view_id.old_style()) { |
| AutomationMsg_NavigationResponseValues navigate_response; |
| if (!SendNavigateToURLJSONRequest(automation(), view_locator, url, |
| 0, &navigate_response, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| return; |
| } |
| } else { |
| scoped_refptr<BrowserProxy> browser = |
| automation()->GetBrowserWindow(view_locator.browser_index()); |
| if (!browser.get()) { |
| *error = new Error(kUnknownError, "Couldn't obtain browser proxy"); |
| return; |
| } |
| scoped_refptr<TabProxy> tab = browser->GetTab(view_locator.tab_index()); |
| if (!tab.get()) { |
| *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(const WebViewId& view_id, Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendGoForwardJSONRequest( |
| automation(), view_locator, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::GoBack(const WebViewId& view_id, Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendGoBackJSONRequest(automation(), view_locator, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::Reload(const WebViewId& view_id, Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendReloadJSONRequest(automation(), view_locator, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::GetCookies(const std::string& url, |
| scoped_ptr<ListValue>* cookies, |
| Error** error) { |
| automation::Error auto_error; |
| if (!SendGetCookiesJSONRequest(automation(), url, cookies, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::DeleteCookie(const std::string& url, |
| const std::string& cookie_name, |
| Error** error) { |
| automation::Error auto_error; |
| if (!SendDeleteCookieJSONRequest( |
| automation(), url, cookie_name, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::SetCookie(const std::string& url, |
| DictionaryValue* cookie_dict, |
| Error** error) { |
| automation::Error auto_error; |
| if (!SendSetCookieJSONRequest(automation(), url, cookie_dict, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::GetViews(std::vector<WebViewInfo>* views, |
| Error** error) { |
| automation::Error auto_error; |
| if (build_no_ >= 963) { |
| if (!SendGetWebViewsJSONRequest(automation(), views, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } else { |
| if (!SendGetTabIdsJSONRequest(automation(), views, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::DoesViewExist( |
| const WebViewId& view_id, bool* does_exist, Error** error) { |
| automation::Error auto_error; |
| if (view_id.old_style()) { |
| if (!SendIsTabIdValidJSONRequest( |
| automation(), view_id, does_exist, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } else { |
| if (!SendDoesAutomationObjectExistJSONRequest( |
| automation(), view_id, does_exist, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::CloseView(const WebViewId& view_id, Error** error) { |
| WebViewLocator view_locator; |
| *error = ConvertViewIdToLocator(view_id, &view_locator); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendCloseViewJSONRequest(automation(), view_locator, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::SetViewBounds(const WebViewId& view_id, |
| const Rect& bounds, |
| Error** error) { |
| automation::Error auto_error; |
| if (!SendSetViewBoundsJSONRequest( |
| automation(), view_id, bounds.x(), bounds.y(), |
| bounds.width(), bounds.height(), &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::MaximizeView(const WebViewId& view_id, Error** error) { |
| *error = CheckMaximizeSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendMaximizeJSONRequest( |
| automation(), view_id, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::GetAppModalDialogMessage(std::string* message, Error** error) { |
| *error = CheckAlertsSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendGetAppModalDialogMessageJSONRequest( |
| automation(), message, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::AcceptOrDismissAppModalDialog(bool accept, Error** error) { |
| *error = CheckAlertsSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendAcceptOrDismissAppModalDialogJSONRequest( |
| automation(), accept, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::AcceptPromptAppModalDialog(const std::string& prompt_text, |
| Error** error) { |
| *error = CheckAlertsSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendAcceptPromptAppModalDialogJSONRequest( |
| automation(), prompt_text, &auto_error)) { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| void Automation::GetBrowserVersion(std::string* version) { |
| *version = automation()->server_version(); |
| } |
| |
| void Automation::GetChromeDriverAutomationVersion(int* version, Error** error) { |
| automation::Error auto_error; |
| if (!SendGetChromeDriverAutomationVersion(automation(), version, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::WaitForAllViewsToStopLoading(Error** error) { |
| automation::Error auto_error; |
| if (!SendWaitForAllViewsToStopLoadingJSONRequestDeprecated( |
| automation(), &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::InstallExtension( |
| const base::FilePath& path, std::string* extension_id, Error** error) { |
| *error = CheckNewExtensionInterfaceSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendInstallExtensionJSONRequest( |
| automation(), path, false /* with_ui */, extension_id, |
| &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::GetExtensionsInfo( |
| base::ListValue* extensions_list, Error** error) { |
| *error = CheckNewExtensionInterfaceSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendGetExtensionsInfoJSONRequest( |
| automation(), extensions_list, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::IsPageActionVisible( |
| const WebViewId& tab_id, |
| const std::string& extension_id, |
| bool* is_visible, |
| Error** error) { |
| *error = CheckNewExtensionInterfaceSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendIsPageActionVisibleJSONRequest( |
| automation(), tab_id, extension_id, is_visible, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::SetExtensionState( |
| const std::string& extension_id, |
| bool enable, |
| Error** error) { |
| *error = CheckNewExtensionInterfaceSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendSetExtensionStateJSONRequest( |
| automation(), extension_id, enable /* enable */, |
| false /* allow_in_incognito */, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::ClickExtensionButton( |
| const std::string& extension_id, |
| bool browser_action, |
| Error** error) { |
| automation::Error auto_error; |
| if (!SendClickExtensionButtonJSONRequest( |
| automation(), extension_id, browser_action, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::UninstallExtension( |
| const std::string& extension_id, Error** error) { |
| *error = CheckNewExtensionInterfaceSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (!SendUninstallExtensionJSONRequest( |
| automation(), extension_id, &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } |
| |
| void Automation::SetLocalStatePreference(const std::string& pref, |
| base::Value* value, |
| Error** error) { |
| scoped_ptr<Value> scoped_value(value); |
| // In version 927, SetLocalStatePrefs was changed from taking a browser |
| // handle to a browser index. |
| if (build_no_ >= 927) { |
| automation::Error auto_error; |
| if (!SendSetLocalStatePreferenceJSONRequest( |
| automation(), pref, scoped_value.release(), &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } else { |
| std::string request, response; |
| DictionaryValue request_dict; |
| request_dict.SetString("command", "SetLocalStatePrefs"); |
| request_dict.SetString("path", pref); |
| request_dict.Set("value", scoped_value.release()); |
| base::JSONWriter::Write(&request_dict, &request); |
| if (!automation()->GetBrowserWindow(0)->SendJSONRequest( |
| request, -1, &response)) { |
| *error = new Error(kUnknownError, base::StringPrintf( |
| "Failed to set local state pref '%s'", pref.c_str())); |
| } |
| } |
| } |
| |
| void Automation::SetPreference(const std::string& pref, |
| base::Value* value, |
| Error** error) { |
| scoped_ptr<Value> scoped_value(value); |
| // Chrome 17 is on the 963 branch. The first released 18 build should have |
| // the new SetPrefs method which uses a browser index instead of handle. |
| if (build_no_ >= 964) { |
| automation::Error auto_error; |
| if (!SendSetPreferenceJSONRequest( |
| automation(), pref, scoped_value.release(), &auto_error)) |
| *error = Error::FromAutomationError(auto_error); |
| } else { |
| std::string request, response; |
| DictionaryValue request_dict; |
| request_dict.SetString("command", "SetPrefs"); |
| request_dict.SetInteger("windex", 0); |
| request_dict.SetString("path", pref); |
| request_dict.Set("value", scoped_value.release()); |
| base::JSONWriter::Write(&request_dict, &request); |
| if (!automation()->GetBrowserWindow(0)->SendJSONRequest( |
| request, -1, &response)) { |
| *error = new Error(kUnknownError, base::StringPrintf( |
| "Failed to set pref '%s'", pref.c_str())); |
| } |
| } |
| } |
| |
| void Automation::GetGeolocation(scoped_ptr<DictionaryValue>* geolocation, |
| Error** error) { |
| *error = CheckGeolocationSupported(); |
| if (*error) |
| return; |
| |
| if (geolocation_.get()) { |
| geolocation->reset(geolocation_->DeepCopy()); |
| } else { |
| *error = new Error(kUnknownError, |
| "Location must be set before it can be retrieved"); |
| } |
| } |
| |
| void Automation::OverrideGeolocation(const DictionaryValue* geolocation, |
| Error** error) { |
| *error = CheckGeolocationSupported(); |
| if (*error) |
| return; |
| |
| automation::Error auto_error; |
| if (SendOverrideGeolocationJSONRequest( |
| automation(), geolocation, &auto_error)) { |
| geolocation_.reset(geolocation->DeepCopy()); |
| } else { |
| *error = Error::FromAutomationError(auto_error); |
| } |
| } |
| |
| AutomationProxy* Automation::automation() const { |
| return launcher_->automation(); |
| } |
| |
| Error* Automation::ConvertViewIdToLocator( |
| const WebViewId& view_id, WebViewLocator* view_locator) { |
| if (view_id.old_style()) { |
| int browser_index, tab_index; |
| automation::Error auto_error; |
| if (!SendGetIndicesFromTabIdJSONRequest( |
| automation(), view_id.tab_id(), &browser_index, &tab_index, |
| &auto_error)) |
| return Error::FromAutomationError(auto_error); |
| *view_locator = WebViewLocator::ForIndexPair(browser_index, tab_index); |
| } else { |
| *view_locator = WebViewLocator::ForViewId(view_id.GetId()); |
| } |
| return NULL; |
| } |
| |
| Error* Automation::DetermineBuildNumber() { |
| 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); |
| } |
| if (!base::StringToInt(split_version[2], &build_no_)) { |
| return new Error( |
| kUnknownError, "Browser version has unrecognized format: " + version); |
| } |
| return NULL; |
| } |
| |
| Error* Automation::CheckVersion(int min_required_build_no, |
| const std::string& error_msg) { |
| if (build_no_ < min_required_build_no) |
| return new Error(kUnknownError, error_msg); |
| return NULL; |
| } |
| |
| Error* Automation::CheckAlertsSupported() { |
| return CheckVersion( |
| 768, "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, message); |
| } |
| |
| Error* Automation::CheckNewExtensionInterfaceSupported() { |
| const char* message = |
| "Extension interface is not supported for this version of Chrome"; |
| return CheckVersion(947, message); |
| } |
| |
| Error* Automation::CheckGeolocationSupported() { |
| const char* message = |
| "Geolocation automation interface is not supported for this version of " |
| "Chrome."; |
| return CheckVersion(1119, message); |
| } |
| |
| Error* Automation::CheckMaximizeSupported() { |
| const char* message = |
| "Maximize automation interface is not supported for this version of " |
| "Chrome."; |
| return CheckVersion(1160, message); |
| } |
| |
| } // namespace webdriver |