Remove INJECT_EVENTS permissions from test APKs.
[chromium-blink-merge.git] / chrome / test / chromedriver / element_util.cc
blob6a3ee195f5aacab89d07d1fae005f79e5e1d2137
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_util.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "base/threading/platform_thread.h"
11 #include "base/time/time.h"
12 #include "base/values.h"
13 #include "chrome/test/chromedriver/basic_types.h"
14 #include "chrome/test/chromedriver/chrome/chrome.h"
15 #include "chrome/test/chromedriver/chrome/js.h"
16 #include "chrome/test/chromedriver/chrome/status.h"
17 #include "chrome/test/chromedriver/chrome/web_view.h"
18 #include "chrome/test/chromedriver/session.h"
19 #include "third_party/webdriver/atoms.h"
21 namespace {
23 const char kElementKey[] = "ELEMENT";
25 bool ParseFromValue(base::Value* value, WebPoint* point) {
26 base::DictionaryValue* dict_value;
27 if (!value->GetAsDictionary(&dict_value))
28 return false;
29 double x = 0;
30 double y = 0;
31 if (!dict_value->GetDouble("x", &x) ||
32 !dict_value->GetDouble("y", &y))
33 return false;
34 point->x = static_cast<int>(x);
35 point->y = static_cast<int>(y);
36 return true;
39 bool ParseFromValue(base::Value* value, WebSize* size) {
40 base::DictionaryValue* dict_value;
41 if (!value->GetAsDictionary(&dict_value))
42 return false;
43 double width = 0;
44 double height = 0;
45 if (!dict_value->GetDouble("width", &width) ||
46 !dict_value->GetDouble("height", &height))
47 return false;
48 size->width = static_cast<int>(width);
49 size->height = static_cast<int>(height);
50 return true;
53 bool ParseFromValue(base::Value* value, WebRect* rect) {
54 base::DictionaryValue* dict_value;
55 if (!value->GetAsDictionary(&dict_value))
56 return false;
57 double x = 0;
58 double y = 0;
59 double width = 0;
60 double height = 0;
61 if (!dict_value->GetDouble("left", &x) ||
62 !dict_value->GetDouble("top", &y) ||
63 !dict_value->GetDouble("width", &width) ||
64 !dict_value->GetDouble("height", &height))
65 return false;
66 rect->origin.x = static_cast<int>(x);
67 rect->origin.y = static_cast<int>(y);
68 rect->size.width = static_cast<int>(width);
69 rect->size.height = static_cast<int>(height);
70 return true;
73 base::Value* CreateValueFrom(const WebRect& rect) {
74 base::DictionaryValue* dict = new base::DictionaryValue();
75 dict->SetInteger("left", rect.X());
76 dict->SetInteger("top", rect.Y());
77 dict->SetInteger("width", rect.Width());
78 dict->SetInteger("height", rect.Height());
79 return dict;
82 Status CallAtomsJs(
83 const std::string& frame,
84 WebView* web_view,
85 const char* const* atom_function,
86 const base::ListValue& args,
87 scoped_ptr<base::Value>* result) {
88 return web_view->CallFunction(
89 frame, webdriver::atoms::asString(atom_function), args, result);
92 Status VerifyElementClickable(
93 const std::string& frame,
94 WebView* web_view,
95 const std::string& element_id,
96 const WebPoint& location) {
97 base::ListValue args;
98 args.Append(CreateElement(element_id));
99 args.Append(CreateValueFrom(location));
100 scoped_ptr<base::Value> result;
101 Status status = CallAtomsJs(
102 frame, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE,
103 args, &result);
104 if (status.IsError())
105 return status;
106 base::DictionaryValue* dict;
107 bool is_clickable = false;
108 if (!result->GetAsDictionary(&dict) ||
109 !dict->GetBoolean("clickable", &is_clickable)) {
110 return Status(kUnknownError,
111 "failed to parse value of IS_ELEMENT_CLICKABLE");
114 if (!is_clickable) {
115 std::string message;
116 if (!dict->GetString("message", &message))
117 message = "element is not clickable";
118 return Status(kUnknownError, message);
120 return Status(kOk);
123 Status ScrollElementRegionIntoViewHelper(
124 const std::string& frame,
125 WebView* web_view,
126 const std::string& element_id,
127 const WebRect& region,
128 bool center,
129 const std::string& clickable_element_id,
130 WebPoint* location) {
131 WebPoint tmp_location = *location;
132 base::ListValue args;
133 args.Append(CreateElement(element_id));
134 args.AppendBoolean(center);
135 args.Append(CreateValueFrom(region));
136 scoped_ptr<base::Value> result;
137 Status status = web_view->CallFunction(
138 frame, webdriver::atoms::asString(webdriver::atoms::GET_LOCATION_IN_VIEW),
139 args, &result);
140 if (status.IsError())
141 return status;
142 if (!ParseFromValue(result.get(), &tmp_location)) {
143 return Status(kUnknownError,
144 "failed to parse value of GET_LOCATION_IN_VIEW");
146 if (!clickable_element_id.empty()) {
147 WebPoint middle = tmp_location;
148 middle.Offset(region.Width() / 2, region.Height() / 2);
149 status = VerifyElementClickable(
150 frame, web_view, clickable_element_id, middle);
151 if (status.IsError())
152 return status;
154 *location = tmp_location;
155 return Status(kOk);
158 Status GetElementEffectiveStyle(
159 const std::string& frame,
160 WebView* web_view,
161 const std::string& element_id,
162 const std::string& property,
163 std::string* value) {
164 base::ListValue args;
165 args.Append(CreateElement(element_id));
166 args.AppendString(property);
167 scoped_ptr<base::Value> result;
168 Status status = web_view->CallFunction(
169 frame, webdriver::atoms::asString(webdriver::atoms::GET_EFFECTIVE_STYLE),
170 args, &result);
171 if (status.IsError())
172 return status;
173 if (!result->GetAsString(value)) {
174 return Status(kUnknownError,
175 "failed to parse value of GET_EFFECTIVE_STYLE");
177 return Status(kOk);
180 Status GetElementBorder(
181 const std::string& frame,
182 WebView* web_view,
183 const std::string& element_id,
184 int* border_left,
185 int* border_top) {
186 std::string border_left_str;
187 Status status = GetElementEffectiveStyle(
188 frame, web_view, element_id, "border-left-width", &border_left_str);
189 if (status.IsError())
190 return status;
191 std::string border_top_str;
192 status = GetElementEffectiveStyle(
193 frame, web_view, element_id, "border-top-width", &border_top_str);
194 if (status.IsError())
195 return status;
196 int border_left_tmp = -1;
197 int border_top_tmp = -1;
198 base::StringToInt(border_left_str, &border_left_tmp);
199 base::StringToInt(border_top_str, &border_top_tmp);
200 if (border_left_tmp == -1 || border_top_tmp == -1)
201 return Status(kUnknownError, "failed to get border width of element");
202 *border_left = border_left_tmp;
203 *border_top = border_top_tmp;
204 return Status(kOk);
207 } // namespace
209 base::DictionaryValue* CreateElement(const std::string& element_id) {
210 base::DictionaryValue* element = new base::DictionaryValue();
211 element->SetString(kElementKey, element_id);
212 return element;
215 base::Value* CreateValueFrom(const WebPoint& point) {
216 base::DictionaryValue* dict = new base::DictionaryValue();
217 dict->SetInteger("x", point.x);
218 dict->SetInteger("y", point.y);
219 return dict;
222 Status FindElement(
223 int interval_ms,
224 bool only_one,
225 const std::string* root_element_id,
226 Session* session,
227 WebView* web_view,
228 const base::DictionaryValue& params,
229 scoped_ptr<base::Value>* value) {
230 std::string strategy;
231 if (!params.GetString("using", &strategy))
232 return Status(kUnknownError, "'using' must be a string");
233 std::string target;
234 if (!params.GetString("value", &target))
235 return Status(kUnknownError, "'value' must be a string");
237 std::string script;
238 if (only_one)
239 script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT);
240 else
241 script = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS);
242 scoped_ptr<base::DictionaryValue> locator(new base::DictionaryValue());
243 locator->SetString(strategy, target);
244 base::ListValue arguments;
245 arguments.Append(locator.release());
246 if (root_element_id)
247 arguments.Append(CreateElement(*root_element_id));
249 base::TimeTicks start_time = base::TimeTicks::Now();
250 while (true) {
251 scoped_ptr<base::Value> temp;
252 Status status = web_view->CallFunction(
253 session->GetCurrentFrameId(), script, arguments, &temp);
254 if (status.IsError())
255 return status;
257 if (!temp->IsType(base::Value::TYPE_NULL)) {
258 if (only_one) {
259 value->reset(temp.release());
260 return Status(kOk);
261 } else {
262 base::ListValue* result;
263 if (!temp->GetAsList(&result))
264 return Status(kUnknownError, "script returns unexpected result");
266 if (result->GetSize() > 0U) {
267 value->reset(temp.release());
268 return Status(kOk);
273 if (base::TimeTicks::Now() - start_time >= session->implicit_wait) {
274 if (only_one) {
275 return Status(kNoSuchElement);
276 } else {
277 value->reset(new base::ListValue());
278 return Status(kOk);
281 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(interval_ms));
284 return Status(kUnknownError);
287 Status GetActiveElement(
288 Session* session,
289 WebView* web_view,
290 scoped_ptr<base::Value>* value) {
291 base::ListValue args;
292 return web_view->CallFunction(
293 session->GetCurrentFrameId(),
294 "function() { return document.activeElement || document.body }",
295 args,
296 value);
299 Status IsElementFocused(
300 Session* session,
301 WebView* web_view,
302 const std::string& element_id,
303 bool* is_focused) {
304 scoped_ptr<base::Value> result;
305 Status status = GetActiveElement(session, web_view, &result);
306 if (status.IsError())
307 return status;
308 scoped_ptr<base::Value> element_dict(CreateElement(element_id));
309 *is_focused = result->Equals(element_dict.get());
310 return Status(kOk);
313 Status GetElementAttribute(
314 Session* session,
315 WebView* web_view,
316 const std::string& element_id,
317 const std::string& attribute_name,
318 scoped_ptr<base::Value>* value) {
319 base::ListValue args;
320 args.Append(CreateElement(element_id));
321 args.AppendString(attribute_name);
322 return CallAtomsJs(
323 session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_ATTRIBUTE,
324 args, value);
327 Status IsElementAttributeEqualToIgnoreCase(
328 Session* session,
329 WebView* web_view,
330 const std::string& element_id,
331 const std::string& attribute_name,
332 const std::string& attribute_value,
333 bool* is_equal) {
334 scoped_ptr<base::Value> result;
335 Status status = GetElementAttribute(
336 session, web_view, element_id, attribute_name, &result);
337 if (status.IsError())
338 return status;
339 std::string actual_value;
340 if (result->GetAsString(&actual_value)) {
341 *is_equal =
342 base::LowerCaseEqualsASCII(actual_value, attribute_value.c_str());
343 } else {
344 *is_equal = false;
346 return status;
349 Status GetElementClickableLocation(
350 Session* session,
351 WebView* web_view,
352 const std::string& element_id,
353 WebPoint* location) {
354 std::string tag_name;
355 Status status = GetElementTagName(session, web_view, element_id, &tag_name);
356 if (status.IsError())
357 return status;
358 std::string target_element_id = element_id;
359 if (tag_name == "area") {
360 // Scroll the image into view instead of the area.
361 const char kGetImageElementForArea[] =
362 "function (element) {"
363 " var map = element.parentElement;"
364 " if (map.tagName.toLowerCase() != 'map')"
365 " throw new Error('the area is not within a map');"
366 " var mapName = map.getAttribute('name');"
367 " if (mapName == null)"
368 " throw new Error ('area\\'s parent map must have a name');"
369 " mapName = '#' + mapName.toLowerCase();"
370 " var images = document.getElementsByTagName('img');"
371 " for (var i = 0; i < images.length; i++) {"
372 " if (images[i].useMap.toLowerCase() == mapName)"
373 " return images[i];"
374 " }"
375 " throw new Error('no img is found for the area');"
376 "}";
377 base::ListValue args;
378 args.Append(CreateElement(element_id));
379 scoped_ptr<base::Value> result;
380 status = web_view->CallFunction(
381 session->GetCurrentFrameId(), kGetImageElementForArea, args, &result);
382 if (status.IsError())
383 return status;
384 const base::DictionaryValue* element_dict;
385 if (!result->GetAsDictionary(&element_dict) ||
386 !element_dict->GetString(kElementKey, &target_element_id))
387 return Status(kUnknownError, "no element reference returned by script");
389 bool is_displayed = false;
390 status = IsElementDisplayed(
391 session, web_view, target_element_id, true, &is_displayed);
392 if (status.IsError())
393 return status;
394 if (!is_displayed)
395 return Status(kElementNotVisible);
397 WebRect rect;
398 status = GetElementRegion(session, web_view, element_id, &rect);
399 if (status.IsError())
400 return status;
402 status = ScrollElementRegionIntoView(
403 session, web_view, target_element_id, rect,
404 true /* center */, element_id, location);
405 if (status.IsError())
406 return status;
407 location->Offset(rect.Width() / 2, rect.Height() / 2);
408 return Status(kOk);
411 Status GetElementEffectiveStyle(
412 Session* session,
413 WebView* web_view,
414 const std::string& element_id,
415 const std::string& property_name,
416 std::string* property_value) {
417 return GetElementEffectiveStyle(session->GetCurrentFrameId(), web_view,
418 element_id, property_name, property_value);
421 Status GetElementRegion(
422 Session* session,
423 WebView* web_view,
424 const std::string& element_id,
425 WebRect* rect) {
426 base::ListValue args;
427 args.Append(CreateElement(element_id));
428 scoped_ptr<base::Value> result;
429 Status status = web_view->CallFunction(
430 session->GetCurrentFrameId(), kGetElementRegionScript, args, &result);
431 if (status.IsError())
432 return status;
433 if (!ParseFromValue(result.get(), rect)) {
434 return Status(kUnknownError,
435 "failed to parse value of getElementRegion");
437 return Status(kOk);
440 Status GetElementTagName(
441 Session* session,
442 WebView* web_view,
443 const std::string& element_id,
444 std::string* name) {
445 base::ListValue args;
446 args.Append(CreateElement(element_id));
447 scoped_ptr<base::Value> result;
448 Status status = web_view->CallFunction(
449 session->GetCurrentFrameId(),
450 "function(elem) { return elem.tagName.toLowerCase(); }",
451 args, &result);
452 if (status.IsError())
453 return status;
454 if (!result->GetAsString(name))
455 return Status(kUnknownError, "failed to get element tag name");
456 return Status(kOk);
459 Status GetElementSize(
460 Session* session,
461 WebView* web_view,
462 const std::string& element_id,
463 WebSize* size) {
464 base::ListValue args;
465 args.Append(CreateElement(element_id));
466 scoped_ptr<base::Value> result;
467 Status status = CallAtomsJs(
468 session->GetCurrentFrameId(), web_view, webdriver::atoms::GET_SIZE,
469 args, &result);
470 if (status.IsError())
471 return status;
472 if (!ParseFromValue(result.get(), size))
473 return Status(kUnknownError, "failed to parse value of GET_SIZE");
474 return Status(kOk);
477 Status IsElementDisplayed(
478 Session* session,
479 WebView* web_view,
480 const std::string& element_id,
481 bool ignore_opacity,
482 bool* is_displayed) {
483 base::ListValue args;
484 args.Append(CreateElement(element_id));
485 args.AppendBoolean(ignore_opacity);
486 scoped_ptr<base::Value> result;
487 Status status = CallAtomsJs(
488 session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_DISPLAYED,
489 args, &result);
490 if (status.IsError())
491 return status;
492 if (!result->GetAsBoolean(is_displayed))
493 return Status(kUnknownError, "IS_DISPLAYED should return a boolean value");
494 return Status(kOk);
497 Status IsElementEnabled(
498 Session* session,
499 WebView* web_view,
500 const std::string& element_id,
501 bool* is_enabled) {
502 base::ListValue args;
503 args.Append(CreateElement(element_id));
504 scoped_ptr<base::Value> result;
505 Status status = CallAtomsJs(
506 session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_ENABLED,
507 args, &result);
508 if (status.IsError())
509 return status;
510 if (!result->GetAsBoolean(is_enabled))
511 return Status(kUnknownError, "IS_ENABLED should return a boolean value");
512 return Status(kOk);
515 Status IsOptionElementSelected(
516 Session* session,
517 WebView* web_view,
518 const std::string& element_id,
519 bool* is_selected) {
520 base::ListValue args;
521 args.Append(CreateElement(element_id));
522 scoped_ptr<base::Value> result;
523 Status status = CallAtomsJs(
524 session->GetCurrentFrameId(), web_view, webdriver::atoms::IS_SELECTED,
525 args, &result);
526 if (status.IsError())
527 return status;
528 if (!result->GetAsBoolean(is_selected))
529 return Status(kUnknownError, "IS_SELECTED should return a boolean value");
530 return Status(kOk);
533 Status IsOptionElementTogglable(
534 Session* session,
535 WebView* web_view,
536 const std::string& element_id,
537 bool* is_togglable) {
538 base::ListValue args;
539 args.Append(CreateElement(element_id));
540 scoped_ptr<base::Value> result;
541 Status status = web_view->CallFunction(
542 session->GetCurrentFrameId(), kIsOptionElementToggleableScript,
543 args, &result);
544 if (status.IsError())
545 return status;
546 if (!result->GetAsBoolean(is_togglable))
547 return Status(kUnknownError, "failed check if option togglable or not");
548 return Status(kOk);
551 Status SetOptionElementSelected(
552 Session* session,
553 WebView* web_view,
554 const std::string& element_id,
555 bool selected) {
556 // TODO(171034): need to fix throwing error if an alert is triggered.
557 base::ListValue args;
558 args.Append(CreateElement(element_id));
559 args.AppendBoolean(selected);
560 scoped_ptr<base::Value> result;
561 return CallAtomsJs(
562 session->GetCurrentFrameId(), web_view, webdriver::atoms::CLICK,
563 args, &result);
566 Status ToggleOptionElement(
567 Session* session,
568 WebView* web_view,
569 const std::string& element_id) {
570 bool is_selected;
571 Status status = IsOptionElementSelected(
572 session, web_view, element_id, &is_selected);
573 if (status.IsError())
574 return status;
575 return SetOptionElementSelected(session, web_view, element_id, !is_selected);
578 Status ScrollElementIntoView(
579 Session* session,
580 WebView* web_view,
581 const std::string& id,
582 const WebPoint* offset,
583 WebPoint* location) {
584 WebRect region;
585 Status status = GetElementRegion(session, web_view, id, &region);
586 if (status.IsError())
587 return status;
588 status = ScrollElementRegionIntoView(session, web_view, id, region,
589 false /* center */, std::string(), location);
590 if (status.IsError())
591 return status;
592 if (offset)
593 location->Offset(offset->x, offset->y);
594 else
595 location->Offset(region.size.width / 2, region.size.height / 2);
596 return Status(kOk);
599 Status ScrollElementRegionIntoView(
600 Session* session,
601 WebView* web_view,
602 const std::string& element_id,
603 const WebRect& region,
604 bool center,
605 const std::string& clickable_element_id,
606 WebPoint* location) {
607 WebPoint region_offset = region.origin;
608 WebSize region_size = region.size;
609 Status status = ScrollElementRegionIntoViewHelper(
610 session->GetCurrentFrameId(), web_view, element_id, region,
611 center, clickable_element_id, &region_offset);
612 if (status.IsError())
613 return status;
614 const char kFindSubFrameScript[] =
615 "function(xpath) {"
616 " return document.evaluate(xpath, document, null,"
617 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
618 "}";
619 for (std::list<FrameInfo>::reverse_iterator rit = session->frames.rbegin();
620 rit != session->frames.rend(); ++rit) {
621 base::ListValue args;
622 args.AppendString(
623 base::StringPrintf("//*[@cd_frame_id_ = '%s']",
624 rit->chromedriver_frame_id.c_str()));
625 scoped_ptr<base::Value> result;
626 status = web_view->CallFunction(
627 rit->parent_frame_id, kFindSubFrameScript, args, &result);
628 if (status.IsError())
629 return status;
630 const base::DictionaryValue* element_dict;
631 if (!result->GetAsDictionary(&element_dict))
632 return Status(kUnknownError, "no element reference returned by script");
633 std::string frame_element_id;
634 if (!element_dict->GetString(kElementKey, &frame_element_id))
635 return Status(kUnknownError, "failed to locate a sub frame");
637 // Modify |region_offset| by the frame's border.
638 int border_left = -1;
639 int border_top = -1;
640 status = GetElementBorder(
641 rit->parent_frame_id, web_view, frame_element_id,
642 &border_left, &border_top);
643 if (status.IsError())
644 return status;
645 region_offset.Offset(border_left, border_top);
647 status = ScrollElementRegionIntoViewHelper(
648 rit->parent_frame_id, web_view, frame_element_id,
649 WebRect(region_offset, region_size),
650 center, frame_element_id, &region_offset);
651 if (status.IsError())
652 return status;
654 *location = region_offset;
655 return Status(kOk);