summaryrefslogtreecommitdiffstats
path: root/chrome/test/webdriver/session.cc
diff options
context:
space:
mode:
authorjmikhail@google.com <jmikhail@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-19 00:57:51 +0000
committerjmikhail@google.com <jmikhail@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-19 00:57:51 +0000
commit35a369575ad34881580ed0724f8c0304f7342c20 (patch)
treedba8ec8d9ae4679a6dd571a2c65c7328e0e0a465 /chrome/test/webdriver/session.cc
parentbc073ca8358c8edd5b21f61d5194120a838e3530 (diff)
downloadchromium_src-35a369575ad34881580ed0724f8c0304f7342c20.zip
chromium_src-35a369575ad34881580ed0724f8c0304f7342c20.tar.gz
chromium_src-35a369575ad34881580ed0724f8c0304f7342c20.tar.bz2
Base implementation of WebDriver for Chrome.
WebDriver is a tool for automating testing web applications, and in particular to verify that they work as expected. It aims to provide a friendly API that's easy to explore and understand, which will help make your tests easier to read and maintain. It's not tied to any particular test framework, so it can be used equally well with JUnit, TestNG or from a plain old "main" method. This checkin includes all that it necessary to implement the JSON over HTTP protocol for WebDriver along with the /session and /session/sessionID URLs. Each URL is added under the webdriver/command directory. To run simply run execute webdriver command, on linux you may need to add the path to chrome to your enviroment settings. A port can be specified with the --port option, by default webdriver will listen in on port 8080. Note: A total refactor of my original code was done by Jason Leyba (jleyba). All of his changes are included in this checkin For further reference on the WebDriver remote protocol see: http://code.google.com/p/selenium/wiki/JsonWireProtocol BUG=none TEST=Start webdriver then run the file webdriver_tests.py Review URL: http://codereview.chromium.org/3064012 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56626 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test/webdriver/session.cc')
-rw-r--r--chrome/test/webdriver/session.cc288
1 files changed, 288 insertions, 0 deletions
diff --git a/chrome/test/webdriver/session.cc b/chrome/test/webdriver/session.cc
new file mode 100644
index 0000000..2aff00b
--- /dev/null
+++ b/chrome/test/webdriver/session.cc
@@ -0,0 +1,288 @@
+// Copyright (c) 2010 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/session_manager.h"
+
+#ifdef OS_POSIX
+#include <unistd.h>
+#endif
+#ifdef OS_WIN
+#include <windows.h>
+#include <shellapi.h>
+#endif
+
+#include <stdlib.h>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/process.h"
+#include "base/process_util.h"
+#include "base/string_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+
+#include "chrome/app/chrome_dll_resource.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/webdriver/utility_functions.h"
+
+#include "third_party/webdriver/atoms.h"
+
+namespace webdriver {
+
+#ifdef OS_WIN
+namespace {
+std::string GetTempPath() {
+ DWORD result = ::GetTempPath(0, L"");
+ if (result == 0)
+ LOG(ERROR) << "Could not get system temp path" << std::endl;
+
+ std::vector<TCHAR> tempPath(result + 1);
+ result = ::GetTempPath(static_cast<DWORD>(tempPath.size()), &tempPath[0]);
+ if ((result == 0) || (result >= tempPath.size())) {
+ LOG(ERROR) << "Could not get system temp path" << std::endl;
+ NOTREACHED();
+ }
+ return std::string(tempPath.begin(),
+ tempPath.begin() + static_cast<std::size_t>(result));
+}
+} // namespace
+#endif
+
+Session::Session(const std::string& id, AutomationProxy* proxy)
+ : id_(id), proxy_(proxy), process_(base::kNullProcessHandle),
+ window_num_(0), implicit_wait_(0), current_frame_xpath_(L"") {
+}
+
+bool Session::Init(base::ProcessHandle process_handle) {
+ CHECK(process_.handle() == base::kNullProcessHandle)
+ << "Session has already been initialized";
+ process_.set_handle(process_handle);
+
+ if (!proxy_->WaitForInitialLoads()) {
+ LOG(WARNING) << "Failed to wait for initial loads" << std::endl;
+ return false;
+ }
+
+ if (!WaitForLaunch()) {
+ return false;
+ }
+
+ return LoadProxies();
+}
+
+scoped_refptr<WindowProxy> Session::GetWindow() {
+ return ActivateTab() ? browser_->GetWindow() : NULL;
+}
+
+scoped_refptr<TabProxy> Session::ActivateTab() {
+ int tab_index;
+ if (!tab_->GetTabIndex(&tab_index) ||
+ !browser_->ActivateTab(tab_index)) {
+ LOG(ERROR) << "Failed to session tab";
+ return NULL;
+ }
+ return tab_;
+}
+
+bool Session::WaitForLaunch() {
+ AutomationLaunchResult result = proxy_->WaitForAppLaunch();
+ if (result == AUTOMATION_SUCCESS) {
+ LOG(INFO) << "Automation setup is a success" << std::endl;
+ } else if (result == AUTOMATION_TIMEOUT) {
+ LOG(WARNING) << "Timeout, automation setup failed" << std::endl;
+ return false;
+ } else {
+ LOG(WARNING) << "Failure in automation setup" << std::endl;
+ return false;
+ }
+
+ LOG(INFO) << proxy_->channel_id() << std::endl;
+ if (!proxy_->OpenNewBrowserWindow(Browser::TYPE_NORMAL, true)) {
+ LOG(WARNING) << "Failed to open a new browser window" << std::endl;
+ return false;
+ }
+ return true;
+}
+
+bool Session::LoadProxies() {
+ scoped_refptr<BrowserProxy> browser = proxy_->GetBrowserWindow(0);
+ if (!browser.get()) {
+ LOG(WARNING) << "Failed to get browser window.";
+ return false;
+ }
+
+ scoped_refptr<TabProxy> tab = browser->GetActiveTab();
+ if (!tab->NavigateToURL(GURL("about://config/"))) {
+ LOG(WARNING) << "Could not navigate to about://config/" << std::endl;
+ return false;
+ }
+
+ SetBrowserAndTab(0, browser, tab);
+ return true;
+}
+
+void Session::SetBrowserAndTab(const int window_num,
+ const scoped_refptr<BrowserProxy>& browser,
+ const scoped_refptr<TabProxy>& tab) {
+ window_num_ = window_num;
+ browser_ = browser;
+ tab_ = tab;
+ current_frame_xpath_ = L"";
+
+ int tab_num;
+ LOG_IF(WARNING, !tab->GetTabIndex(&tab_num) || !browser->ActivateTab(tab_num))
+ << "Failed to activate tab";
+}
+
+bool Session::CreateTemporaryProfileDirectory() {
+ memset(tmp_profile_dir_, 0, sizeof tmp_profile_dir_);
+#ifdef OS_POSIX
+ strncat(tmp_profile_dir_, "/tmp/webdriverXXXXXX", sizeof tmp_profile_dir_);
+ if (mkdtemp(tmp_profile_dir_) == NULL) {
+ LOG(ERROR) << "mkdtemp failed";
+ return false;
+ }
+#elif OS_WIN
+ DWORD ret;
+ ProfileDir temp_dir;
+
+ ret = GetTempPathA(sizeof temp_dir, temp_dir);
+ if (ret == 0 || ret > sizeof temp_dir) {
+ LOG(ERROR) << "Could not find the temp directory" << std::endl;
+ return false;
+ }
+
+ ret = GetTempFileNameA(temp_dir, // directory for tmp files
+ "webdriver", // temp file name prefix
+ static_cast<int>(time(NULL)) % 65535 + 1,
+ tmp_profile_dir_); // buffer for name
+
+ if (ret ==0) {
+ LOG(ERROR) << "Could not generate temp directory name" << std::endl;
+ return false;
+ }
+
+ if (!CreateDirectoryA(tmp_profile_dir_, NULL)) {
+ DWORD dw = GetLastError();
+ LOG(ERROR) << "Error code: " << dw << std::endl;
+ return false;
+ }
+#endif
+ LOG(INFO) << "Using temporary profile directory: " << tmp_profile_dir_;
+ return true;
+}
+
+#ifdef OS_WIN
+bool DeleteDirectory(const char* directory) {
+ SHFILEOPSTRUCT fileop;
+ size_t convertedChars;
+ int len = strlen(directory);
+ wchar_t *pszFrom = new wchar_t[len+2];
+ memset(&fileop, 0, sizeof fileop);
+ memset(pszFrom, 0, sizeof pszFrom);
+
+ mbstowcs_s(&convertedChars, pszFrom, len+2, directory, len);
+ fileop.wFunc = FO_DELETE; // delete operation
+ // source file name as double null terminated string
+ fileop.pFrom = (LPCWSTR) pszFrom;
+ fileop.fFlags = FOF_NOCONFIRMATION|FOF_SILENT; // do not prompt the user
+ fileop.fAnyOperationsAborted = FALSE;
+
+ int ret = SHFileOperation(&fileop);
+ delete pszFrom;
+ return (ret == 0);
+}
+#endif
+
+void Session::Terminate() {
+ if (!proxy_->SetFilteredInet(false)) {
+ LOG(ERROR) << "Error in closing down session" << std::endl;
+ }
+
+#ifdef OS_WIN
+ if (!DeleteDirectory(tmp_profile_dir_)) {
+ LOG(ERROR) << "Could not clean up temp directory: "
+ << tmp_profile_dir_ << std::endl;
+ }
+#else
+ if (rmdir(tmp_profile_dir_)) {
+ LOG(ERROR) << "Could not clean up temp directory: "
+ << tmp_profile_dir_ << std::endl;
+ }
+#endif
+ process_.Terminate(EXIT_SUCCESS);
+}
+
+ErrorCode Session::ExecuteScript(const std::wstring& script,
+ const ListValue* const args,
+ Value** value) {
+ std::string args_as_json;
+ base::JSONWriter::Write(static_cast<const Value* const>(args),
+ /*pretty_print=*/false,
+ &args_as_json);
+
+ std::wstring jscript = L"window.domAutomationController.send(";
+ jscript.append(L"(function(){")
+ // Every injected script is fed through the executeScript atom. This atom
+ // will catch any errors that are thrown and convert them to the
+ // appropriate JSON structure.
+ .append(build_atom(EXECUTE_SCRIPT, sizeof EXECUTE_SCRIPT))
+ .append(L"var result = executeScript(function(){")
+ .append(script)
+ .append(L"},")
+ .append(ASCIIToWide(args_as_json))
+ .append(L");return JSON.stringify(result);})());");
+
+ // Should we also log the script that's being executed? It could be several KB
+ // in size and will add lots of noise to the logs.
+ LOG(INFO) << "Executing script in frame: " << current_frame_xpath_;
+
+ std::wstring result;
+ scoped_refptr<TabProxy> tab = ActivateTab();
+ if (!tab->ExecuteAndExtractString(current_frame_xpath_, jscript, &result)) {
+ *value = Value::CreateStringValue(
+ "Unknown internal script execution failure");
+ return kUnknownError;
+ }
+
+ LOG(INFO) << "...script result: " << result;
+ std::string temp = WideToASCII(result);
+ scoped_ptr<Value> r(base::JSONReader::ReadAndReturnError(
+ temp, true, NULL, NULL));
+ if (!r.get()) {
+ *value = Value::CreateStringValue(
+ "Internal script execution error: failed to parse script result");
+ return kUnknownError;
+ }
+
+ if (r->GetType() != Value::TYPE_DICTIONARY) {
+ std::ostringstream stream;
+ stream << "Internal script execution error: script result must be a "
+ << print_valuetype(Value::TYPE_DICTIONARY)
+ << ", but was "
+ << print_valuetype(r->GetType()) << ": " << result;
+ *value = Value::CreateStringValue(stream.str());
+ return kUnknownError;
+ }
+
+ DictionaryValue* result_dict = static_cast<DictionaryValue*>(r.get());
+
+ Value* tmp;
+ if (result_dict->Get("value", &tmp)) {
+ // result_dict owns the returned value, so we need to make a copy.
+ *value = tmp->DeepCopy();
+ } else {
+ // "value" was not defined in the returned dictionary, set to null.
+ *value = Value::CreateNullValue();
+ }
+
+ int status;
+ if (!result_dict->GetInteger("status", &status)) {
+ NOTREACHED() << "...script did not return a status flag.";
+ }
+ return static_cast<ErrorCode>(status);
+}
+} // namespace webdriver