Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(28)

Side by Side Diff: chrome/test/chromedriver/element_util.cc

Issue 12764021: [chromedriver] Support clicking an element in sub frames. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address comments. Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/test/chromedriver/element_util.h" 5 #include "chrome/test/chromedriver/element_util.h"
6 6
7 #include "base/string_util.h" 7 #include "base/string_util.h"
8 #include "base/stringprintf.h"
9 #include "base/strings/string_number_conversions.cc"
8 #include "base/threading/platform_thread.h" 10 #include "base/threading/platform_thread.h"
9 #include "base/time.h" 11 #include "base/time.h"
10 #include "base/values.h" 12 #include "base/values.h"
11 #include "chrome/test/chromedriver/basic_types.h" 13 #include "chrome/test/chromedriver/basic_types.h"
12 #include "chrome/test/chromedriver/js.h" 14 #include "chrome/test/chromedriver/js.h"
13 #include "chrome/test/chromedriver/session.h" 15 #include "chrome/test/chromedriver/session.h"
14 #include "chrome/test/chromedriver/status.h" 16 #include "chrome/test/chromedriver/status.h"
15 #include "chrome/test/chromedriver/web_view.h" 17 #include "chrome/test/chromedriver/web_view.h"
16 #include "third_party/webdriver/atoms.h" 18 #include "third_party/webdriver/atoms.h"
17 19
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
79 base::Value* CreateValueFrom(const WebRect* rect) { 81 base::Value* CreateValueFrom(const WebRect* rect) {
80 base::DictionaryValue* dict = new base::DictionaryValue(); 82 base::DictionaryValue* dict = new base::DictionaryValue();
81 dict->SetInteger("left", rect->origin.x); 83 dict->SetInteger("left", rect->origin.x);
82 dict->SetInteger("top", rect->origin.y); 84 dict->SetInteger("top", rect->origin.y);
83 dict->SetInteger("width", rect->size.width); 85 dict->SetInteger("width", rect->size.width);
84 dict->SetInteger("height", rect->size.height); 86 dict->SetInteger("height", rect->size.height);
85 return dict; 87 return dict;
86 } 88 }
87 89
88 Status CallAtomsJs( 90 Status CallAtomsJs(
89 Session* session, 91 const std::string& frame,
90 WebView* web_view, 92 WebView* web_view,
91 const char* const* atom_function, 93 const char* const* atom_function,
92 const base::ListValue& args, 94 const base::ListValue& args,
93 scoped_ptr<base::Value>* result) { 95 scoped_ptr<base::Value>* result) {
94 return web_view->CallFunction( 96 return web_view->CallFunction(
95 session->frame, webdriver::atoms::asString(atom_function), 97 frame, webdriver::atoms::asString(atom_function), args, result);
96 args, result); 98 }
99
100 Status VerifyElementClickable(
101 const std::string& frame,
102 WebView* web_view,
103 const std::string& element_id,
104 WebPoint* location) {
kkania 2013/03/12 18:34:25 make this const WebPoint&; also, change all the Cr
chrisgao (Use stgao instead) 2013/03/12 23:17:41 Done.
105 base::ListValue args;
106 args.Append(CreateElement(element_id));
107 args.Append(CreateValueFrom(location));
108 scoped_ptr<base::Value> result;
109 Status status = CallAtomsJs(
110 frame, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE,
111 args, &result);
112 if (status.IsError())
113 return status;
114 base::DictionaryValue* dict;
115 bool is_clickable;
116 if (!result->GetAsDictionary(&dict) ||
117 !dict->GetBoolean("clickable", &is_clickable))
118 return Status(kUnknownError, "fail to parse value of IS_ELEMENT_CLICKABLE");
119
120 if (!is_clickable) {
121 std::string message;
122 if (!dict->GetString("message", &message))
123 message = "element is not clickable";
124 return Status(kUnknownError, message);
125 }
126 return Status(kOk);
127 }
128
129 Status ScrollElementRegionIntoViewHelper(
130 const std::string& frame,
131 WebView* web_view,
132 const std::string& element_id,
133 const WebRect& region,
134 bool center,
135 bool verify_clickable,
136 WebPoint* location) {
137 base::ListValue args;
138 args.Append(CreateElement(element_id));
139 args.AppendBoolean(center);
140 args.Append(CreateValueFrom(&region));
141 scoped_ptr<base::Value> result;
142 Status status = web_view->CallFunction(
143 frame, webdriver::atoms::asString(webdriver::atoms::GET_LOCATION_IN_VIEW),
144 args, &result);
145 if (status.IsError())
146 return status;
147 if (!ParseFromValue(result.get(), location))
kkania 2013/03/12 18:34:25 usually we've been trying to keep to the conventio
chrisgao (Use stgao instead) 2013/03/12 23:17:41 Done.
148 return Status(kUnknownError, "fail to parse value of GET_LOCATION_IN_VIEW");
149 if (verify_clickable) {
150 WebPoint middle = *location;
151 middle.Offset(region.width() / 2, region.height() / 2);
152 return VerifyElementClickable(frame, web_view, element_id, &middle);
153 }
154 return Status(kOk);
155 }
156
157 Status GetElementEffectiveStyle(
158 const std::string& frame,
159 WebView* web_view,
160 const std::string& element_id,
161 const std::string& property,
162 std::string* value) {
163 base::ListValue args;
164 args.Append(CreateElement(element_id));
165 args.AppendString(property);
166 scoped_ptr<base::Value> result;
167 Status status = web_view->CallFunction(
168 frame, webdriver::atoms::asString(webdriver::atoms::GET_EFFECTIVE_STYLE),
169 args, &result);
170 if (status.IsError())
171 return status;
172 if (!result->GetAsString(value))
173 return Status(kUnknownError, "fail to parse value of GET_EFFECTIVE_STYLE");
174 return Status(kOk);
175 }
176
177 Status GetElementBorder(
178 const std::string& frame,
179 WebView* web_view,
180 const std::string& element_id,
181 int* border_left,
182 int* border_top) {
183 std::string border_left_str;
184 Status status = GetElementEffectiveStyle(
185 frame, web_view, element_id, "border-left-width", &border_left_str);
186 if (status.IsError())
187 return status;
188 std::string border_top_str;
189 status = GetElementEffectiveStyle(
190 frame, web_view, element_id, "border-top-width", &border_top_str);
191 if (status.IsError())
192 return status;
193 int border_left_tmp = -1;
194 int border_top_tmp = -1;
195 base::StringToInt(border_left_str, &border_left_tmp);
196 base::StringToInt(border_top_str, &border_top_tmp);
197 if (border_left_tmp == -1 || border_top_tmp == -1)
198 return Status(kUnknownError, "fail to get border width of element");
199 *border_left = border_left_tmp;
200 *border_top = border_top_tmp;
201 return Status(kOk);
97 } 202 }
98 203
99 } // namespace 204 } // namespace
100 205
101 base::DictionaryValue* CreateElement(const std::string& element_id) { 206 base::DictionaryValue* CreateElement(const std::string& element_id) {
102 base::DictionaryValue* element = new base::DictionaryValue(); 207 base::DictionaryValue* element = new base::DictionaryValue();
103 element->SetString(kElementKey, element_id); 208 element->SetString(kElementKey, element_id);
104 return element; 209 return element;
105 } 210 }
106 211
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
173 Status GetElementAttribute( 278 Status GetElementAttribute(
174 Session* session, 279 Session* session,
175 WebView* web_view, 280 WebView* web_view,
176 const std::string& element_id, 281 const std::string& element_id,
177 const std::string& attribute_name, 282 const std::string& attribute_name,
178 scoped_ptr<base::Value>* value) { 283 scoped_ptr<base::Value>* value) {
179 base::ListValue args; 284 base::ListValue args;
180 args.Append(CreateElement(element_id)); 285 args.Append(CreateElement(element_id));
181 args.AppendString(attribute_name); 286 args.AppendString(attribute_name);
182 return CallAtomsJs( 287 return CallAtomsJs(
183 session, web_view, webdriver::atoms::GET_ATTRIBUTE, args, value); 288 session->frame, web_view, webdriver::atoms::GET_ATTRIBUTE, args, value);
184 } 289 }
185 290
186 Status IsElementAttributeEqualToIgnoreCase( 291 Status IsElementAttributeEqualToIgnoreCase(
187 Session* session, 292 Session* session,
188 WebView* web_view, 293 WebView* web_view,
189 const std::string& element_id, 294 const std::string& element_id,
190 const std::string& attribute_name, 295 const std::string& attribute_name,
191 const std::string& attribute_value, 296 const std::string& attribute_value,
192 bool* is_equal) { 297 bool* is_equal) {
193 scoped_ptr<base::Value> result; 298 scoped_ptr<base::Value> result;
194 Status status = GetElementAttribute( 299 Status status = GetElementAttribute(
195 session, web_view, element_id, attribute_name, &result); 300 session, web_view, element_id, attribute_name, &result);
196 if (status.IsError()) 301 if (status.IsError())
197 return status; 302 return status;
198 std::string actual_value; 303 std::string actual_value;
199 if (result->GetAsString(&actual_value)) 304 if (result->GetAsString(&actual_value))
200 *is_equal = LowerCaseEqualsASCII(actual_value, attribute_value.c_str()); 305 *is_equal = LowerCaseEqualsASCII(actual_value, attribute_value.c_str());
201 else 306 else
202 *is_equal = false; 307 *is_equal = false;
203 return status; 308 return status;
204 } 309 }
205 310
206 Status GetElementClickableLocation( 311 Status GetElementClickableLocation(
207 Session* session, 312 Session* session,
208 WebView* web_view, 313 WebView* web_view,
209 const std::string& element_id, 314 const std::string& element_id,
210 WebPoint* location, 315 WebPoint* location) {
211 bool* is_clickable) {
212 bool is_displayed = false; 316 bool is_displayed = false;
213 Status status = IsElementDisplayed( 317 Status status = IsElementDisplayed(
214 session, web_view, element_id, true, &is_displayed); 318 session, web_view, element_id, true, &is_displayed);
215 if (status.IsError()) 319 if (status.IsError())
216 return status; 320 return status;
217 if (!is_displayed) 321 if (!is_displayed)
218 return Status(kElementNotVisible); 322 return Status(kElementNotVisible);
219 323
220 WebRect rect; 324 WebRect rect;
221 status = GetElementRegion(session, web_view, element_id, &rect); 325 status = GetElementRegion(session, web_view, element_id, &rect);
222 if (status.IsError()) 326 if (status.IsError())
223 return status; 327 return status;
224 328
225 status = ScrollElementRegionIntoView( 329 status = ScrollElementRegionIntoView(
226 session, web_view, element_id, rect, true, location); 330 session, web_view, element_id, rect,
331 true /* center */, true /* verify_clickable */, location);
227 if (status.IsError()) 332 if (status.IsError())
228 return status; 333 return status;
229 location->offset(rect.width() / 2, rect.height() / 2); 334 location->Offset(rect.width() / 2, rect.height() / 2);
230 if (is_clickable) {
231 return IsElementClickable(
232 session, web_view, element_id, location, is_clickable);
233 }
234 return Status(kOk); 335 return Status(kOk);
235 } 336 }
236 337
237 Status GetElementRegion( 338 Status GetElementRegion(
238 Session* session, 339 Session* session,
239 WebView* web_view, 340 WebView* web_view,
240 const std::string& element_id, 341 const std::string& element_id,
241 WebRect* rect) { 342 WebRect* rect) {
242 base::ListValue args; 343 base::ListValue args;
243 args.Append(CreateElement(element_id)); 344 args.Append(CreateElement(element_id));
(...skipping 29 matching lines...) Expand all
273 374
274 Status GetElementSize( 375 Status GetElementSize(
275 Session* session, 376 Session* session,
276 WebView* web_view, 377 WebView* web_view,
277 const std::string& element_id, 378 const std::string& element_id,
278 WebSize* size) { 379 WebSize* size) {
279 base::ListValue args; 380 base::ListValue args;
280 args.Append(CreateElement(element_id)); 381 args.Append(CreateElement(element_id));
281 scoped_ptr<base::Value> result; 382 scoped_ptr<base::Value> result;
282 Status status = CallAtomsJs( 383 Status status = CallAtomsJs(
283 session, web_view, webdriver::atoms::GET_SIZE, args, &result); 384 session->frame, web_view, webdriver::atoms::GET_SIZE, args, &result);
284 if (status.IsError()) 385 if (status.IsError())
285 return status; 386 return status;
286 if (!ParseFromValue(result.get(), size)) 387 if (!ParseFromValue(result.get(), size))
287 return Status(kUnknownError, "fail to parse value of GET_SIZE"); 388 return Status(kUnknownError, "fail to parse value of GET_SIZE");
288 return Status(kOk); 389 return Status(kOk);
289 } 390 }
290 391
291 Status IsElementClickable(
292 Session* session,
293 WebView* web_view,
294 const std::string& element_id,
295 WebPoint* location,
296 bool* is_clickable) {
297 base::ListValue args;
298 args.Append(CreateElement(element_id));
299 args.Append(CreateValueFrom(location));
300 scoped_ptr<base::Value> result;
301 Status status = CallAtomsJs(
302 session, web_view, webdriver::atoms::IS_ELEMENT_CLICKABLE, args, &result);
303 if (status.IsError())
304 return status;
305 base::DictionaryValue* dict;
306 if (!result->GetAsDictionary(&dict) ||
307 !dict->GetBoolean("clickable", is_clickable))
308 return Status(kUnknownError, "fail to parse value of IS_ELEMENT_CLICKABLE");
309
310 if (!*is_clickable) {
311 std::string message;
312 if (!dict->GetString("message", &message))
313 message = "element is not clickable";
314 return Status(kOk, message);
315 }
316 return Status(kOk);
317 }
318
319 Status IsElementDisplayed( 392 Status IsElementDisplayed(
320 Session* session, 393 Session* session,
321 WebView* web_view, 394 WebView* web_view,
322 const std::string& element_id, 395 const std::string& element_id,
323 bool ignore_opacity, 396 bool ignore_opacity,
324 bool* is_displayed) { 397 bool* is_displayed) {
325 base::ListValue args; 398 base::ListValue args;
326 args.Append(CreateElement(element_id)); 399 args.Append(CreateElement(element_id));
327 args.AppendBoolean(ignore_opacity); 400 args.AppendBoolean(ignore_opacity);
328 scoped_ptr<base::Value> result; 401 scoped_ptr<base::Value> result;
329 Status status = CallAtomsJs( 402 Status status = CallAtomsJs(
330 session, web_view, webdriver::atoms::IS_DISPLAYED, args, &result); 403 session->frame, web_view, webdriver::atoms::IS_DISPLAYED, args, &result);
331 if (status.IsError()) 404 if (status.IsError())
332 return status; 405 return status;
333 if (!result->GetAsBoolean(is_displayed)) 406 if (!result->GetAsBoolean(is_displayed))
334 return Status(kUnknownError, "IS_DISPLAYED should return a boolean value"); 407 return Status(kUnknownError, "IS_DISPLAYED should return a boolean value");
335 return Status(kOk); 408 return Status(kOk);
336 } 409 }
337 410
338 Status IsElementEnabled( 411 Status IsElementEnabled(
339 Session* session, 412 Session* session,
340 WebView* web_view, 413 WebView* web_view,
341 const std::string& element_id, 414 const std::string& element_id,
342 bool* is_enabled) { 415 bool* is_enabled) {
343 base::ListValue args; 416 base::ListValue args;
344 args.Append(CreateElement(element_id)); 417 args.Append(CreateElement(element_id));
345 scoped_ptr<base::Value> result; 418 scoped_ptr<base::Value> result;
346 Status status = CallAtomsJs( 419 Status status = CallAtomsJs(
347 session, web_view, webdriver::atoms::IS_ENABLED, args, &result); 420 session->frame, web_view, webdriver::atoms::IS_ENABLED, args, &result);
348 if (status.IsError()) 421 if (status.IsError())
349 return status; 422 return status;
350 if (!result->GetAsBoolean(is_enabled)) 423 if (!result->GetAsBoolean(is_enabled))
351 return Status(kUnknownError, "IS_ENABLED should return a boolean value"); 424 return Status(kUnknownError, "IS_ENABLED should return a boolean value");
352 return Status(kOk); 425 return Status(kOk);
353 } 426 }
354 427
355 Status IsOptionElementSelected( 428 Status IsOptionElementSelected(
356 Session* session, 429 Session* session,
357 WebView* web_view, 430 WebView* web_view,
358 const std::string& element_id, 431 const std::string& element_id,
359 bool* is_selected) { 432 bool* is_selected) {
360 base::ListValue args; 433 base::ListValue args;
361 args.Append(CreateElement(element_id)); 434 args.Append(CreateElement(element_id));
362 scoped_ptr<base::Value> result; 435 scoped_ptr<base::Value> result;
363 Status status = CallAtomsJs( 436 Status status = CallAtomsJs(
364 session, web_view, webdriver::atoms::IS_SELECTED, args, &result); 437 session->frame, web_view, webdriver::atoms::IS_SELECTED, args, &result);
365 if (status.IsError()) 438 if (status.IsError())
366 return status; 439 return status;
367 if (!result->GetAsBoolean(is_selected)) 440 if (!result->GetAsBoolean(is_selected))
368 return Status(kUnknownError, "IS_SELECTED should return a boolean value"); 441 return Status(kUnknownError, "IS_SELECTED should return a boolean value");
369 return Status(kOk); 442 return Status(kOk);
370 } 443 }
371 444
372 Status IsOptionElementTogglable( 445 Status IsOptionElementTogglable(
373 Session* session, 446 Session* session,
374 WebView* web_view, 447 WebView* web_view,
(...skipping 15 matching lines...) Expand all
390 Session* session, 463 Session* session,
391 WebView* web_view, 464 WebView* web_view,
392 const std::string& element_id, 465 const std::string& element_id,
393 bool selected) { 466 bool selected) {
394 // TODO(171034): need to fix throwing error if an alert is triggered. 467 // TODO(171034): need to fix throwing error if an alert is triggered.
395 base::ListValue args; 468 base::ListValue args;
396 args.Append(CreateElement(element_id)); 469 args.Append(CreateElement(element_id));
397 args.AppendBoolean(selected); 470 args.AppendBoolean(selected);
398 scoped_ptr<base::Value> result; 471 scoped_ptr<base::Value> result;
399 return CallAtomsJs( 472 return CallAtomsJs(
400 session, web_view, webdriver::atoms::CLICK, args, &result); 473 session->frame, web_view, webdriver::atoms::CLICK, args, &result);
401 } 474 }
402 475
403 Status ToggleOptionElement( 476 Status ToggleOptionElement(
404 Session* session, 477 Session* session,
405 WebView* web_view, 478 WebView* web_view,
406 const std::string& element_id) { 479 const std::string& element_id) {
407 bool is_selected; 480 bool is_selected;
408 Status status = IsOptionElementSelected( 481 Status status = IsOptionElementSelected(
409 session, web_view, element_id, &is_selected); 482 session, web_view, element_id, &is_selected);
410 if (status.IsError()) 483 if (status.IsError())
411 return status; 484 return status;
412 return SetOptionElementSelected(session, web_view, element_id, !is_selected); 485 return SetOptionElementSelected(session, web_view, element_id, !is_selected);
413 } 486 }
414 487
415 Status ScrollElementIntoView( 488 Status ScrollElementIntoView(
416 Session* session, 489 Session* session,
417 WebView* web_view, 490 WebView* web_view,
418 const std::string& id, 491 const std::string& id,
419 WebPoint* location) { 492 WebPoint* location) {
420 WebSize size; 493 WebSize size;
421 Status status = GetElementSize(session, web_view, id, &size); 494 Status status = GetElementSize(session, web_view, id, &size);
422 if (status.IsError()) 495 if (status.IsError())
423 return status; 496 return status;
424 return ScrollElementRegionIntoView( 497 return ScrollElementRegionIntoView(
425 session, web_view, id, WebRect(WebPoint(0, 0), size), false, location); 498 session, web_view, id, WebRect(WebPoint(0, 0), size),
499 false /* center */, false /* verify_clickable */, location);
426 } 500 }
427 501
428 Status ScrollElementRegionIntoView( 502 Status ScrollElementRegionIntoView(
429 Session* session, 503 Session* session,
430 WebView* web_view, 504 WebView* web_view,
431 const std::string& element_id, 505 const std::string& element_id,
432 const WebRect& region, 506 const WebRect& region,
433 bool center, 507 bool center,
508 bool verify_clickable,
434 WebPoint* location) { 509 WebPoint* location) {
435 WebPoint region_offset = region.origin; 510 WebPoint region_offset = region.origin;
436 base::ListValue args; 511 WebSize region_size = region.size;
437 args.Append(CreateElement(element_id)); 512 Status status = ScrollElementRegionIntoViewHelper(
438 args.AppendBoolean(center); 513 session->frame, web_view, element_id, region,
439 args.Append(CreateValueFrom(&region)); 514 center, verify_clickable, &region_offset);
440 scoped_ptr<base::Value> result;
441
442 // TODO(chrisgao): Nested frame. See http://crbug.com/170998.
443 Status status = CallAtomsJs(
444 session, web_view, webdriver::atoms::GET_LOCATION_IN_VIEW, args, &result);
445 if (status.IsError()) 515 if (status.IsError())
446 return status; 516 return status;
447 if (!ParseFromValue(result.get(), &region_offset)) 517 const char* kFindSubFrameScript =
448 return Status(kUnknownError, "fail to parse value of GET_LOCATION_IN_VIEW"); 518 "function(xpath) {"
519 " return document.evaluate(xpath, document, null,"
520 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
521 "}";
522 for (std::list<FrameInfo>::reverse_iterator rit =
523 session->frame_elements.rbegin();
524 rit != session->frame_elements.rend(); ++rit) {
525 base::ListValue args;
526 args.AppendString(rit->xpath);
527 scoped_ptr<base::Value> result;
528 status = web_view->CallFunction(
529 rit->parent_id, kFindSubFrameScript, args, &result);
530 if (status.IsError())
531 return status;
532 const base::DictionaryValue* element_dict;
533 if (!result->GetAsDictionary(&element_dict))
534 return Status(kUnknownError, "no element reference returned by script");
535 std::string frame_element_id;
536 if (!element_dict->GetString(kElementKey, &frame_element_id))
537 return Status(kUnknownError, "fail to locate a sub frame");
538
539 // Modify |region_offset| by the frame's border.
540 int border_lef, border_top;
541 status = GetElementBorder(
542 rit->parent_id, web_view, frame_element_id,
543 &border_lef, &border_top);
544 if (status.IsError())
545 return status;
546 region_offset.Offset(border_lef, border_top);
547
548 status = ScrollElementRegionIntoViewHelper(
549 rit->parent_id, web_view, frame_element_id,
550 WebRect(region_offset, region_size),
551 center, verify_clickable, &region_offset);
552 if (status.IsError())
553 return status;
554 }
449 *location = region_offset; 555 *location = region_offset;
450 return Status(kOk); 556 return Status(kOk);
451 } 557 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698