blob: d60c77a09972065e28a99eadc531fe2908b64af4 [file] [log] [blame]
// 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_session.h"
#include <sstream>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/automation/automation_json_requests.h"
#include "chrome/test/automation/value_conversion_util.h"
#include "chrome/test/webdriver/webdriver_capabilities_parser.h"
#include "chrome/test/webdriver/webdriver_error.h"
#include "chrome/test/webdriver/webdriver_key_converter.h"
#include "chrome/test/webdriver/webdriver_logging.h"
#include "chrome/test/webdriver/webdriver_session_manager.h"
#include "chrome/test/webdriver/webdriver_util.h"
#include "third_party/webdriver/atoms.h"
using automation::kLeftButton;
using automation::kMouseDown;
using automation::kMouseMove;
using automation::kMouseUp;
using automation::kNoButton;
namespace webdriver {
namespace {
// This is the minimum version of chrome that supports the new mouse API.
const int kNewMouseAPIMinVersion = 1002;
}
FrameId::FrameId() {}
FrameId::FrameId(const WebViewId& view_id, const FramePath& frame_path)
: view_id(view_id),
frame_path(frame_path) {
}
Session::Session()
: session_log_(new InMemoryLog()),
logger_(kAllLogLevel),
id_(GenerateRandomID()),
current_target_(FrameId(WebViewId(), FramePath())),
thread_(id_.c_str()),
async_script_timeout_(0),
implicit_wait_(0),
has_alert_prompt_text_(false),
sticky_modifiers_(0),
build_no_(0) {
SessionManager::GetInstance()->Add(this);
logger_.AddHandler(session_log_.get());
if (FileLog::Get())
logger_.AddHandler(FileLog::Get());
}
Session::~Session() {
SessionManager::GetInstance()->Remove(id_);
}
Error* Session::Init(const base::DictionaryValue* capabilities_dict) {
if (!thread_.Start()) {
delete this;
return new Error(kUnknownError, "Cannot start session thread");
}
if (!temp_dir_.CreateUniqueTempDir()) {
delete this;
return new Error(
kUnknownError, "Unable to create temp directory for unpacking");
}
logger_.Log(kFineLogLevel,
"Initializing session with capabilities " +
JsonStringifyForDisplay(capabilities_dict));
CapabilitiesParser parser(
capabilities_dict, temp_dir_.path(), logger_, &capabilities_);
Error* error = parser.Parse();
if (error) {
delete this;
return error;
}
logger_.set_min_log_level(capabilities_.log_levels[LogType::kDriver]);
Automation::BrowserOptions browser_options;
browser_options.command = capabilities_.command;
browser_options.channel_id = capabilities_.channel;
browser_options.detach_process = capabilities_.detach;
browser_options.user_data_dir = capabilities_.profile;
browser_options.exclude_switches = capabilities_.exclude_switches;
if (!capabilities_.no_website_testing_defaults) {
browser_options.ignore_certificate_errors = true;
}
RunSessionTask(base::Bind(
&Session::InitOnSessionThread,
base::Unretained(this),
browser_options,
&build_no_,
&error));
if (!error)
error = PostBrowserStartInit();
if (error)
Terminate();
return error;
}
Error* Session::BeforeExecuteCommand() {
Error* error = AfterExecuteCommand();
if (!error) {
scoped_ptr<Error> switch_error(SwitchToTopFrameIfCurrentFrameInvalid());
if (switch_error.get()) {
std::string text;
scoped_ptr<Error> alert_error(GetAlertMessage(&text));
if (alert_error.get()) {
// Only return a frame checking error if a modal dialog is not present.
// TODO(kkania): This is ugly. Fix.
return switch_error.release();
}
}
}
return error;
}
Error* Session::AfterExecuteCommand() {
Error* error = NULL;
if (!capabilities_.load_async) {
error = WaitForAllViewsToStopLoading();
}
return error;
}
void Session::Terminate() {
RunSessionTask(base::Bind(
&Session::TerminateOnSessionThread,
base::Unretained(this)));
delete this;
}
Error* Session::ExecuteScript(const FrameId& frame_id,
const std::string& script,
const base::ListValue* const args,
base::Value** value) {
std::string args_as_json;
base::JSONWriter::Write(static_cast<const base::Value* const>(args),
&args_as_json);
// 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.
std::string jscript = base::StringPrintf(
"window.domAutomationController.send((%s).apply(null,"
"[function(){%s\n},%s,true]));",
atoms::asString(atoms::EXECUTE_SCRIPT).c_str(), script.c_str(),
args_as_json.c_str());
return ExecuteScriptAndParseValue(frame_id, jscript, value);
}
Error* Session::ExecuteScript(const std::string& script,
const base::ListValue* const args,
base::Value** value) {
return ExecuteScript(current_target_, script, args, value);
}
Error* Session::ExecuteScriptAndParse(const FrameId& frame_id,
const std::string& anonymous_func_script,
const std::string& script_name,
const base::ListValue* args,
const ValueParser* parser) {
scoped_ptr<const base::ListValue> scoped_args(args);
scoped_ptr<const ValueParser> scoped_parser(parser);
std::string called_script = base::StringPrintf(
"return (%s).apply(null, arguments);", anonymous_func_script.c_str());
base::Value* unscoped_value = NULL;
Error* error = ExecuteScript(frame_id, called_script, args, &unscoped_value);
if (error) {
error->AddDetails(script_name + " execution failed");
return error;
}
scoped_ptr<base::Value> value(unscoped_value);
std::string error_msg;
if (!parser->Parse(value.get())) {
error_msg = base::StringPrintf("%s returned invalid value: %s",
script_name.c_str(), JsonStringify(value.get()).c_str());
return new Error(kUnknownError, error_msg);
}
return NULL;
}
Error* Session::ExecuteAsyncScript(const FrameId& frame_id,
const std::string& script,
const base::ListValue* const args,
base::Value** value) {
std::string args_as_json;
base::JSONWriter::Write(static_cast<const base::Value* const>(args),
&args_as_json);
int timeout_ms = async_script_timeout();
// 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.
std::string jscript = base::StringPrintf(
"(%s).apply(null, [function(){%s},%s,%d,%s,true]);",
atoms::asString(atoms::EXECUTE_ASYNC_SCRIPT).c_str(),
script.c_str(),
args_as_json.c_str(),
timeout_ms,
"function(result) {window.domAutomationController.send(result);}");
return ExecuteScriptAndParseValue(frame_id, jscript, value);
}
Error* Session::SendKeys(const ElementId& element, const string16& keys) {
bool is_displayed = false;
Error* error = IsElementDisplayed(
current_target_, element, true /* ignore_opacity */, &is_displayed);
if (error)
return error;
if (!is_displayed)
return new Error(kElementNotVisible);
bool is_enabled = false;
error = IsElementEnabled(current_target_, element, &is_enabled);
if (error)
return error;
if (!is_enabled)
return new Error(kInvalidElementState);
// Focus the target element in order to send keys to it.
// First, the currently active element is blurred, if it is different from
// the target element. We do not want to blur an element unnecessarily,
// because this may cause us to lose the current cursor position in the
// element.
// Secondly, we focus the target element.
// Thirdly, if the target element is newly focused and is a text input, we
// set the cursor position at the end.
// Fourthly, we check if the new active element is the target element. If not,
// we throw an error.
// Additional notes:
// - |document.activeElement| is the currently focused element, or body if
// no element is focused
// - Even if |document.hasFocus()| returns true and the active element is
// the body, sometimes we still need to focus the body element for send
// keys to work. Not sure why
// - You cannot focus a descendant of a content editable node
// - V8 throws a TypeError when calling setSelectionRange for a non-text
// input, which still have setSelectionRange defined.
// TODO(jleyba): Update this to use the correct atom.
const char* kFocusScript =
"function(elem) {"
" var doc = elem.ownerDocument || elem;"
" var prevActiveElem = doc.activeElement;"
" if (elem != prevActiveElem && prevActiveElem)"
" prevActiveElem.blur();"
" elem.focus();"
" if (elem != prevActiveElem && elem.value && elem.value.length &&"
" elem.setSelectionRange) {"
" try {"
" elem.setSelectionRange(elem.value.length, elem.value.length);"
" } catch (error) {"
" if (!(error instanceof TypeError)) {"
" throw error;"
" }"
" }"
" }"
" if (elem != doc.activeElement)"
" throw new Error('Failed to send keys because cannot focus element');"
"}";
error = ExecuteScriptAndParse(current_target_,
kFocusScript,
"focusElement",
CreateListValueFrom(element),
CreateDirectValueParser(kSkipParsing));
if (error)
return error;
RunSessionTask(base::Bind(
&Session::SendKeysOnSessionThread,
base::Unretained(this),
keys,
true /* release_modifiers */,
&error));
return error;
}
Error* Session::SendKeys(const string16& keys) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Session::SendKeysOnSessionThread,
base::Unretained(this),
keys,
false /* release_modifiers */,
&error));
return error;
}
Error* Session::DragAndDropFilePaths(
const Point& location,
const std::vector<base::FilePath::StringType>& paths) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::DragAndDropFilePaths,
base::Unretained(automation_.get()),
current_target_.view_id,
location,
paths,
&error));
return error;
}
Error* Session::NavigateToURL(const std::string& url) {
if (!current_target_.view_id.IsTab()) {
return new Error(kUnknownError,
"The current target does not support navigation");
}
Error* error = NULL;
if (capabilities_.load_async) {
RunSessionTask(base::Bind(
&Automation::NavigateToURLAsync,
base::Unretained(automation_.get()),
current_target_.view_id,
url,
&error));
} else {
RunSessionTask(base::Bind(
&Automation::NavigateToURL,
base::Unretained(automation_.get()),
current_target_.view_id,
url,
&error));
}
return error;
}
Error* Session::GoForward() {
if (!current_target_.view_id.IsTab()) {
return new Error(kUnknownError,
"The current target does not support navigation");
}
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::GoForward,
base::Unretained(automation_.get()),
current_target_.view_id,
&error));
return error;
}
Error* Session::GoBack() {
if (!current_target_.view_id.IsTab()) {
return new Error(kUnknownError,
"The current target does not support navigation");
}
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::GoBack,
base::Unretained(automation_.get()),
current_target_.view_id,
&error));
return error;
}
Error* Session::Reload() {
if (!current_target_.view_id.IsTab()) {
return new Error(kUnknownError,
"The current target does not support navigation");
}
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::Reload,
base::Unretained(automation_.get()),
current_target_.view_id,
&error));
return error;
}
Error* Session::GetURL(std::string* url) {
return ExecuteScriptAndParse(current_target_,
"function() { return document.URL }",
"getUrl",
new base::ListValue(),
CreateDirectValueParser(url));
}
Error* Session::GetTitle(std::string* tab_title) {
const char* kGetTitleScript =
"function() {"
" if (document.title)"
" return document.title;"
" else"
" return document.URL;"
"}";
return ExecuteScriptAndParse(FrameId(current_target_.view_id, FramePath()),
kGetTitleScript,
"getTitle",
new base::ListValue(),
CreateDirectValueParser(tab_title));
}
Error* Session::MouseMoveAndClick(const Point& location,
automation::MouseButton button) {
Error* error = NULL;
if (build_no_ >= kNewMouseAPIMinVersion) {
std::vector<WebMouseEvent> events;
events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, location, 0));
events.push_back(CreateWebMouseEvent(kMouseDown, button, location, 1));
events.push_back(CreateWebMouseEvent(kMouseUp, button, location, 1));
error = ProcessWebMouseEvents(events);
} else {
RunSessionTask(base::Bind(
&Automation::MouseClickDeprecated,
base::Unretained(automation_.get()),
current_target_.view_id,
location,
button,
&error));
}
if (!error)
mouse_position_ = location;
return error;
}
Error* Session::MouseMove(const Point& location) {
Error* error = NULL;
if (build_no_ >= kNewMouseAPIMinVersion) {
std::vector<WebMouseEvent> events;
events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, location, 0));
error = ProcessWebMouseEvents(events);
} else {
RunSessionTask(base::Bind(
&Automation::MouseMoveDeprecated,
base::Unretained(automation_.get()),
current_target_.view_id,
location,
&error));
}
if (!error)
mouse_position_ = location;
return error;
}
Error* Session::MouseDrag(const Point& start,
const Point& end) {
Error* error = NULL;
if (build_no_ >= kNewMouseAPIMinVersion) {
std::vector<WebMouseEvent> events;
events.push_back(CreateWebMouseEvent(kMouseMove, kNoButton, start, 0));
events.push_back(CreateWebMouseEvent(kMouseDown, kLeftButton, start, 1));
events.push_back(CreateWebMouseEvent(kMouseMove, kLeftButton, end, 0));
events.push_back(CreateWebMouseEvent(kMouseUp, kLeftButton, end, 1));
error = ProcessWebMouseEvents(events);
} else {
RunSessionTask(base::Bind(
&Automation::MouseDragDeprecated,
base::Unretained(automation_.get()),
current_target_.view_id,
start,
end,
&error));
}
if (!error)
mouse_position_ = end;
return error;
}
Error* Session::MouseClick(automation::MouseButton button) {
if (build_no_ >= kNewMouseAPIMinVersion) {
std::vector<WebMouseEvent> events;
events.push_back(CreateWebMouseEvent(
kMouseDown, button, mouse_position_, 1));
events.push_back(CreateWebMouseEvent(
kMouseUp, button, mouse_position_, 1));
return ProcessWebMouseEvents(events);
} else {
return MouseMoveAndClick(mouse_position_, button);
}
}
Error* Session::MouseButtonDown() {
Error* error = NULL;
if (build_no_ >= kNewMouseAPIMinVersion) {
std::vector<WebMouseEvent> events;
events.push_back(CreateWebMouseEvent(
kMouseDown, kLeftButton, mouse_position_, 1));
error = ProcessWebMouseEvents(events);
} else {
RunSessionTask(base::Bind(
&Automation::MouseButtonDownDeprecated,
base::Unretained(automation_.get()),
current_target_.view_id,
mouse_position_,
&error));
}
return error;
}
Error* Session::MouseButtonUp() {
Error* error = NULL;
if (build_no_ >= kNewMouseAPIMinVersion) {
std::vector<WebMouseEvent> events;
events.push_back(CreateWebMouseEvent(
kMouseUp, kLeftButton, mouse_position_, 1));
error = ProcessWebMouseEvents(events);
} else {
RunSessionTask(base::Bind(
&Automation::MouseButtonUpDeprecated,
base::Unretained(automation_.get()),
current_target_.view_id,
mouse_position_,
&error));
}
return error;
}
Error* Session::MouseDoubleClick() {
Error* error = NULL;
if (build_no_ >= kNewMouseAPIMinVersion) {
std::vector<WebMouseEvent> events;
events.push_back(CreateWebMouseEvent(
kMouseDown, kLeftButton, mouse_position_, 1));
events.push_back(CreateWebMouseEvent(
kMouseUp, kLeftButton, mouse_position_, 1));
events.push_back(CreateWebMouseEvent(
kMouseDown, kLeftButton, mouse_position_, 2));
events.push_back(CreateWebMouseEvent(
kMouseUp, kLeftButton, mouse_position_, 2));
error = ProcessWebMouseEvents(events);
} else {
RunSessionTask(base::Bind(
&Automation::MouseDoubleClickDeprecated,
base::Unretained(automation_.get()),
current_target_.view_id,
mouse_position_,
&error));
}
return error;
}
Error* Session::GetCookies(const std::string& url,
scoped_ptr<base::ListValue>* cookies) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::GetCookies,
base::Unretained(automation_.get()),
url,
cookies,
&error));
return error;
}
Error* Session::DeleteCookie(const std::string& url,
const std::string& cookie_name) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::DeleteCookie,
base::Unretained(automation_.get()),
url,
cookie_name,
&error));
return error;
}
// Note that when this is called from CookieCommand::ExecutePost then
// |cookie_dict| is destroyed as soon as the caller finishes. Therefore
// it is essential that RunSessionTask executes synchronously.
Error* Session::SetCookie(const std::string& url,
base::DictionaryValue* cookie_dict) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::SetCookie,
base::Unretained(automation_.get()),
url,
cookie_dict,
&error));
return error;
}
Error* Session::GetViews(std::vector<WebViewInfo>* views) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::GetViews,
base::Unretained(automation_.get()),
views,
&error));
return error;
}
Error* Session::SwitchToView(const std::string& id_or_name) {
Error* error = NULL;
bool does_exist = false;
WebViewId new_view;
StringToWebViewId(id_or_name, &new_view);
if (new_view.IsValid()) {
RunSessionTask(base::Bind(
&Automation::DoesViewExist,
base::Unretained(automation_.get()),
new_view,
&does_exist,
&error));
if (error)
return error;
}
if (!does_exist) {
// See if any of the tab window names match |name|.
std::vector<WebViewInfo> views;
Error* error = GetViews(&views);
if (error)
return error;
for (size_t i = 0; i < views.size(); ++i) {
if (!views[i].view_id.IsTab())
continue;
std::string window_name;
Error* error = ExecuteScriptAndParse(
FrameId(views[i].view_id, FramePath()),
"function() { return window.name; }",
"getWindowName",
new base::ListValue(),
CreateDirectValueParser(&window_name));
if (error)
return error;
if (id_or_name == window_name) {
new_view = views[i].view_id;
does_exist = true;
break;
}
}
}
if (!does_exist)
return new Error(kNoSuchWindow);
frame_elements_.clear();
current_target_ = FrameId(new_view, FramePath());
return NULL;
}
Error* Session::SwitchToFrameWithNameOrId(const std::string& name_or_id) {
std::string script =
"function(arg) {"
" var xpath = '(/html/body//iframe|/html/frameset/frame)';"
" var sub = function(s) { return s.replace(/\\$/g, arg); };"
" xpath += sub('[@name=\"$\" or @id=\"$\"]');"
" return document.evaluate(xpath, document, null, "
" XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
"}";
return SwitchToFrameWithJavaScriptLocatedFrame(
script, CreateListValueFrom(name_or_id));
}
Error* Session::SwitchToFrameWithIndex(int index) {
// We cannot simply index into window.frames because we need to know the
// tagName of the frameElement. If child frame N is from another domain, then
// the following will run afoul of the same origin policy:
// window.frames[N].frameElement;
// Instead of indexing window.frames, we use an XPath expression to index
// into the list of all IFRAME and FRAME elements on the page - if we find
// something, then that XPath expression can be used as the new frame's XPath.
std::string script =
"function(index) {"
" var xpathIndex = '[' + (index + 1) + ']';"
" var xpath = '(/html/body//iframe|/html/frameset/frame)' + "
" xpathIndex;"
" return document.evaluate(xpath, document, null, "
" XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
"}";
return SwitchToFrameWithJavaScriptLocatedFrame(
script, CreateListValueFrom(index));
}
Error* Session::SwitchToFrameWithElement(const ElementId& element) {
// TODO(jleyba): Extract this, and the other frame switch methods to an atom.
std::string script =
"function(elem) {"
" if (elem.nodeType != 1 || !/^i?frame$/i.test(elem.tagName)) {"
" console.error('Element is not a frame');"
" return null;"
" }"
" for (var i = 0; i < window.frames.length; i++) {"
" if (elem.contentWindow == window.frames[i]) {"
" return elem;"
" }"
" }"
" console.info('Frame is not connected to this DOM tree');"
" return null;"
"}";
return SwitchToFrameWithJavaScriptLocatedFrame(
script, CreateListValueFrom(element));
}
void Session::SwitchToTopFrame() {
frame_elements_.clear();
current_target_.frame_path = FramePath();
}
Error* Session::SwitchToTopFrameIfCurrentFrameInvalid() {
std::vector<std::string> components;
current_target_.frame_path.GetComponents(&components);
if (frame_elements_.size() != components.size()) {
return new Error(kUnknownError,
"Frame element vector out of sync with frame path");
}
FramePath frame_path;
// Start from the root path and check that each frame element that makes
// up the current frame target is valid by executing an empty script.
// This code should not execute script in any frame before making sure the
// frame element is valid, otherwise the automation hangs until a timeout.
for (size_t i = 0; i < frame_elements_.size(); ++i) {
FrameId frame_id(current_target_.view_id, frame_path);
scoped_ptr<Error> error(ExecuteScriptAndParse(
frame_id,
"function(){ }",
"emptyScript",
CreateListValueFrom(frame_elements_[i]),
CreateDirectValueParser(kSkipParsing)));
if (error.get() && error->code() == kStaleElementReference) {
SwitchToTopFrame();
} else if (error.get()) {
return error.release();
}
frame_path = frame_path.Append(components[i]);
}
return NULL;
}
Error* Session::CloseWindow() {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::CloseView,
base::Unretained(automation_.get()),
current_target_.view_id,
&error));
if (!error) {
std::vector<WebViewInfo> views;
scoped_ptr<Error> error(GetViews(&views));
if (error.get() || views.empty()) {
// The automation connection will soon be closed, if not already,
// because we supposedly just closed the last window. Terminate the
// session.
// TODO(kkania): This will cause us problems if GetWindowIds fails for a
// reason other than the channel is disconnected. Look into having
// |GetWindowIds| tell us if it just closed the last window.
Terminate();
}
}
return error;
}
Error* Session::GetWindowBounds(const WebViewId& window, Rect* bounds) {
const char* kGetWindowBoundsScript =
"function() {"
" return {"
" 'left': window.screenX,"
" 'top': window.screenY,"
" 'width': window.outerWidth,"
" 'height': window.outerHeight"
" }"
"}";
return ExecuteScriptAndParse(
FrameId(window, FramePath()),
kGetWindowBoundsScript,
"getWindowBoundsScript",
new base::ListValue(),
CreateDirectValueParser(bounds));
}
Error* Session::SetWindowBounds(
const WebViewId& window,
const Rect& bounds) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::SetViewBounds,
base::Unretained(automation_.get()),
window,
bounds,
&error));
return error;
}
Error* Session::MaximizeWindow(const WebViewId& window) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::MaximizeView,
base::Unretained(automation_.get()),
window,
&error));
return error;
}
Error* Session::GetAlertMessage(std::string* text) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::GetAppModalDialogMessage,
base::Unretained(automation_.get()),
text,
&error));
return error;
}
Error* Session::SetAlertPromptText(const std::string& alert_prompt_text) {
std::string message_text;
// Only set the alert prompt text if an alert is actually active.
Error* error = GetAlertMessage(&message_text);
if (!error) {
has_alert_prompt_text_ = true;
alert_prompt_text_ = alert_prompt_text;
}
return error;
}
Error* Session::AcceptOrDismissAlert(bool accept) {
Error* error = NULL;
if (accept && has_alert_prompt_text_) {
RunSessionTask(base::Bind(
&Automation::AcceptPromptAppModalDialog,
base::Unretained(automation_.get()),
alert_prompt_text_,
&error));
} else {
RunSessionTask(base::Bind(
&Automation::AcceptOrDismissAppModalDialog,
base::Unretained(automation_.get()),
accept,
&error));
}
has_alert_prompt_text_ = false;
return error;
}
std::string Session::GetBrowserVersion() {
std::string version;
RunSessionTask(base::Bind(
&Automation::GetBrowserVersion,
base::Unretained(automation_.get()),
&version));
return version;
}
Error* Session::CompareBrowserVersion(int client_build_no,
int client_patch_no,
bool* is_newer_or_equal) {
std::string version = GetBrowserVersion();
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* Session::FindElement(const FrameId& frame_id,
const ElementId& root_element,
const std::string& locator,
const std::string& query,
ElementId* element) {
std::vector<ElementId> elements;
Error* error = FindElementsHelper(
frame_id, root_element, locator, query, true, &elements);
if (!error)
*element = elements[0];
return error;
}
Error* Session::FindElements(const FrameId& frame_id,
const ElementId& root_element,
const std::string& locator,
const std::string& query,
std::vector<ElementId>* elements) {
return FindElementsHelper(
frame_id, root_element, locator, query, false, elements);
}
Error* Session::GetElementLocationInView(
const ElementId& element,
Point* location) {
Size size;
Error* error = GetElementSize(current_target_, element, &size);
if (error)
return error;
return GetElementRegionInView(
element, Rect(Point(0, 0), size),
false /* center */, false /* verify_clickable_at_middle */, location);
}
Error* Session::GetElementRegionInView(
const ElementId& element,
const Rect& region,
bool center,
bool verify_clickable_at_middle,
Point* location) {
CHECK(element.is_valid());
Point region_offset = region.origin();
Size region_size = region.size();
Error* error = GetElementRegionInViewHelper(
current_target_, element, region, center, verify_clickable_at_middle,
&region_offset);
if (error)
return error;
for (FramePath frame_path = current_target_.frame_path;
frame_path.IsSubframe();
frame_path = frame_path.Parent()) {
// Find the frame element for the current frame path.
FrameId frame_id(current_target_.view_id, frame_path.Parent());
ElementId frame_element;
error = FindElement(frame_id,
ElementId(std::string()),
LocatorType::kXpath,
frame_path.BaseName().value(),
&frame_element);
if (error) {
std::string context = base::StringPrintf(
"Could not find frame element (%s) in frame (%s)",
frame_path.BaseName().value().c_str(),
frame_path.Parent().value().c_str());
error->AddDetails(context);
return error;
}
// Modify |region_offset| by the frame's border.
int border_left, border_top;
error = GetElementBorder(
frame_id, frame_element, &border_left, &border_top);
if (error)
return error;
region_offset.Offset(border_left, border_top);
error = GetElementRegionInViewHelper(
frame_id, frame_element, Rect(region_offset, region_size),
center, verify_clickable_at_middle, &region_offset);
if (error)
return error;
}
*location = region_offset;
return NULL;
}
Error* Session::GetElementSize(const FrameId& frame_id,
const ElementId& element,
Size* size) {
return ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::GET_SIZE),
"getSize",
CreateListValueFrom(element),
CreateDirectValueParser(size));
}
Error* Session::GetElementFirstClientRect(const FrameId& frame_id,
const ElementId& element,
Rect* rect) {
return ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::GET_FIRST_CLIENT_RECT),
"getFirstClientRect",
CreateListValueFrom(element),
CreateDirectValueParser(rect));
}
Error* Session::GetElementEffectiveStyle(
const FrameId& frame_id,
const ElementId& element,
const std::string& prop,
std::string* value) {
return ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::GET_EFFECTIVE_STYLE),
"getEffectiveStyle",
CreateListValueFrom(element, prop),
CreateDirectValueParser(value));
}
Error* Session::GetElementBorder(const FrameId& frame_id,
const ElementId& element,
int* border_left,
int* border_top) {
std::string border_left_str, border_top_str;
Error* error = GetElementEffectiveStyle(
frame_id, element, "border-left-width", &border_left_str);
if (error)
return error;
error = GetElementEffectiveStyle(
frame_id, element, "border-top-width", &border_top_str);
if (error)
return error;
base::StringToInt(border_left_str, border_left);
base::StringToInt(border_top_str, border_top);
return NULL;
}
Error* Session::IsElementDisplayed(const FrameId& frame_id,
const ElementId& element,
bool ignore_opacity,
bool* is_displayed) {
return ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::IS_DISPLAYED),
"isDisplayed",
CreateListValueFrom(element, ignore_opacity),
CreateDirectValueParser(is_displayed));
}
Error* Session::IsElementEnabled(const FrameId& frame_id,
const ElementId& element,
bool* is_enabled) {
return ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::IS_ENABLED),
"isEnabled",
CreateListValueFrom(element),
CreateDirectValueParser(is_enabled));
}
Error* Session::IsOptionElementSelected(const FrameId& frame_id,
const ElementId& element,
bool* is_selected) {
return ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::IS_SELECTED),
"isSelected",
CreateListValueFrom(element),
CreateDirectValueParser(is_selected));
}
Error* Session::SetOptionElementSelected(const FrameId& frame_id,
const ElementId& element,
bool selected) {
// This wrapper ensures the script is started successfully and
// allows for an alert to happen when the option selection occurs.
// See selenium bug 2671.
const char kSetSelectedWrapper[] =
"var args = [].slice.apply(arguments);"
"args[args.length - 1]();"
"return (%s).apply(null, args.slice(0, args.length - 1));";
base::Value* value = NULL;
Error* error = ExecuteAsyncScript(
frame_id,
base::StringPrintf(kSetSelectedWrapper,
atoms::asString(atoms::CLICK).c_str()),
CreateListValueFrom(element, selected),
&value);
scoped_ptr<base::Value> scoped_value(value);
return error;
}
Error* Session::ToggleOptionElement(const FrameId& frame_id,
const ElementId& element) {
bool is_selected;
Error* error = IsOptionElementSelected(frame_id, element, &is_selected);
if (error)
return error;
return SetOptionElementSelected(frame_id, element, !is_selected);
}
Error* Session::GetElementTagName(const FrameId& frame_id,
const ElementId& element,
std::string* tag_name) {
return ExecuteScriptAndParse(
frame_id,
"function(elem) { return elem.tagName.toLowerCase() }",
"getElementTagName",
CreateListValueFrom(element),
CreateDirectValueParser(tag_name));
}
Error* Session::GetClickableLocation(const ElementId& element,
Point* location) {
bool is_displayed = false;
Error* error = IsElementDisplayed(
current_target_, element, true /* ignore_opacity */, &is_displayed);
if (error)
return error;
if (!is_displayed)
return new Error(kElementNotVisible, "Element must be displayed to click");
// We try 3 methods to determine clickable location. This mostly follows
// what FirefoxDriver does. Try the first client rect, then the bounding
// client rect, and lastly the size of the element (via closure).
// SVG is one case that doesn't have a first client rect.
Rect rect;
scoped_ptr<Error> ignore_error(
GetElementFirstClientRect(current_target_, element, &rect));
if (ignore_error.get()) {
Rect client_rect;
ignore_error.reset(ExecuteScriptAndParse(
current_target_,
"function(elem) { return elem.getBoundingClientRect() }",
"getBoundingClientRect",
CreateListValueFrom(element),
CreateDirectValueParser(&client_rect)));
rect = Rect(0, 0, client_rect.width(), client_rect.height());
}
if (ignore_error.get()) {
Size size;
ignore_error.reset(GetElementSize(current_target_, element, &size));
rect = Rect(0, 0, size.width(), size.height());
}
if (ignore_error.get()) {
return new Error(kUnknownError,
"Unable to determine clickable location of element");
}
error = GetElementRegionInView(
element, rect, true /* center */, true /* verify_clickable_at_middle */,
location);
if (error)
return error;
location->Offset(rect.width() / 2, rect.height() / 2);
return NULL;
}
Error* Session::GetAttribute(const ElementId& element,
const std::string& key,
base::Value** value) {
return ExecuteScriptAndParse(
current_target_,
atoms::asString(atoms::GET_ATTRIBUTE),
"getAttribute",
CreateListValueFrom(element, key),
CreateDirectValueParser(value));
}
Error* Session::WaitForAllViewsToStopLoading() {
if (!automation_.get())
return NULL;
logger_.Log(kFinerLogLevel, "Waiting for all views to stop loading...");
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::WaitForAllViewsToStopLoading,
base::Unretained(automation_.get()),
&error));
logger_.Log(kFinerLogLevel, "Done waiting for all views to stop loading");
return error;
}
Error* Session::InstallExtension(
const base::FilePath& path, std::string* extension_id) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::InstallExtension,
base::Unretained(automation_.get()),
path,
extension_id,
&error));
return error;
}
Error* Session::GetExtensionsInfo(base::ListValue* extensions_list) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::GetExtensionsInfo,
base::Unretained(automation_.get()),
extensions_list,
&error));
return error;
}
Error* Session::IsPageActionVisible(
const WebViewId& tab_id,
const std::string& extension_id,
bool* is_visible) {
if (!tab_id.IsTab()) {
return new Error(
kUnknownError,
"The current target does not support page actions. Switch to a tab.");
}
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::IsPageActionVisible,
base::Unretained(automation_.get()),
tab_id,
extension_id,
is_visible,
&error));
return error;
}
Error* Session::SetExtensionState(
const std::string& extension_id, bool enable) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::SetExtensionState,
base::Unretained(automation_.get()),
extension_id,
enable,
&error));
return error;
}
Error* Session::ClickExtensionButton(
const std::string& extension_id, bool browser_action) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::ClickExtensionButton,
base::Unretained(automation_.get()),
extension_id,
browser_action,
&error));
return error;
}
Error* Session::UninstallExtension(const std::string& extension_id) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::UninstallExtension,
base::Unretained(automation_.get()),
extension_id,
&error));
return error;
}
Error* Session::SetPreference(
const std::string& pref,
bool is_user_pref,
base::Value* value) {
Error* error = NULL;
if (is_user_pref) {
RunSessionTask(base::Bind(
&Automation::SetPreference,
base::Unretained(automation_.get()),
pref,
value,
&error));
if (error)
error->AddDetails("Failed to set user pref '" + pref + "'");
} else {
RunSessionTask(base::Bind(
&Automation::SetLocalStatePreference,
base::Unretained(automation_.get()),
pref,
value,
&error));
if (error)
error->AddDetails("Failed to set local state pref '" + pref + "'");
}
return error;
}
base::ListValue* Session::GetLog() const {
return session_log_->entries_list()->DeepCopy();
}
Error* Session::GetBrowserConnectionState(bool* online) {
return ExecuteScriptAndParse(
current_target_,
atoms::asString(atoms::IS_ONLINE),
"isOnline",
new base::ListValue(),
CreateDirectValueParser(online));
}
Error* Session::GetAppCacheStatus(int* status) {
return ExecuteScriptAndParse(
current_target_,
atoms::asString(atoms::GET_APPCACHE_STATUS),
"getAppcacheStatus",
new base::ListValue(),
CreateDirectValueParser(status));
}
Error* Session::GetStorageSize(StorageType type, int* size) {
std::string js = atoms::asString(
type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_SIZE
: atoms::GET_SESSION_STORAGE_SIZE);
return ExecuteScriptAndParse(
current_target_,
js,
"getStorageSize",
new base::ListValue(),
CreateDirectValueParser(size));
}
Error* Session::SetStorageItem(StorageType type,
const std::string& key,
const std::string& value) {
std::string js = atoms::asString(
type == kLocalStorageType ? atoms::SET_LOCAL_STORAGE_ITEM
: atoms::SET_SESSION_STORAGE_ITEM);
return ExecuteScriptAndParse(
current_target_,
js,
"setStorageItem",
CreateListValueFrom(key, value),
CreateDirectValueParser(kSkipParsing));
}
Error* Session::ClearStorage(StorageType type) {
std::string js = atoms::asString(
type == kLocalStorageType ? atoms::CLEAR_LOCAL_STORAGE
: atoms::CLEAR_SESSION_STORAGE);
return ExecuteScriptAndParse(
current_target_,
js,
"clearStorage",
new base::ListValue(),
CreateDirectValueParser(kSkipParsing));
}
Error* Session::GetStorageKeys(StorageType type, base::ListValue** keys) {
std::string js = atoms::asString(
type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_KEYS
: atoms::GET_SESSION_STORAGE_KEYS);
return ExecuteScriptAndParse(
current_target_,
js,
"getStorageKeys",
new base::ListValue(),
CreateDirectValueParser(keys));
}
Error* Session::GetStorageItem(StorageType type,
const std::string& key,
std::string* value) {
std::string js = atoms::asString(
type == kLocalStorageType ? atoms::GET_LOCAL_STORAGE_ITEM
: atoms::GET_SESSION_STORAGE_ITEM);
return ExecuteScriptAndParse(
current_target_,
js,
"getStorageItem",
CreateListValueFrom(key),
CreateDirectValueParser(value));
}
Error* Session::RemoveStorageItem(StorageType type,
const std::string& key,
std::string* value) {
std::string js = atoms::asString(
type == kLocalStorageType ? atoms::REMOVE_LOCAL_STORAGE_ITEM
: atoms::REMOVE_SESSION_STORAGE_ITEM);
return ExecuteScriptAndParse(
current_target_,
js,
"removeStorageItem",
CreateListValueFrom(key),
CreateDirectValueParser(value));
}
Error* Session::GetGeolocation(
scoped_ptr<base::DictionaryValue>* geolocation) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::GetGeolocation,
base::Unretained(automation_.get()),
geolocation,
&error));
return error;
}
Error* Session::OverrideGeolocation(const base::DictionaryValue* geolocation) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::OverrideGeolocation,
base::Unretained(automation_.get()),
geolocation,
&error));
return error;
}
const std::string& Session::id() const {
return id_;
}
const FrameId& Session::current_target() const {
return current_target_;
}
void Session::set_async_script_timeout(int timeout_ms) {
async_script_timeout_ = timeout_ms;
}
int Session::async_script_timeout() const {
return async_script_timeout_;
}
void Session::set_implicit_wait(int timeout_ms) {
implicit_wait_ = timeout_ms;
}
int Session::implicit_wait() const {
return implicit_wait_;
}
const Point& Session::get_mouse_position() const {
return mouse_position_;
}
const Logger& Session::logger() const {
return logger_;
}
const base::FilePath& Session::temp_dir() const {
return temp_dir_.path();
}
const Capabilities& Session::capabilities() const {
return capabilities_;
}
void Session::RunSessionTask(const base::Closure& task) {
base::WaitableEvent done_event(false, false);
thread_.message_loop_proxy()->PostTask(FROM_HERE, base::Bind(
&Session::RunClosureOnSessionThread,
base::Unretained(this),
task,
&done_event));
// See SetCookie for why it is essential that we wait here.
done_event.Wait();
}
void Session::RunClosureOnSessionThread(const base::Closure& task,
base::WaitableEvent* done_event) {
task.Run();
done_event->Signal();
}
void Session::InitOnSessionThread(const Automation::BrowserOptions& options,
int* build_no,
Error** error) {
automation_.reset(new Automation(logger_));
automation_->Init(options, build_no, error);
if (*error)
return;
std::vector<WebViewInfo> views;
automation_->GetViews(&views, error);
if (*error)
return;
if (views.empty()) {
*error = new Error(kUnknownError, "No view ids after initialization");
return;
}
current_target_ = FrameId(views[0].view_id, FramePath());
}
void Session::TerminateOnSessionThread() {
if (automation_.get())
automation_->Terminate();
automation_.reset();
}
Error* Session::ExecuteScriptAndParseValue(const FrameId& frame_id,
const std::string& script,
base::Value** script_result) {
std::string response_json;
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::ExecuteScript,
base::Unretained(automation_.get()),
frame_id.view_id,
frame_id.frame_path,
script,
&response_json,
&error));
if (error)
return error;
scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError(
response_json, base::JSON_ALLOW_TRAILING_COMMAS, NULL, NULL));
if (!value.get())
return new Error(kUnknownError, "Failed to parse script result");
if (value->GetType() != base::Value::TYPE_DICTIONARY)
return new Error(kUnknownError, "Execute script returned non-dict: " +
JsonStringify(value.get()));
base::DictionaryValue* result_dict =
static_cast<base::DictionaryValue*>(value.get());
int status;
if (!result_dict->GetInteger("status", &status))
return new Error(kUnknownError, "Execute script did not return status: " +
JsonStringify(result_dict));
ErrorCode code = static_cast<ErrorCode>(status);
if (code != kSuccess) {
base::DictionaryValue* error_dict;
std::string error_msg;
if (result_dict->GetDictionary("value", &error_dict))
error_dict->GetString("message", &error_msg);
if (error_msg.empty())
error_msg = "Script failed with error code: " + base::IntToString(code);
return new Error(code, error_msg);
}
base::Value* tmp;
if (result_dict->Get("value", &tmp)) {
*script_result= tmp->DeepCopy();
} else {
// "value" was not defined in the returned dictionary; set to null.
*script_result= base::Value::CreateNullValue();
}
return NULL;
}
void Session::SendKeysOnSessionThread(const string16& keys,
bool release_modifiers, Error** error) {
std::vector<WebKeyEvent> key_events;
std::string error_msg;
if (!ConvertKeysToWebKeyEvents(keys, logger_, release_modifiers,
&sticky_modifiers_, &key_events, &error_msg)) {
*error = new Error(kUnknownError, error_msg);
return;
}
for (size_t i = 0; i < key_events.size(); ++i) {
automation_->SendWebKeyEvent(
current_target_.view_id,
key_events[i], error);
if (*error) {
std::string details = base::StringPrintf(
"Failed to send key event. Event details:\n"
"Type: %d, KeyCode: %d, UnmodifiedText: %s, ModifiedText: %s, "
"Modifiers: %d",
key_events[i].type,
key_events[i].key_code,
key_events[i].unmodified_text.c_str(),
key_events[i].modified_text.c_str(),
key_events[i].modifiers);
(*error)->AddDetails(details);
return;
}
}
}
Error* Session::ProcessWebMouseEvents(
const std::vector<WebMouseEvent>& events) {
for (size_t i = 0; i < events.size(); ++i) {
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::SendWebMouseEvent,
base::Unretained(automation_.get()),
current_target_.view_id,
events[i],
&error));
if (error)
return error;
mouse_position_ = Point(events[i].x, events[i].y);
}
return NULL;
}
WebMouseEvent Session::CreateWebMouseEvent(
automation::MouseEventType type,
automation::MouseButton button,
const Point& point,
int click_count) {
return WebMouseEvent(type, button, point.rounded_x(), point.rounded_y(),
click_count, sticky_modifiers_);
}
Error* Session::SwitchToFrameWithJavaScriptLocatedFrame(
const std::string& script, base::ListValue* args) {
class SwitchFrameValueParser : public ValueParser {
public:
SwitchFrameValueParser(
bool* found_frame, ElementId* frame)
: found_frame_(found_frame), frame_(frame) { }
virtual ~SwitchFrameValueParser() { }
virtual bool Parse(base::Value* value) const OVERRIDE {
if (value->IsType(base::Value::TYPE_NULL)) {
*found_frame_ = false;
return true;
}
ElementId id(value);
if (!id.is_valid()) {
return false;
}
*frame_ = id;
*found_frame_ = true;
return true;
}
private:
bool* found_frame_;
ElementId* frame_;
};
bool found_frame;
ElementId new_frame_element;
Error* error = ExecuteScriptAndParse(
current_target_, script, "switchFrame", args,
new SwitchFrameValueParser(&found_frame, &new_frame_element));
if (error)
return error;
if (!found_frame)
return new Error(kNoSuchFrame);
std::string frame_id = GenerateRandomID();
error = ExecuteScriptAndParse(
current_target_,
"function(elem, id) { elem.setAttribute('wd_frame_id_', id); }",
"setFrameId",
CreateListValueFrom(new_frame_element, frame_id),
CreateDirectValueParser(kSkipParsing));
if (error)
return error;
frame_elements_.push_back(new_frame_element);
current_target_.frame_path = current_target_.frame_path.Append(
base::StringPrintf("//*[@wd_frame_id_ = '%s']", frame_id.c_str()));
return NULL;
}
Error* Session::FindElementsHelper(const FrameId& frame_id,
const ElementId& root_element,
const std::string& locator,
const std::string& query,
bool find_one,
std::vector<ElementId>* elements) {
CHECK(root_element.is_valid());
base::Time start_time = base::Time::Now();
while (true) {
std::vector<ElementId> temp_elements;
Error* error = ExecuteFindElementScriptAndParse(
frame_id, root_element, locator, query, find_one, &temp_elements);
if (error)
return error;
if (temp_elements.size() > 0u) {
elements->swap(temp_elements);
break;
}
if ((base::Time::Now() - start_time).InMilliseconds() > implicit_wait_) {
if (find_one)
return new Error(kNoSuchElement);
break;
}
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
}
return NULL;
}
Error* Session::ExecuteFindElementScriptAndParse(
const FrameId& frame_id,
const ElementId& root_element,
const std::string& locator,
const std::string& query,
bool find_one,
std::vector<ElementId>* elements) {
CHECK(root_element.is_valid());
class FindElementsParser : public ValueParser {
public:
explicit FindElementsParser(std::vector<ElementId>* elements)
: elements_(elements) { }
virtual ~FindElementsParser() { }
virtual bool Parse(base::Value* value) const OVERRIDE {
if (!value->IsType(base::Value::TYPE_LIST))
return false;
base::ListValue* list = static_cast<base::ListValue*>(value);
for (size_t i = 0; i < list->GetSize(); ++i) {
ElementId element;
base::Value* element_value = NULL;
if (!list->Get(i, &element_value))
return false;
if (!SetFromValue(element_value, &element))
return false;
elements_->push_back(element);
}
return true;
}
private:
std::vector<ElementId>* elements_;
};
class FindElementParser : public ValueParser {
public:
explicit FindElementParser(std::vector<ElementId>* elements)
: elements_(elements) { }
virtual ~FindElementParser() { }
virtual bool Parse(base::Value* value) const OVERRIDE {
if (value->IsType(base::Value::TYPE_NULL))
return true;
ElementId element;
bool set = SetFromValue(value, &element);
if (set)
elements_->push_back(element);
return set;
}
private:
std::vector<ElementId>* elements_;
};
base::DictionaryValue locator_dict;
locator_dict.SetString(locator, query);
std::vector<ElementId> temp_elements;
Error* error = NULL;
if (find_one) {
error = ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::FIND_ELEMENT),
"findElement",
CreateListValueFrom(&locator_dict, root_element),
new FindElementParser(&temp_elements));
} else {
error = ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::FIND_ELEMENTS),
"findElements",
CreateListValueFrom(&locator_dict, root_element),
new FindElementsParser(&temp_elements));
}
if (!error)
elements->swap(temp_elements);
return error;
}
Error* Session::VerifyElementIsClickable(
const FrameId& frame_id,
const ElementId& element,
const Point& location) {
class IsElementClickableParser : public ValueParser {
public:
IsElementClickableParser(bool* clickable, std::string* message)
: clickable_(clickable), message_(message) { }
virtual ~IsElementClickableParser() { }
virtual bool Parse(base::Value* value) const OVERRIDE {
if (!value->IsType(base::Value::TYPE_DICTIONARY))
return false;
base::DictionaryValue* dict = static_cast<base::DictionaryValue*>(value);
dict->GetString("message", message_);
return dict->GetBoolean("clickable", clickable_);
}
private:
bool* clickable_;
std::string* message_;
};
bool clickable;
std::string message;
Error* error = ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::IS_ELEMENT_CLICKABLE),
"isElementClickable",
CreateListValueFrom(element, location),
new IsElementClickableParser(&clickable, &message));
if (error)
return error;
if (!clickable) {
if (message.empty())
message = "element is not clickable";
return new Error(kUnknownError, message);
}
if (message.length()) {
logger_.Log(kWarningLogLevel, message);
}
return NULL;
}
Error* Session::GetElementRegionInViewHelper(
const FrameId& frame_id,
const ElementId& element,
const Rect& region,
bool center,
bool verify_clickable_at_middle,
Point* location) {
Point temp_location;
Error* error = ExecuteScriptAndParse(
frame_id,
atoms::asString(atoms::GET_LOCATION_IN_VIEW),
"getLocationInView",
CreateListValueFrom(element, center, region),
CreateDirectValueParser(&temp_location));
if (verify_clickable_at_middle) {
Point middle_point = temp_location;
middle_point.Offset(region.width() / 2, region.height() / 2);
error = VerifyElementIsClickable(frame_id, element, middle_point);
if (error)
return error;
}
*location = temp_location;
return NULL;
}
Error* Session::GetScreenShot(std::string* png) {
if (!current_target_.view_id.IsTab()) {
return new Error(kUnknownError,
"The current target does not support screenshot");
}
Error* error = NULL;
base::ScopedTempDir screenshots_dir;
if (!screenshots_dir.CreateUniqueTempDir()) {
return new Error(kUnknownError,
"Could not create temp directory for screenshot");
}
base::FilePath path = screenshots_dir.path().AppendASCII("screen");
RunSessionTask(base::Bind(
&Automation::CaptureEntirePageAsPNG,
base::Unretained(automation_.get()),
current_target_.view_id,
path,
&error));
if (error)
return error;
if (!base::ReadFileToString(path, png))
return new Error(kUnknownError, "Could not read screenshot file");
return NULL;
}
#if !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS))
Error* Session::HeapProfilerDump(const std::string& reason) {
// TODO(dmikurube): Support browser processes.
Error* error = NULL;
RunSessionTask(base::Bind(
&Automation::HeapProfilerDump,
base::Unretained(automation_.get()),
current_target_.view_id,
reason,
&error));
return error;
}
#endif // !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS))
Error* Session::PostBrowserStartInit() {
Error* error = NULL;
if (!capabilities_.no_website_testing_defaults)
error = InitForWebsiteTesting();
if (!error)
error = SetPrefs();
if (error)
return error;
// Install extensions.
for (size_t i = 0; i < capabilities_.extensions.size(); ++i) {
std::string extension_id;
error = InstallExtension(capabilities_.extensions[i], &extension_id);
if (error)
return error;
}
return NULL;
}
Error* Session::InitForWebsiteTesting() {
bool has_prefs_api = false;
// Don't set these prefs for Chrome 14 and below.
// TODO(kkania): Remove this when Chrome 14 is unsupported.
Error* error = CompareBrowserVersion(874, 0, &has_prefs_api);
if (error || !has_prefs_api)
return error;
// Disable checking for SSL certificate revocation.
error = SetPreference(
"ssl.rev_checking.enabled",
false /* is_user_pref */,
new base::FundamentalValue(false));
if (error)
return error;
// Allow content by default.
// Media-stream cannot be enabled by default; we must specify
// particular host patterns and devices.
base::DictionaryValue* devices = new base::DictionaryValue();
devices->SetString("audio", "Default");
devices->SetString("video", "Default");
base::DictionaryValue* content_settings = new base::DictionaryValue();
content_settings->Set("media-stream", devices);
base::DictionaryValue* pattern_pairs = new base::DictionaryValue();
pattern_pairs->Set("https://*,*", content_settings);
error = SetPreference(
"profile.content_settings.pattern_pairs",
true /* is_user_pref */,
pattern_pairs);
if (error)
return error;
const int kAllowContent = 1;
base::DictionaryValue* default_content_settings = new base::DictionaryValue();
default_content_settings->SetInteger("geolocation", kAllowContent);
default_content_settings->SetInteger("mouselock", kAllowContent);
default_content_settings->SetInteger("notifications", kAllowContent);
default_content_settings->SetInteger("popups", kAllowContent);
return SetPreference(
"profile.default_content_settings",
true /* is_user_pref */,
default_content_settings);
}
Error* Session::SetPrefs() {
for (base::DictionaryValue::Iterator iter(*capabilities_.prefs);
!iter.IsAtEnd(); iter.Advance()) {
Error* error = SetPreference(iter.key(), true /* is_user_pref */,
iter.value().DeepCopy());
if (error)
return error;
}
for (base::DictionaryValue::Iterator iter(*capabilities_.local_state);
!iter.IsAtEnd(); iter.Advance()) {
Error* error = SetPreference(iter.key(), false /* is_user_pref */,
iter.value().DeepCopy());
if (error)
return error;
}
return NULL;
}
} // namespace webdriver