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

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: 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) {
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))
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 base::StringToInt(border_left_str, border_left);
kkania 2013/03/12 04:04:07 check return value?
chrisgao (Use stgao instead) 2013/03/12 17:41:18 Done.
194 base::StringToInt(border_top_str, border_top);
195 return Status(kOk);
97 } 196 }
98 197
99 } // namespace 198 } // namespace
100 199
101 base::DictionaryValue* CreateElement(const std::string& element_id) { 200 base::DictionaryValue* CreateElement(const std::string& element_id) {
102 base::DictionaryValue* element = new base::DictionaryValue(); 201 base::DictionaryValue* element = new base::DictionaryValue();
103 element->SetString(kElementKey, element_id); 202 element->SetString(kElementKey, element_id);
104 return element; 203 return element;
105 } 204 }
106 205
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
173 Status GetElementAttribute( 272 Status GetElementAttribute(
174 Session* session, 273 Session* session,
175 WebView* web_view, 274 WebView* web_view,
176 const std::string& element_id, 275 const std::string& element_id,
177 const std::string& attribute_name, 276 const std::string& attribute_name,
178 scoped_ptr<base::Value>* value) { 277 scoped_ptr<base::Value>* value) {
179 base::ListValue args; 278 base::ListValue args;
180 args.Append(CreateElement(element_id)); 279 args.Append(CreateElement(element_id));
181 args.AppendString(attribute_name); 280 args.AppendString(attribute_name);
182 return CallAtomsJs( 281 return CallAtomsJs(
183 session, web_view, webdriver::atoms::GET_ATTRIBUTE, args, value); 282 session->frame, web_view, webdriver::atoms::GET_ATTRIBUTE, args, value);
184 } 283 }
185 284
186 Status IsElementAttributeEqualToIgnoreCase( 285 Status IsElementAttributeEqualToIgnoreCase(
187 Session* session, 286 Session* session,
188 WebView* web_view, 287 WebView* web_view,
189 const std::string& element_id, 288 const std::string& element_id,
190 const std::string& attribute_name, 289 const std::string& attribute_name,
191 const std::string& attribute_value, 290 const std::string& attribute_value,
192 bool* is_equal) { 291 bool* is_equal) {
193 scoped_ptr<base::Value> result; 292 scoped_ptr<base::Value> result;
194 Status status = GetElementAttribute( 293 Status status = GetElementAttribute(
195 session, web_view, element_id, attribute_name, &result); 294 session, web_view, element_id, attribute_name, &result);
196 if (status.IsError()) 295 if (status.IsError())
197 return status; 296 return status;
198 std::string actual_value; 297 std::string actual_value;
199 if (result->GetAsString(&actual_value)) 298 if (result->GetAsString(&actual_value))
200 *is_equal = LowerCaseEqualsASCII(actual_value, attribute_value.c_str()); 299 *is_equal = LowerCaseEqualsASCII(actual_value, attribute_value.c_str());
201 else 300 else
202 *is_equal = false; 301 *is_equal = false;
203 return status; 302 return status;
204 } 303 }
205 304
206 Status GetElementClickableLocation( 305 Status GetElementClickableLocation(
207 Session* session, 306 Session* session,
208 WebView* web_view, 307 WebView* web_view,
209 const std::string& element_id, 308 const std::string& element_id,
210 WebPoint* location, 309 WebPoint* location) {
211 bool* is_clickable) {
212 bool is_displayed = false; 310 bool is_displayed = false;
213 Status status = IsElementDisplayed( 311 Status status = IsElementDisplayed(
214 session, web_view, element_id, true, &is_displayed); 312 session, web_view, element_id, true, &is_displayed);
215 if (status.IsError()) 313 if (status.IsError())
216 return status; 314 return status;
217 if (!is_displayed) 315 if (!is_displayed)
218 return Status(kElementNotVisible); 316 return Status(kElementNotVisible);
219 317
220 WebRect rect; 318 WebRect rect;
221 status = GetElementRegion(session, web_view, element_id, &rect); 319 status = GetElementRegion(session, web_view, element_id, &rect);
222 if (status.IsError()) 320 if (status.IsError())
223 return status; 321 return status;
224 322
225 status = ScrollElementRegionIntoView( 323 status = ScrollElementRegionIntoView(
226 session, web_view, element_id, rect, true, location); 324 session, web_view, element_id, rect,
325 true /* center */, true /* verify_clickable */, location);
227 if (status.IsError()) 326 if (status.IsError())
228 return status; 327 return status;
229 location->offset(rect.width() / 2, rect.height() / 2); 328 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); 329 return Status(kOk);
235 } 330 }
236 331
237 Status GetElementRegion( 332 Status GetElementRegion(
238 Session* session, 333 Session* session,
239 WebView* web_view, 334 WebView* web_view,
240 const std::string& element_id, 335 const std::string& element_id,
241 WebRect* rect) { 336 WebRect* rect) {
242 base::ListValue args; 337 base::ListValue args;
243 args.Append(CreateElement(element_id)); 338 args.Append(CreateElement(element_id));
(...skipping 29 matching lines...) Expand all
273 368
274 Status GetElementSize( 369 Status GetElementSize(
275 Session* session, 370 Session* session,
276 WebView* web_view, 371 WebView* web_view,
277 const std::string& element_id, 372 const std::string& element_id,
278 WebSize* size) { 373 WebSize* size) {
279 base::ListValue args; 374 base::ListValue args;
280 args.Append(CreateElement(element_id)); 375 args.Append(CreateElement(element_id));
281 scoped_ptr<base::Value> result; 376 scoped_ptr<base::Value> result;
282 Status status = CallAtomsJs( 377 Status status = CallAtomsJs(
283 session, web_view, webdriver::atoms::GET_SIZE, args, &result); 378 session->frame, web_view, webdriver::atoms::GET_SIZE, args, &result);
284 if (status.IsError()) 379 if (status.IsError())
285 return status; 380 return status;
286 if (!ParseFromValue(result.get(), size)) 381 if (!ParseFromValue(result.get(), size))
287 return Status(kUnknownError, "fail to parse value of GET_SIZE"); 382 return Status(kUnknownError, "fail to parse value of GET_SIZE");
288 return Status(kOk); 383 return Status(kOk);
289 } 384 }
290 385
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( 386 Status IsElementDisplayed(
320 Session* session, 387 Session* session,
321 WebView* web_view, 388 WebView* web_view,
322 const std::string& element_id, 389 const std::string& element_id,
323 bool ignore_opacity, 390 bool ignore_opacity,
324 bool* is_displayed) { 391 bool* is_displayed) {
325 base::ListValue args; 392 base::ListValue args;
326 args.Append(CreateElement(element_id)); 393 args.Append(CreateElement(element_id));
327 args.AppendBoolean(ignore_opacity); 394 args.AppendBoolean(ignore_opacity);
328 scoped_ptr<base::Value> result; 395 scoped_ptr<base::Value> result;
329 Status status = CallAtomsJs( 396 Status status = CallAtomsJs(
330 session, web_view, webdriver::atoms::IS_DISPLAYED, args, &result); 397 session->frame, web_view, webdriver::atoms::IS_DISPLAYED, args, &result);
331 if (status.IsError()) 398 if (status.IsError())
332 return status; 399 return status;
333 if (!result->GetAsBoolean(is_displayed)) 400 if (!result->GetAsBoolean(is_displayed))
334 return Status(kUnknownError, "IS_DISPLAYED should return a boolean value"); 401 return Status(kUnknownError, "IS_DISPLAYED should return a boolean value");
335 return Status(kOk); 402 return Status(kOk);
336 } 403 }
337 404
338 Status IsElementEnabled( 405 Status IsElementEnabled(
339 Session* session, 406 Session* session,
340 WebView* web_view, 407 WebView* web_view,
341 const std::string& element_id, 408 const std::string& element_id,
342 bool* is_enabled) { 409 bool* is_enabled) {
343 base::ListValue args; 410 base::ListValue args;
344 args.Append(CreateElement(element_id)); 411 args.Append(CreateElement(element_id));
345 scoped_ptr<base::Value> result; 412 scoped_ptr<base::Value> result;
346 Status status = CallAtomsJs( 413 Status status = CallAtomsJs(
347 session, web_view, webdriver::atoms::IS_ENABLED, args, &result); 414 session->frame, web_view, webdriver::atoms::IS_ENABLED, args, &result);
348 if (status.IsError()) 415 if (status.IsError())
349 return status; 416 return status;
350 if (!result->GetAsBoolean(is_enabled)) 417 if (!result->GetAsBoolean(is_enabled))
351 return Status(kUnknownError, "IS_ENABLED should return a boolean value"); 418 return Status(kUnknownError, "IS_ENABLED should return a boolean value");
352 return Status(kOk); 419 return Status(kOk);
353 } 420 }
354 421
355 Status IsOptionElementSelected( 422 Status IsOptionElementSelected(
356 Session* session, 423 Session* session,
357 WebView* web_view, 424 WebView* web_view,
358 const std::string& element_id, 425 const std::string& element_id,
359 bool* is_selected) { 426 bool* is_selected) {
360 base::ListValue args; 427 base::ListValue args;
361 args.Append(CreateElement(element_id)); 428 args.Append(CreateElement(element_id));
362 scoped_ptr<base::Value> result; 429 scoped_ptr<base::Value> result;
363 Status status = CallAtomsJs( 430 Status status = CallAtomsJs(
364 session, web_view, webdriver::atoms::IS_SELECTED, args, &result); 431 session->frame, web_view, webdriver::atoms::IS_SELECTED, args, &result);
365 if (status.IsError()) 432 if (status.IsError())
366 return status; 433 return status;
367 if (!result->GetAsBoolean(is_selected)) 434 if (!result->GetAsBoolean(is_selected))
368 return Status(kUnknownError, "IS_SELECTED should return a boolean value"); 435 return Status(kUnknownError, "IS_SELECTED should return a boolean value");
369 return Status(kOk); 436 return Status(kOk);
370 } 437 }
371 438
372 Status IsOptionElementTogglable( 439 Status IsOptionElementTogglable(
373 Session* session, 440 Session* session,
374 WebView* web_view, 441 WebView* web_view,
(...skipping 15 matching lines...) Expand all
390 Session* session, 457 Session* session,
391 WebView* web_view, 458 WebView* web_view,
392 const std::string& element_id, 459 const std::string& element_id,
393 bool selected) { 460 bool selected) {
394 // TODO(171034): need to fix throwing error if an alert is triggered. 461 // TODO(171034): need to fix throwing error if an alert is triggered.
395 base::ListValue args; 462 base::ListValue args;
396 args.Append(CreateElement(element_id)); 463 args.Append(CreateElement(element_id));
397 args.AppendBoolean(selected); 464 args.AppendBoolean(selected);
398 scoped_ptr<base::Value> result; 465 scoped_ptr<base::Value> result;
399 return CallAtomsJs( 466 return CallAtomsJs(
400 session, web_view, webdriver::atoms::CLICK, args, &result); 467 session->frame, web_view, webdriver::atoms::CLICK, args, &result);
401 } 468 }
402 469
403 Status ToggleOptionElement( 470 Status ToggleOptionElement(
404 Session* session, 471 Session* session,
405 WebView* web_view, 472 WebView* web_view,
406 const std::string& element_id) { 473 const std::string& element_id) {
407 bool is_selected; 474 bool is_selected;
408 Status status = IsOptionElementSelected( 475 Status status = IsOptionElementSelected(
409 session, web_view, element_id, &is_selected); 476 session, web_view, element_id, &is_selected);
410 if (status.IsError()) 477 if (status.IsError())
411 return status; 478 return status;
412 return SetOptionElementSelected(session, web_view, element_id, !is_selected); 479 return SetOptionElementSelected(session, web_view, element_id, !is_selected);
413 } 480 }
414 481
415 Status ScrollElementIntoView( 482 Status ScrollElementIntoView(
416 Session* session, 483 Session* session,
417 WebView* web_view, 484 WebView* web_view,
418 const std::string& id, 485 const std::string& id,
419 WebPoint* location) { 486 WebPoint* location) {
420 WebSize size; 487 WebSize size;
421 Status status = GetElementSize(session, web_view, id, &size); 488 Status status = GetElementSize(session, web_view, id, &size);
422 if (status.IsError()) 489 if (status.IsError())
423 return status; 490 return status;
424 return ScrollElementRegionIntoView( 491 return ScrollElementRegionIntoView(
425 session, web_view, id, WebRect(WebPoint(0, 0), size), false, location); 492 session, web_view, id, WebRect(WebPoint(0, 0), size),
493 false /* center */, false /* verify_clickable */, location);
426 } 494 }
427 495
428 Status ScrollElementRegionIntoView( 496 Status ScrollElementRegionIntoView(
429 Session* session, 497 Session* session,
430 WebView* web_view, 498 WebView* web_view,
431 const std::string& element_id, 499 const std::string& element_id,
432 const WebRect& region, 500 const WebRect& region,
433 bool center, 501 bool center,
502 bool verify_clickable,
434 WebPoint* location) { 503 WebPoint* location) {
435 WebPoint region_offset = region.origin; 504 WebPoint region_offset = region.origin;
436 base::ListValue args; 505 WebSize region_size = region.size;
437 args.Append(CreateElement(element_id)); 506 Status status = ScrollElementRegionIntoViewHelper(
438 args.AppendBoolean(center); 507 session->frame, web_view, element_id, region,
439 args.Append(CreateValueFrom(&region)); 508 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()) 509 if (status.IsError())
446 return status; 510 return status;
447 if (!ParseFromValue(result.get(), &region_offset)) 511 if (!session->frame.empty()) {
448 return Status(kUnknownError, "fail to parse value of GET_LOCATION_IN_VIEW"); 512 std::list<Frame> frames;
513 status = web_view->GetFramePath(session->frame, &frames);
514 if (status.IsError())
515 return status;
516 std::string root_frame_id = frames.back().id;
517 const char* kFindSubFrameScript =
518 "function(xpath) {"
519 " return document.evaluate(xpath, document, null,"
520 " XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;"
521 "}";
522 for (std::list<Frame>::const_iterator it = frames.begin();
523 it != frames.end(); ++it) {
524 if (!it->IsSubFrame())
525 break;
526
527 std::string xpath = "(/html/body//iframe|/html/frameset/frame)";
528 if (it->HasName())
529 xpath += base::StringPrintf("[@name=\"%s\"]", it->name.c_str());
kkania 2013/03/12 04:04:07 why do we support both name and index? i don't th
chrisgao (Use stgao instead) 2013/03/12 17:41:18 Because name of frame is optional in the Inspector
530 else
531 xpath += base::StringPrintf("[%d]", it->index + 1);
532
533 std::string parent_frame_id = it->parent_id;
534 if (parent_frame_id == root_frame_id)
535 parent_frame_id = "";
536
537 base::ListValue args;
538 args.AppendString(xpath);
539 scoped_ptr<base::Value> result;
540 status = web_view->CallFunction(
541 parent_frame_id, kFindSubFrameScript, args, &result);
542 if (status.IsError())
543 return status;
544 const base::DictionaryValue* element_dict;
545 if (!result->GetAsDictionary(&element_dict))
546 return Status(kUnknownError, "no element reference returned by script");
547 std::string frame_element_id;
548 if (!element_dict->GetString(kElementKey, &frame_element_id))
549 return Status(kUnknownError, "fail to locate a sub frame");
550
551 // Modify |region_offset| by the frame's border.
552 int border_lef, border_top;
553 status = GetElementBorder(
554 parent_frame_id, web_view, frame_element_id,
555 &border_lef, &border_top);
556 if (status.IsError())
557 return status;
558 region_offset.Offset(border_lef, border_top);
559
560 status = ScrollElementRegionIntoViewHelper(
561 parent_frame_id, web_view, frame_element_id,
562 WebRect(region_offset, region_size),
563 center, verify_clickable, &region_offset);
564 if (status.IsError())
565 return status;
566 }
567 }
449 *location = region_offset; 568 *location = region_offset;
450 return Status(kOk); 569 return Status(kOk);
451 } 570 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698