blob: 124bb4e3c770619ce4e9368e53ffff64c183fc76 [file] [log] [blame]
// Copyright (c) 2013 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/chromedriver/element_commands.h"
#include <stddef.h>
#include <cmath>
#include <list>
#include <vector>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/test/chromedriver/basic_types.h"
#include "chrome/test/chromedriver/chrome/chrome.h"
#include "chrome/test/chromedriver/chrome/js.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/ui_events.h"
#include "chrome/test/chromedriver/chrome/web_view.h"
#include "chrome/test/chromedriver/element_util.h"
#include "chrome/test/chromedriver/session.h"
#include "chrome/test/chromedriver/util.h"
#include "third_party/webdriver/atoms.h"
const int kFlickTouchEventsPerSecond = 30;
namespace {
Status SendKeysToElement(
Session* session,
WebView* web_view,
const std::string& element_id,
const base::ListValue* key_list) {
bool is_displayed = false;
bool is_focused = false;
base::TimeTicks start_time = base::TimeTicks::Now();
while (true) {
Status status = IsElementDisplayed(
session, web_view, element_id, true, &is_displayed);
if (status.IsError())
return status;
if (is_displayed)
break;
status = IsElementFocused(session, web_view, element_id, &is_focused);
if (status.IsError())
return status;
if (is_focused)
break;
if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
return Status(kElementNotVisible);
}
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
}
bool is_enabled = false;
Status status = IsElementEnabled(session, web_view, element_id, &is_enabled);
if (status.IsError())
return status;
if (!is_enabled)
return Status(kInvalidElementState);
if (!is_focused) {
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
status = web_view->CallFunction(
session->GetCurrentFrameId(), kFocusScript, args, &result);
if (status.IsError())
return status;
}
return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers);
}
} // namespace
Status ExecuteElementCommand(
const ElementCommand& command,
Session* session,
WebView* web_view,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value,
Timeout* timeout) {
std::string id;
if (params.GetString("id", &id) || params.GetString("element", &id))
return command.Run(session, web_view, id, params, value);
return Status(kUnknownError, "element identifier must be a string");
}
Status ExecuteFindChildElement(int interval_ms,
Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
return FindElement(
interval_ms, true, &element_id, session, web_view, params, value);
}
Status ExecuteFindChildElements(int interval_ms,
Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
return FindElement(
interval_ms, false, &element_id, session, web_view, params, value);
}
Status ExecuteHoverOverElement(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
WebPoint location;
Status status = GetElementClickableLocation(
session, web_view, element_id, &location);
if (status.IsError())
return status;
MouseEvent move_event(
kMovedMouseEventType, kNoneMouseButton, location.x, location.y,
session->sticky_modifiers, 0);
std::list<MouseEvent> events;
events.push_back(move_event);
status = web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
if (status.IsOk())
session->mouse_position = location;
return status;
}
Status ExecuteClickElement(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string tag_name;
Status status = GetElementTagName(session, web_view, element_id, &tag_name);
if (status.IsError())
return status;
if (tag_name == "option") {
bool is_toggleable;
status = IsOptionElementTogglable(
session, web_view, element_id, &is_toggleable);
if (status.IsError())
return status;
if (is_toggleable)
return ToggleOptionElement(session, web_view, element_id);
else
return SetOptionElementSelected(session, web_view, element_id, true);
} else {
WebPoint location;
status = GetElementClickableLocation(
session, web_view, element_id, &location);
if (status.IsError())
return status;
std::list<MouseEvent> events;
events.push_back(
MouseEvent(kMovedMouseEventType, kNoneMouseButton,
location.x, location.y, session->sticky_modifiers, 0));
events.push_back(
MouseEvent(kPressedMouseEventType, kLeftMouseButton,
location.x, location.y, session->sticky_modifiers, 1));
events.push_back(
MouseEvent(kReleasedMouseEventType, kLeftMouseButton,
location.x, location.y, session->sticky_modifiers, 1));
status =
web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
if (status.IsOk())
session->mouse_position = location;
return status;
}
}
Status ExecuteTouchSingleTap(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
WebPoint location;
Status status = GetElementClickableLocation(
session, web_view, element_id, &location);
if (status.IsError())
return status;
if (!session->chrome->HasTouchScreen()) {
// TODO(samuong): remove this once we stop supporting M44.
std::list<TouchEvent> events;
events.push_back(
TouchEvent(kTouchStart, location.x, location.y));
events.push_back(
TouchEvent(kTouchEnd, location.x, location.y));
return web_view->DispatchTouchEvents(events);
}
return web_view->SynthesizeTapGesture(location.x, location.y, 1, false);
}
Status ExecuteTouchDoubleTap(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
if (!session->chrome->HasTouchScreen()) {
// TODO(samuong): remove this once we stop supporting M44.
return Status(kUnknownCommand, "Double tap command requires Chrome 44+");
}
WebPoint location;
Status status = GetElementClickableLocation(
session, web_view, element_id, &location);
if (status.IsError())
return status;
return web_view->SynthesizeTapGesture(location.x, location.y, 2, false);
}
Status ExecuteTouchLongPress(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
if (!session->chrome->HasTouchScreen()) {
// TODO(samuong): remove this once we stop supporting M44.
return Status(kUnknownCommand, "Long press command requires Chrome 44+");
}
WebPoint location;
Status status = GetElementClickableLocation(
session, web_view, element_id, &location);
if (status.IsError())
return status;
return web_view->SynthesizeTapGesture(location.x, location.y, 1, true);
}
Status ExecuteFlick(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
WebPoint location;
Status status = GetElementClickableLocation(
session, web_view, element_id, &location);
if (status.IsError())
return status;
int xoffset, yoffset, speed;
if (!params.GetInteger("xoffset", &xoffset))
return Status(kUnknownError, "'xoffset' must be an integer");
if (!params.GetInteger("yoffset", &yoffset))
return Status(kUnknownError, "'yoffset' must be an integer");
if (!params.GetInteger("speed", &speed))
return Status(kUnknownError, "'speed' must be an integer");
if (speed < 1)
return Status(kUnknownError, "'speed' must be a positive integer");
status = web_view->DispatchTouchEvent(
TouchEvent(kTouchStart, location.x, location.y));
if (status.IsError())
return status;
const double offset =
std::sqrt(static_cast<double>(xoffset * xoffset + yoffset * yoffset));
const double xoffset_per_event =
(speed * xoffset) / (kFlickTouchEventsPerSecond * offset);
const double yoffset_per_event =
(speed * yoffset) / (kFlickTouchEventsPerSecond * offset);
const int total_events =
(offset * kFlickTouchEventsPerSecond) / speed;
for (int i = 0; i < total_events; i++) {
status = web_view->DispatchTouchEvent(
TouchEvent(kTouchMove,
location.x + xoffset_per_event * i,
location.y + yoffset_per_event * i));
if (status.IsError())
return status;
base::PlatformThread::Sleep(
base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond));
}
return web_view->DispatchTouchEvent(
TouchEvent(kTouchEnd, location.x + xoffset, location.y + yoffset));
}
Status ExecuteClearElement(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
std::unique_ptr<base::Value> result;
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::CLEAR),
args, &result);
}
Status ExecuteSendKeysToElement(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
const base::ListValue* key_list;
if (!params.GetList("value", &key_list))
return Status(kUnknownError, "'value' must be a list");
bool is_input = false;
Status status = IsElementAttributeEqualToIgnoreCase(
session, web_view, element_id, "tagName", "input", &is_input);
if (status.IsError())
return status;
bool is_file = false;
status = IsElementAttributeEqualToIgnoreCase(
session, web_view, element_id, "type", "file", &is_file);
if (status.IsError())
return status;
if (is_input && is_file) {
// Compress array into a single string.
base::FilePath::StringType paths_string;
for (size_t i = 0; i < key_list->GetSize(); ++i) {
base::FilePath::StringType path_part;
if (!key_list->GetString(i, &path_part))
return Status(kUnknownError, "'value' is invalid");
paths_string.append(path_part);
}
// Separate the string into separate paths, delimited by '\n'.
std::vector<base::FilePath> paths;
for (const auto& path_piece : base::SplitStringPiece(
paths_string, base::FilePath::StringType(1, '\n'),
base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL))
paths.push_back(base::FilePath(path_piece));
bool multiple = false;
status = IsElementAttributeEqualToIgnoreCase(
session, web_view, element_id, "multiple", "true", &multiple);
if (status.IsError())
return status;
if (!multiple && paths.size() > 1)
return Status(kUnknownError, "the element can not hold multiple files");
std::unique_ptr<base::DictionaryValue> element(CreateElement(element_id));
return web_view->SetFileInputFiles(
session->GetCurrentFrameId(), *element, paths);
} else {
return SendKeysToElement(session, web_view, element_id, key_list);
}
}
Status ExecuteSubmitElement(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::SUBMIT),
args,
value);
}
Status ExecuteGetElementText(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::GET_TEXT),
args,
value);
}
Status ExecuteGetElementValue(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
"function(elem) { return elem['value'] }",
args,
value);
}
Status ExecuteGetElementTagName(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
"function(elem) { return elem.tagName.toLowerCase() }",
args,
value);
}
Status ExecuteIsElementSelected(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::IS_SELECTED),
args,
value);
}
Status ExecuteIsElementEnabled(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::IS_ENABLED),
args,
value);
}
Status ExecuteIsElementDisplayed(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED),
args,
value);
}
Status ExecuteGetElementLocation(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::GET_LOCATION),
args,
value);
}
Status ExecuteGetElementLocationOnceScrolledIntoView(
Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
WebPoint offset(0, 0);
WebPoint location;
Status status = ScrollElementIntoView(
session, web_view, element_id, &offset, &location);
if (status.IsError())
return status;
*value = CreateValueFrom(location);
return Status(kOk);
}
Status ExecuteGetElementSize(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
base::ListValue args;
args.Append(CreateElement(element_id));
return web_view->CallFunction(
session->GetCurrentFrameId(),
webdriver::atoms::asString(webdriver::atoms::GET_SIZE),
args,
value);
}
Status ExecuteGetElementAttribute(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string name;
if (!params.GetString("name", &name))
return Status(kUnknownError, "missing 'name'");
return GetElementAttribute(session, web_view, element_id, name, value);
}
Status ExecuteGetElementValueOfCSSProperty(
Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string property_name;
if (!params.GetString("propertyName", &property_name))
return Status(kUnknownError, "missing 'propertyName'");
std::string property_value;
Status status = GetElementEffectiveStyle(
session, web_view, element_id, property_name, &property_value);
if (status.IsError())
return status;
value->reset(new base::StringValue(property_value));
return Status(kOk);
}
Status ExecuteElementEquals(Session* session,
WebView* web_view,
const std::string& element_id,
const base::DictionaryValue& params,
std::unique_ptr<base::Value>* value) {
std::string other_element_id;
if (!params.GetString("other", &other_element_id))
return Status(kUnknownError, "'other' must be a string");
value->reset(new base::FundamentalValue(element_id == other_element_id));
return Status(kOk);
}