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