1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/test/chromedriver/element_commands.h"
11 #include "base/callback.h"
12 #include "base/files/file_path.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/threading/platform_thread.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "chrome/test/chromedriver/basic_types.h"
19 #include "chrome/test/chromedriver/chrome/chrome.h"
20 #include "chrome/test/chromedriver/chrome/js.h"
21 #include "chrome/test/chromedriver/chrome/status.h"
22 #include "chrome/test/chromedriver/chrome/ui_events.h"
23 #include "chrome/test/chromedriver/chrome/web_view.h"
24 #include "chrome/test/chromedriver/element_util.h"
25 #include "chrome/test/chromedriver/session.h"
26 #include "chrome/test/chromedriver/util.h"
27 #include "third_party/webdriver/atoms.h"
29 const int kFlickTouchEventsPerSecond = 30;
33 Status SendKeysToElement(
36 const std::string& element_id,
37 const base::ListValue* key_list) {
38 bool is_displayed = false;
39 bool is_focused = false;
40 base::TimeTicks start_time = base::TimeTicks::Now();
42 Status status = IsElementDisplayed(
43 session, web_view, element_id, true, &is_displayed);
48 status = IsElementFocused(session, web_view, element_id, &is_focused);
53 if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
54 return Status(kElementNotVisible);
56 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
59 bool is_enabled = false;
60 Status status = IsElementEnabled(session, web_view, element_id, &is_enabled);
64 return Status(kInvalidElementState);
68 args.Append(CreateElement(element_id));
69 scoped_ptr<base::Value> result;
70 status = web_view->CallFunction(
71 session->GetCurrentFrameId(), kFocusScript, args, &result);
76 return SendKeysOnWindow(web_view, key_list, true, &session->sticky_modifiers);
79 Status ExecuteTouchSingleTapAtom(
82 const std::string& element_id,
83 const base::DictionaryValue& params,
84 scoped_ptr<base::Value>* value) {
86 args.Append(CreateElement(element_id));
87 return web_view->CallFunction(
88 session->GetCurrentFrameId(),
89 webdriver::atoms::asString(webdriver::atoms::TOUCH_SINGLE_TAP),
96 Status ExecuteElementCommand(
97 const ElementCommand& command,
100 const base::DictionaryValue& params,
101 scoped_ptr<base::Value>* value) {
103 if (params.GetString("id", &id) || params.GetString("element", &id))
104 return command.Run(session, web_view, id, params, value);
105 return Status(kUnknownError, "element identifier must be a string");
108 Status ExecuteFindChildElement(
112 const std::string& element_id,
113 const base::DictionaryValue& params,
114 scoped_ptr<base::Value>* value) {
116 interval_ms, true, &element_id, session, web_view, params, value);
119 Status ExecuteFindChildElements(
123 const std::string& element_id,
124 const base::DictionaryValue& params,
125 scoped_ptr<base::Value>* value) {
127 interval_ms, false, &element_id, session, web_view, params, value);
130 Status ExecuteHoverOverElement(
133 const std::string& element_id,
134 const base::DictionaryValue& params,
135 scoped_ptr<base::Value>* value) {
137 Status status = GetElementClickableLocation(
138 session, web_view, element_id, &location);
139 if (status.IsError())
142 MouseEvent move_event(
143 kMovedMouseEventType, kNoneMouseButton, location.x, location.y,
144 session->sticky_modifiers, 0);
145 std::list<MouseEvent> events;
146 events.push_back(move_event);
147 status = web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
149 session->mouse_position = location;
153 Status ExecuteClickElement(
156 const std::string& element_id,
157 const base::DictionaryValue& params,
158 scoped_ptr<base::Value>* value) {
159 std::string tag_name;
160 Status status = GetElementTagName(session, web_view, element_id, &tag_name);
161 if (status.IsError())
163 if (tag_name == "option") {
165 status = IsOptionElementTogglable(
166 session, web_view, element_id, &is_toggleable);
167 if (status.IsError())
170 return ToggleOptionElement(session, web_view, element_id);
172 return SetOptionElementSelected(session, web_view, element_id, true);
175 status = GetElementClickableLocation(
176 session, web_view, element_id, &location);
177 if (status.IsError())
180 std::list<MouseEvent> events;
182 MouseEvent(kMovedMouseEventType, kNoneMouseButton,
183 location.x, location.y, session->sticky_modifiers, 0));
185 MouseEvent(kPressedMouseEventType, kLeftMouseButton,
186 location.x, location.y, session->sticky_modifiers, 1));
188 MouseEvent(kReleasedMouseEventType, kLeftMouseButton,
189 location.x, location.y, session->sticky_modifiers, 1));
191 web_view->DispatchMouseEvents(events, session->GetCurrentFrameId());
193 session->mouse_position = location;
198 Status ExecuteTouchSingleTap(
201 const std::string& element_id,
202 const base::DictionaryValue& params,
203 scoped_ptr<base::Value>* value) {
204 // Fall back to javascript atom for pre-m30 Chrome.
205 if (session->chrome->GetBuildNo() < 1576)
206 return ExecuteTouchSingleTapAtom(
207 session, web_view, element_id, params, value);
210 Status status = GetElementClickableLocation(
211 session, web_view, element_id, &location);
212 if (status.IsError())
215 std::list<TouchEvent> events;
217 TouchEvent(kTouchStart, location.x, location.y));
219 TouchEvent(kTouchEnd, location.x, location.y));
220 return web_view->DispatchTouchEvents(events);
226 const std::string& element_id,
227 const base::DictionaryValue& params,
228 scoped_ptr<base::Value>* value) {
230 Status status = GetElementClickableLocation(
231 session, web_view, element_id, &location);
232 if (status.IsError())
235 int xoffset, yoffset, speed;
236 if (!params.GetInteger("xoffset", &xoffset))
237 return Status(kUnknownError, "'xoffset' must be an integer");
238 if (!params.GetInteger("yoffset", &yoffset))
239 return Status(kUnknownError, "'yoffset' must be an integer");
240 if (!params.GetInteger("speed", &speed))
241 return Status(kUnknownError, "'speed' must be an integer");
243 return Status(kUnknownError, "'speed' must be a positive integer");
245 status = web_view->DispatchTouchEvent(
246 TouchEvent(kTouchStart, location.x, location.y));
247 if (status.IsError())
250 const double offset =
251 std::sqrt(static_cast<double>(xoffset * xoffset + yoffset * yoffset));
252 const double xoffset_per_event =
253 (speed * xoffset) / (kFlickTouchEventsPerSecond * offset);
254 const double yoffset_per_event =
255 (speed * yoffset) / (kFlickTouchEventsPerSecond * offset);
256 const int total_events =
257 (offset * kFlickTouchEventsPerSecond) / speed;
258 for (int i = 0; i < total_events; i++) {
259 status = web_view->DispatchTouchEvent(
260 TouchEvent(kTouchMove,
261 location.x + xoffset_per_event * i,
262 location.y + yoffset_per_event * i));
263 if (status.IsError())
265 base::PlatformThread::Sleep(
266 base::TimeDelta::FromMilliseconds(1000 / kFlickTouchEventsPerSecond));
268 return web_view->DispatchTouchEvent(
269 TouchEvent(kTouchEnd, location.x + xoffset, location.y + yoffset));
272 Status ExecuteClearElement(
275 const std::string& element_id,
276 const base::DictionaryValue& params,
277 scoped_ptr<base::Value>* value) {
278 base::ListValue args;
279 args.Append(CreateElement(element_id));
280 scoped_ptr<base::Value> result;
281 return web_view->CallFunction(
282 session->GetCurrentFrameId(),
283 webdriver::atoms::asString(webdriver::atoms::CLEAR),
287 Status ExecuteSendKeysToElement(
290 const std::string& element_id,
291 const base::DictionaryValue& params,
292 scoped_ptr<base::Value>* value) {
293 const base::ListValue* key_list;
294 if (!params.GetList("value", &key_list))
295 return Status(kUnknownError, "'value' must be a list");
297 bool is_input = false;
298 Status status = IsElementAttributeEqualToIgnoreCase(
299 session, web_view, element_id, "tagName", "input", &is_input);
300 if (status.IsError())
302 bool is_file = false;
303 status = IsElementAttributeEqualToIgnoreCase(
304 session, web_view, element_id, "type", "file", &is_file);
305 if (status.IsError())
307 if (is_input && is_file) {
308 // Compress array into a single string.
309 base::FilePath::StringType paths_string;
310 for (size_t i = 0; i < key_list->GetSize(); ++i) {
311 base::FilePath::StringType path_part;
312 if (!key_list->GetString(i, &path_part))
313 return Status(kUnknownError, "'value' is invalid");
314 paths_string.append(path_part);
317 // Separate the string into separate paths, delimited by '\n'.
318 std::vector<base::FilePath::StringType> path_strings;
319 base::SplitString(paths_string, '\n', &path_strings);
320 std::vector<base::FilePath> paths;
321 for (size_t i = 0; i < path_strings.size(); ++i)
322 paths.push_back(base::FilePath(path_strings[i]));
324 bool multiple = false;
325 status = IsElementAttributeEqualToIgnoreCase(
326 session, web_view, element_id, "multiple", "true", &multiple);
327 if (status.IsError())
329 if (!multiple && paths.size() > 1)
330 return Status(kUnknownError, "the element can not hold multiple files");
332 scoped_ptr<base::DictionaryValue> element(CreateElement(element_id));
333 return web_view->SetFileInputFiles(
334 session->GetCurrentFrameId(), *element, paths);
336 return SendKeysToElement(session, web_view, element_id, key_list);
340 Status ExecuteSubmitElement(
343 const std::string& element_id,
344 const base::DictionaryValue& params,
345 scoped_ptr<base::Value>* value) {
346 base::ListValue args;
347 args.Append(CreateElement(element_id));
348 return web_view->CallFunction(
349 session->GetCurrentFrameId(),
350 webdriver::atoms::asString(webdriver::atoms::SUBMIT),
355 Status ExecuteGetElementText(
358 const std::string& element_id,
359 const base::DictionaryValue& params,
360 scoped_ptr<base::Value>* value) {
361 base::ListValue args;
362 args.Append(CreateElement(element_id));
363 return web_view->CallFunction(
364 session->GetCurrentFrameId(),
365 webdriver::atoms::asString(webdriver::atoms::GET_TEXT),
370 Status ExecuteGetElementValue(
373 const std::string& element_id,
374 const base::DictionaryValue& params,
375 scoped_ptr<base::Value>* value) {
376 base::ListValue args;
377 args.Append(CreateElement(element_id));
378 return web_view->CallFunction(
379 session->GetCurrentFrameId(),
380 "function(elem) { return elem['value'] }",
385 Status ExecuteGetElementTagName(
388 const std::string& element_id,
389 const base::DictionaryValue& params,
390 scoped_ptr<base::Value>* value) {
391 base::ListValue args;
392 args.Append(CreateElement(element_id));
393 return web_view->CallFunction(
394 session->GetCurrentFrameId(),
395 "function(elem) { return elem.tagName.toLowerCase() }",
400 Status ExecuteIsElementSelected(
403 const std::string& element_id,
404 const base::DictionaryValue& params,
405 scoped_ptr<base::Value>* value) {
406 base::ListValue args;
407 args.Append(CreateElement(element_id));
408 return web_view->CallFunction(
409 session->GetCurrentFrameId(),
410 webdriver::atoms::asString(webdriver::atoms::IS_SELECTED),
415 Status ExecuteIsElementEnabled(
418 const std::string& element_id,
419 const base::DictionaryValue& params,
420 scoped_ptr<base::Value>* value) {
421 base::ListValue args;
422 args.Append(CreateElement(element_id));
423 return web_view->CallFunction(
424 session->GetCurrentFrameId(),
425 webdriver::atoms::asString(webdriver::atoms::IS_ENABLED),
430 Status ExecuteIsElementDisplayed(
433 const std::string& element_id,
434 const base::DictionaryValue& params,
435 scoped_ptr<base::Value>* value) {
436 base::ListValue args;
437 args.Append(CreateElement(element_id));
438 return web_view->CallFunction(
439 session->GetCurrentFrameId(),
440 webdriver::atoms::asString(webdriver::atoms::IS_DISPLAYED),
445 Status ExecuteGetElementLocation(
448 const std::string& element_id,
449 const base::DictionaryValue& params,
450 scoped_ptr<base::Value>* value) {
451 base::ListValue args;
452 args.Append(CreateElement(element_id));
453 return web_view->CallFunction(
454 session->GetCurrentFrameId(),
455 webdriver::atoms::asString(webdriver::atoms::GET_LOCATION),
460 Status ExecuteGetElementLocationOnceScrolledIntoView(
463 const std::string& element_id,
464 const base::DictionaryValue& params,
465 scoped_ptr<base::Value>* value) {
467 Status status = ScrollElementIntoView(
468 session, web_view, element_id, &location);
469 if (status.IsError())
471 value->reset(CreateValueFrom(location));
475 Status ExecuteGetElementSize(
478 const std::string& element_id,
479 const base::DictionaryValue& params,
480 scoped_ptr<base::Value>* value) {
481 base::ListValue args;
482 args.Append(CreateElement(element_id));
483 return web_view->CallFunction(
484 session->GetCurrentFrameId(),
485 webdriver::atoms::asString(webdriver::atoms::GET_SIZE),
490 Status ExecuteGetElementAttribute(
493 const std::string& element_id,
494 const base::DictionaryValue& params,
495 scoped_ptr<base::Value>* value) {
497 if (!params.GetString("name", &name))
498 return Status(kUnknownError, "missing 'name'");
499 return GetElementAttribute(session, web_view, element_id, name, value);
502 Status ExecuteGetElementValueOfCSSProperty(
505 const std::string& element_id,
506 const base::DictionaryValue& params,
507 scoped_ptr<base::Value>* value) {
508 std::string property_name;
509 if (!params.GetString("propertyName", &property_name))
510 return Status(kUnknownError, "missing 'propertyName'");
511 std::string property_value;
512 Status status = GetElementEffectiveStyle(
513 session, web_view, element_id, property_name, &property_value);
514 if (status.IsError())
516 value->reset(new base::StringValue(property_value));
520 Status ExecuteElementEquals(
523 const std::string& element_id,
524 const base::DictionaryValue& params,
525 scoped_ptr<base::Value>* value) {
526 std::string other_element_id;
527 if (!params.GetString("other", &other_element_id))
528 return Status(kUnknownError, "'other' must be a string");
529 value->reset(new base::FundamentalValue(element_id == other_element_id));