testcentricity_web 4.3.1 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -12
  3. data/LICENSE.md +1 -1
  4. data/README.md +1794 -697
  5. data/lib/devices/devices.yml +144 -216
  6. data/lib/testcentricity_web/browser_helper.rb +33 -4
  7. data/lib/testcentricity_web/data_objects/environment.rb +96 -13
  8. data/lib/testcentricity_web/exception_queue_helper.rb +5 -6
  9. data/lib/testcentricity_web/version.rb +1 -1
  10. data/lib/testcentricity_web/web_core/page_object.rb +53 -49
  11. data/lib/testcentricity_web/web_core/page_objects_helper.rb +20 -11
  12. data/lib/testcentricity_web/web_core/page_section.rb +31 -34
  13. data/lib/testcentricity_web/web_core/webdriver_helper.rb +379 -252
  14. data/lib/testcentricity_web/web_elements/audio.rb +6 -4
  15. data/lib/testcentricity_web/web_elements/button.rb +7 -4
  16. data/lib/testcentricity_web/web_elements/checkbox.rb +149 -147
  17. data/lib/testcentricity_web/web_elements/file_field.rb +38 -36
  18. data/lib/testcentricity_web/web_elements/image.rb +75 -70
  19. data/lib/testcentricity_web/web_elements/label.rb +6 -4
  20. data/lib/testcentricity_web/web_elements/link.rb +15 -13
  21. data/lib/testcentricity_web/web_elements/list.rb +171 -169
  22. data/lib/testcentricity_web/web_elements/media.rb +384 -379
  23. data/lib/testcentricity_web/web_elements/radio.rb +135 -133
  24. data/lib/testcentricity_web/web_elements/range.rb +16 -29
  25. data/lib/testcentricity_web/web_elements/select_list.rb +247 -245
  26. data/lib/testcentricity_web/web_elements/table.rb +575 -573
  27. data/lib/testcentricity_web/web_elements/textfield.rb +143 -139
  28. data/lib/testcentricity_web/web_elements/ui_element.rb +1171 -0
  29. data/lib/testcentricity_web/web_elements/video.rb +39 -37
  30. data/lib/testcentricity_web/world_extensions.rb +37 -4
  31. data/lib/testcentricity_web.rb +4 -23
  32. metadata +27 -79
  33. data/lib/testcentricity_web/web_elements/ui_elements_helper.rb +0 -1148
@@ -0,0 +1,1171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test/unit'
4
+
5
+ Capybara::Node::Element.class_eval do
6
+ def click_at(x, y)
7
+ right = x - (native.size.width / 2)
8
+ top = y - (native.size.height / 2)
9
+ driver.browser.action.move_to(native).move_by(right.to_i, top.to_i).click.perform
10
+ end
11
+
12
+ def hover_at(x, y)
13
+ right = x - (native.size.width / 2)
14
+ top = y - (native.size.height / 2)
15
+ driver.browser.action.move_to(native).move_by(right.to_i, top.to_i).perform
16
+ end
17
+
18
+ def get_width
19
+ native.size.width
20
+ end
21
+
22
+ def get_height
23
+ native.size.height
24
+ end
25
+
26
+ def get_x
27
+ native.location.x
28
+ end
29
+
30
+ def get_y
31
+ native.location.y
32
+ end
33
+
34
+ def displayed?
35
+ native.displayed?
36
+ end
37
+ end
38
+
39
+
40
+ module TestCentricity
41
+ module Elements
42
+ class UIElement
43
+ include Capybara::DSL
44
+ include Test::Unit::Assertions
45
+
46
+ attr_reader :parent, :locator, :context, :type, :name
47
+ attr_accessor :alt_locator, :locator_type, :original_style
48
+ attr_accessor :base_object
49
+ attr_accessor :mru_object, :mru_locator, :mru_parent
50
+
51
+ XPATH_SELECTORS = ['//', '[@', '[contains(']
52
+ CSS_SELECTORS = %w[# :nth-child( :first-child :last-child :nth-of-type( :first-of-type :last-of-type ^= $= *= :contains(]
53
+
54
+ def initialize(name, parent, locator, context)
55
+ @name = name
56
+ @parent = parent
57
+ @locator = locator
58
+ @context = context
59
+ @type = nil
60
+ @alt_locator = nil
61
+ @original_style = nil
62
+ reset_mru_cache
63
+ set_locator_type
64
+ end
65
+
66
+ def reset_mru_cache
67
+ @mru_object = nil
68
+ @mru_locator = nil
69
+ @mru_parent = nil
70
+ end
71
+
72
+ def set_locator_type(locator = nil)
73
+ locator = @locator if locator.nil?
74
+ is_xpath = XPATH_SELECTORS.any? { |selector| locator.include?(selector) }
75
+ is_css = CSS_SELECTORS.any? { |selector| locator.include?(selector) }
76
+ @locator_type = if is_xpath && !is_css
77
+ :xpath
78
+ elsif is_css && !is_xpath
79
+ :css
80
+ elsif !is_css && !is_xpath
81
+ :css
82
+ else
83
+ :css
84
+ end
85
+ end
86
+
87
+ def get_locator_type
88
+ @locator_type
89
+ end
90
+
91
+ def get_object_type
92
+ if @type
93
+ @type
94
+ else
95
+ obj, type = find_element
96
+ object_not_found_exception(obj, type)
97
+ if obj.tag_name
98
+ obj.tag_name
99
+ elsif obj.native.attribute('type')
100
+ obj.native.attribute('type')
101
+ end
102
+ end
103
+ end
104
+
105
+ def get_locator
106
+ @locator
107
+ end
108
+
109
+ def get_name
110
+ @name
111
+ end
112
+
113
+ def set_alt_locator(temp_locator)
114
+ @alt_locator = temp_locator
115
+ end
116
+
117
+ def clear_alt_locator
118
+ @alt_locator = nil
119
+ end
120
+
121
+ # Click on an object
122
+ #
123
+ # @example
124
+ # basket_link.click
125
+ #
126
+ def click
127
+ obj, type = find_element
128
+ object_not_found_exception(obj, type)
129
+ begin
130
+ obj.click
131
+ rescue StandardError
132
+ obj.click_at(10, 10)
133
+ end
134
+ end
135
+
136
+ # Double-click on an object
137
+ #
138
+ # @example
139
+ # file_image.double_click
140
+ #
141
+ def double_click
142
+ obj, type = find_element
143
+ object_not_found_exception(obj, type)
144
+ page.driver.browser.action.double_click(obj.native).perform
145
+ end
146
+
147
+ # Right-click on an object
148
+ #
149
+ # @example
150
+ # basket_item_image.right_click
151
+ #
152
+ def right_click
153
+ obj, type = find_element
154
+ object_not_found_exception(obj, type)
155
+ page.driver.browser.action.context_click(obj.native).perform
156
+ end
157
+
158
+ # Click at a specific location within an object
159
+ #
160
+ # @param x [Integer] X offset
161
+ # @param y [Integer] Y offset
162
+ # @example
163
+ # basket_item_image.click_at(10, 10)
164
+ #
165
+ def click_at(x, y)
166
+ obj, = find_element
167
+ raise "UI #{object_ref_message} not found" unless obj
168
+ obj.click_at(x, y)
169
+ end
170
+
171
+ # Scroll the object to its top, middle, or bottom
172
+ #
173
+ # @param position [Symbol] :top, :bottom, :center
174
+ # @example
175
+ # cue_list.scroll_to(:bottom)
176
+ #
177
+ def scroll_to(position)
178
+ obj, type = find_element
179
+ object_not_found_exception(obj, type)
180
+ page.scroll_to(obj, align: position)
181
+ end
182
+
183
+ def set(value)
184
+ obj, type = find_element
185
+ object_not_found_exception(obj, type)
186
+ obj.set(value)
187
+ end
188
+
189
+ # Send keystrokes to this object.
190
+ #
191
+ # @param keys [String] keys
192
+ # @example
193
+ # comment_field.send_keys(:enter)
194
+ #
195
+ def send_keys(*keys)
196
+ obj, type = find_element
197
+ object_not_found_exception(obj, type)
198
+ obj.send_keys(*keys)
199
+ end
200
+
201
+ # Does UI object exists?
202
+ #
203
+ # @return [Boolean]
204
+ # @example
205
+ # basket_link.exists?
206
+ #
207
+ def exists?(visible = true)
208
+ obj, = find_object(visible)
209
+ !obj.nil?
210
+ end
211
+
212
+ # Is UI object visible?
213
+ #
214
+ # @return [Boolean]
215
+ # @example
216
+ # remember_me_checkbox.visible?
217
+ #
218
+ def visible?
219
+ obj, = find_element
220
+ exists = obj
221
+ # the object is visible if it exists and it is not invisible
222
+ exists && obj.visible?
223
+ end
224
+
225
+ # Is UI object hidden (not visible)?
226
+ #
227
+ # @return [Boolean]
228
+ # @example
229
+ # remember_me_checkbox.hidden?
230
+ #
231
+ def hidden?
232
+ !visible?
233
+ end
234
+
235
+ # Is UI object enabled?
236
+ #
237
+ # @return [Boolean]
238
+ # @example
239
+ # login_button.enabled?
240
+ #
241
+ def enabled?
242
+ !disabled?
243
+ end
244
+
245
+ # Is UI object disabled (not enabled)?
246
+ #
247
+ # @return [Boolean]
248
+ # @example
249
+ # login_button.disabled?
250
+ #
251
+ def disabled?
252
+ obj, type = find_element
253
+ object_not_found_exception(obj, type)
254
+ obj.disabled?
255
+ end
256
+
257
+ # Is UI object's required attribute set?
258
+ #
259
+ # @return [Boolean]
260
+ # @example
261
+ # first_name_field.required?
262
+ #
263
+ def required?
264
+ obj, type = find_element
265
+ object_not_found_exception(obj, type)
266
+ state = get_attribute(:required)
267
+ state.boolean? ? state : state == 'true'
268
+ end
269
+
270
+ # Is UI object obscured (not currently in viewport and not clickable)?
271
+ #
272
+ # @return [Boolean]
273
+ # @example
274
+ # buy_now_button.obscured?
275
+ #
276
+ def obscured?
277
+ obj, type = find_element
278
+ object_not_found_exception(obj, type)
279
+ obj.obscured?
280
+ end
281
+
282
+ # Does UI object have the current focus?
283
+ #
284
+ # @return [Boolean]
285
+ # @example
286
+ # first_name_field.focused?
287
+ #
288
+ def focused?
289
+ obj, type = find_element(visible = :all)
290
+ object_not_found_exception(obj, type)
291
+ focused_obj = page.driver.browser.switch_to.active_element
292
+ focused_obj == obj.native
293
+ end
294
+
295
+ # Wait until the object exists, or until the specified wait time has expired. If the wait time is nil, then the wait
296
+ # time will be Capybara.default_max_wait_time.
297
+ #
298
+ # @param seconds [Integer or Float] wait time in seconds
299
+ # @example
300
+ # run_button.wait_until_exists(0.5)
301
+ #
302
+ def wait_until_exists(seconds = nil, post_exception = true)
303
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
304
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
305
+ wait.until do
306
+ reset_mru_cache
307
+ exists?
308
+ end
309
+ rescue StandardError
310
+ if post_exception
311
+ raise "Could not find UI #{object_ref_message} after #{timeout} seconds" unless exists?
312
+ else
313
+ exists?
314
+ end
315
+ end
316
+
317
+ # Wait until the object no longer exists, or until the specified wait time has expired. If the wait time is nil, then
318
+ # the wait time will be Capybara.default_max_wait_time.
319
+ #
320
+ # @param seconds [Integer or Float] wait time in seconds
321
+ # @example
322
+ # logout_button.wait_until_gone(5)
323
+ #
324
+ def wait_until_gone(seconds = nil, post_exception = true)
325
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
326
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
327
+ wait.until do
328
+ reset_mru_cache
329
+ !exists?
330
+ end
331
+ rescue StandardError
332
+ if post_exception
333
+ raise "UI #{object_ref_message} remained visible after #{timeout} seconds" if exists?
334
+ else
335
+ exists?
336
+ end
337
+ end
338
+
339
+ # Wait until the object is visible, or until the specified wait time has expired. If the wait time is nil, then the
340
+ # wait time will be Capybara.default_max_wait_time.
341
+ #
342
+ # @param seconds [Integer or Float] wait time in seconds
343
+ # @example
344
+ # run_button.wait_until_visible(0.5)
345
+ #
346
+ def wait_until_visible(seconds = nil, post_exception = true)
347
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
348
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
349
+ wait.until do
350
+ reset_mru_cache
351
+ visible?
352
+ end
353
+ rescue StandardError
354
+ if post_exception
355
+ raise "Could not find UI #{object_ref_message} after #{timeout} seconds" unless visible?
356
+ else
357
+ visible?
358
+ end
359
+ end
360
+
361
+ # Wait until the object is hidden, or until the specified wait time has expired. If the wait time is nil, then the
362
+ # wait time will be Capybara.default_max_wait_time.
363
+ #
364
+ # @param seconds [Integer or Float] wait time in seconds
365
+ # @example
366
+ # run_button.wait_until_hidden(10)
367
+ #
368
+ def wait_until_hidden(seconds = nil, post_exception = true)
369
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
370
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
371
+ wait.until do
372
+ reset_mru_cache
373
+ hidden?
374
+ end
375
+ rescue StandardError
376
+ if post_exception
377
+ raise "UI #{object_ref_message} remained visible after #{timeout} seconds" if visible?
378
+ else
379
+ visible?
380
+ end
381
+ end
382
+
383
+ # Wait until the object is enabled, or until the specified wait time has expired. If the wait time is nil, then the
384
+ # wait time will be Capybara.default_max_wait_time.
385
+ #
386
+ # @param seconds [Integer or Float] wait time in seconds
387
+ # @example
388
+ # run_button.wait_until_enabled(10)
389
+ #
390
+ def wait_until_enabled(seconds = nil, post_exception = true)
391
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
392
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
393
+ wait.until do
394
+ reset_mru_cache
395
+ enabled?
396
+ end
397
+ rescue StandardError
398
+ if post_exception
399
+ raise "UI #{object_ref_message} remained disabled after #{timeout} seconds" unless enabled?
400
+ else
401
+ enabled?
402
+ end
403
+ end
404
+
405
+ # Wait until the object is no longer in a busy state, or until the specified wait time has expired. If the wait time
406
+ # is nil, then the wait time will be Capybara.default_max_wait_time.
407
+ #
408
+ # @param seconds [Integer or Float] wait time in seconds
409
+ # @example
410
+ # login_button.wait_while_busy(10)
411
+ #
412
+ def wait_while_busy(seconds = nil, post_exception = true)
413
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
414
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
415
+ wait.until do
416
+ reset_mru_cache
417
+ aria_busy?
418
+ end
419
+ rescue StandardError
420
+ if post_exception
421
+ raise "UI #{object_ref_message} remained in busy state after #{timeout} seconds" if aria_busy?
422
+ else
423
+ aria_busy?
424
+ end
425
+ end
426
+
427
+ # Wait until the object's value equals the specified value, or until the specified wait time has expired. If the wait
428
+ # time is nil, then the wait time will be Capybara.default_max_wait_time.
429
+ #
430
+ # @param value [String or Hash] value expected or comparison hash
431
+ # @param seconds [Integer or Float] wait time in seconds
432
+ # @example
433
+ # card_authorized_label.wait_until_value_is('Card authorized', 5)
434
+ # or
435
+ # total_weight_field.wait_until_value_is({ greater_than: '250' }, 5)
436
+ #
437
+ def wait_until_value_is(value, seconds = nil, post_exception = true)
438
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
439
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
440
+ wait.until do
441
+ reset_mru_cache
442
+ compare(value, get_value)
443
+ end
444
+ rescue StandardError
445
+ if post_exception
446
+ raise "Value of UI #{object_ref_message} failed to equal '#{value}' after #{timeout} seconds" unless get_value == value
447
+ else
448
+ get_value == value
449
+ end
450
+ end
451
+
452
+ # Wait until the object's value changes to a different value, or until the specified wait time has expired. If the
453
+ # wait time is nil, then the wait time will be Capybara.default_max_wait_time.
454
+ #
455
+ # @param seconds [Integer or Float] wait time in seconds
456
+ # @example
457
+ # basket_grand_total_label.wait_until_value_changes(5)
458
+ #
459
+ def wait_until_value_changes(seconds = nil, post_exception = true)
460
+ value = get_value
461
+ timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
462
+ wait = Selenium::WebDriver::Wait.new(timeout: timeout)
463
+ wait.until do
464
+ reset_mru_cache
465
+ get_value != value
466
+ end
467
+ rescue StandardError
468
+ if post_exception
469
+ raise "Value of UI #{object_ref_message} failed to change from '#{value}' after #{timeout} seconds" if get_value == value
470
+ else
471
+ get_value == value
472
+ end
473
+ end
474
+
475
+ # Return the number of occurrences of an object with an ambiguous locator that evaluates to multiple UI elements.
476
+ #
477
+ # @param visible [Boolean, Symbol] Only find elements with the specified visibility:
478
+ # * true - only finds visible elements.
479
+ # * false - finds invisible _and_ visible elements.
480
+ # * :all - same as false; finds visible and invisible elements.
481
+ # * :hidden - only finds invisible elements.
482
+ # * :visible - same as true; only finds visible elements.
483
+ # @example
484
+ # num_uploads = upload_progress_bars.count(:all)
485
+ #
486
+ def count(visible = true)
487
+ obj_locator = @alt_locator.nil? ? @locator : @alt_locator
488
+ page.all(@locator_type, obj_locator, wait: 0.01, visible: visible, minimum: 0).count
489
+ end
490
+
491
+ # Return width of object.
492
+ #
493
+ # @return [Integer]
494
+ # @example
495
+ # button_width = my_button.width
496
+ #
497
+ def width
498
+ obj, type = find_element(visible = false)
499
+ object_not_found_exception(obj, type)
500
+ obj.get_width
501
+ end
502
+
503
+ # Return height of object.
504
+ #
505
+ # @return [Integer]
506
+ # @example
507
+ # button_height = my_button.height
508
+ #
509
+ def height
510
+ obj, type = find_element(visible = false)
511
+ object_not_found_exception(obj, type)
512
+ obj.get_height
513
+ end
514
+
515
+ # Return x coordinate of object's location.
516
+ #
517
+ # @return [Integer]
518
+ # @example
519
+ # button_x = my_button.x
520
+ #
521
+ def x
522
+ obj, type = find_element(visible = false)
523
+ object_not_found_exception(obj, type)
524
+ obj.get_x
525
+ end
526
+
527
+ # Return y coordinate of object's location.
528
+ #
529
+ # @return [Integer]
530
+ # @example
531
+ # button_y = my_button.y
532
+ #
533
+ def y
534
+ obj, type = find_element(visible = false)
535
+ object_not_found_exception(obj, type)
536
+ obj.get_y
537
+ end
538
+
539
+ # Return UI object's title property
540
+ #
541
+ # @return [String]
542
+ # @example
543
+ # buy_now_button.title
544
+ #
545
+ def title
546
+ get_attribute(:title)
547
+ end
548
+
549
+ # Is UI object displayed in browser window?
550
+ #
551
+ # @return [Boolean]
552
+ # @example
553
+ # basket_link.displayed??
554
+ #
555
+ def displayed?
556
+ obj, type = find_element(visible = false)
557
+ object_not_found_exception(obj, type)
558
+ obj.displayed?
559
+ end
560
+
561
+ def get_value(visible = true)
562
+ obj, type = find_element(visible)
563
+ object_not_found_exception(obj, type)
564
+ text = case obj.tag_name.downcase
565
+ when 'input', 'select', 'textarea'
566
+ obj.value
567
+ else
568
+ obj.text
569
+ end
570
+ text.gsub(/[[:space:]]+/, ' ').strip unless text.nil?
571
+ end
572
+
573
+ alias get_caption get_value
574
+ alias caption get_value
575
+ alias value get_value
576
+
577
+ def verify_value(expected, enqueue = false)
578
+ actual = get_value
579
+ enqueue ?
580
+ ExceptionQueue.enqueue_assert_equal(expected.strip, actual.strip, "Expected UI #{object_ref_message}") :
581
+ assert_equal(expected.strip, actual.strip, "Expected UI #{object_ref_message} to display '#{expected}' but found '#{actual}'")
582
+ end
583
+
584
+ alias verify_caption verify_value
585
+
586
+ # Hover the cursor over an object
587
+ #
588
+ # @param visible [Boolean, Symbol] Only find elements with the specified visibility:
589
+ # * true - only finds visible elements.
590
+ # * false - finds invisible _and_ visible elements.
591
+ # * :all - same as false; finds visible and invisible elements.
592
+ # * :hidden - only finds invisible elements.
593
+ # * :visible - same as true; only finds visible elements.
594
+ # @example
595
+ # basket_link.hover
596
+ #
597
+ def hover(visible = true)
598
+ obj, type = find_element(visible)
599
+ object_not_found_exception(obj, type)
600
+ obj.hover
601
+ end
602
+
603
+ # Hover at a specific location within an object
604
+ #
605
+ # @param x [Integer] X offset
606
+ # @param y [Integer] Y offset
607
+ # @param visible [Boolean, Symbol] Only find elements with the specified visibility:
608
+ # * true - only finds visible elements.
609
+ # * false - finds invisible _and_ visible elements.
610
+ # * :all - same as false; finds visible and invisible elements.
611
+ # * :hidden - only finds invisible elements.
612
+ # * :visible - same as true; only finds visible elements.
613
+ # @example
614
+ # timeline_bar.hover_at(100, 5)
615
+ #
616
+ def hover_at(x, y, visible = true)
617
+ obj, = find_element(visible)
618
+ raise "UI #{object_ref_message} not found" unless obj
619
+ obj.hover_at(x, y)
620
+ end
621
+
622
+ def drag_by(right_offset, down_offset)
623
+ obj, type = find_element
624
+ object_not_found_exception(obj, type)
625
+ page.driver.browser.action.click_and_hold(obj.native).perform
626
+ sleep(1)
627
+ obj.drag_by(right_offset, down_offset)
628
+ end
629
+
630
+ def drag_and_drop(target, right_offset = nil, down_offset = nil)
631
+ source, type = find_element
632
+ object_not_found_exception(source, type)
633
+ page.driver.browser.action.click_and_hold(source.native).perform
634
+ sleep(1)
635
+ target_drop, = target.find_element
636
+ page.driver.browser.action.move_to(target_drop.native, right_offset.to_i, down_offset.to_i).release.perform
637
+ end
638
+
639
+ # Highlight an object with a 3 pixel wide, red dashed border for the specified wait time.
640
+ # If wait time is zero, then the highlight will remain until the page is refreshed
641
+ #
642
+ # @param duration [Integer or Float] wait time in seconds
643
+ # @example
644
+ # error_message.highlight(3)
645
+ #
646
+ def highlight(duration = 1)
647
+ obj, type = find_element
648
+ object_not_found_exception(obj, type)
649
+ # store original style so it can be reset later
650
+ @original_style = obj.native.attribute('style')
651
+ # style element with red border
652
+ page.execute_script(
653
+ 'arguments[0].setAttribute(arguments[1], arguments[2])',
654
+ obj,
655
+ 'style',
656
+ 'border: 3px solid red; border-style: dashed;'
657
+ )
658
+ # keep element highlighted for duration and then revert to original style
659
+ if duration.positive?
660
+ sleep duration
661
+ page.execute_script(
662
+ 'arguments[0].setAttribute(arguments[1], arguments[2])',
663
+ obj,
664
+ 'style',
665
+ @original_style
666
+ )
667
+ end
668
+ end
669
+
670
+ # Restore a highlighted object's original style
671
+ #
672
+ # @example
673
+ # store_link.unhighlight
674
+ #
675
+ def unhighlight
676
+ obj, type = find_element
677
+ object_not_found_exception(obj, type)
678
+ return if @original_style.nil?
679
+ page.execute_script(
680
+ 'arguments[0].setAttribute(arguments[1], arguments[2])',
681
+ obj,
682
+ 'style',
683
+ @original_style
684
+ )
685
+ end
686
+
687
+ # Return UI object's style property
688
+ #
689
+ # @return [String]
690
+ # @example
691
+ # buy_now_button.style
692
+ #
693
+ def style
694
+ get_attribute('style')
695
+ end
696
+
697
+ # Return state of UI object's role property
698
+ #
699
+ # @return [String]
700
+ # @example
701
+ # buy_now_button.role
702
+ #
703
+ def role
704
+ get_attribute('role')
705
+ end
706
+
707
+ # Return state of UI object's tabindex property
708
+ #
709
+ # @return [String]
710
+ # @example
711
+ # buy_now_button.tabindex
712
+ #
713
+ def tabindex
714
+ get_attribute('tabindex')
715
+ end
716
+
717
+ # Return state of UI object's aria-label property
718
+ #
719
+ # @return [String]
720
+ # @example
721
+ # buy_now_button.aria_label
722
+ #
723
+ def aria_label
724
+ get_attribute('aria-label')
725
+ end
726
+
727
+ # Return state of UI object's aria-labelledby property
728
+ #
729
+ # @return [String]
730
+ # @example
731
+ # buy_now_button.aria_labelledby
732
+ #
733
+ def aria_labelledby
734
+ get_attribute('aria-labelledby')
735
+ end
736
+
737
+ # Return state of UI object's aria-describedby property
738
+ #
739
+ # @return [String]
740
+ # @example
741
+ # buy_now_button.aria_describedby
742
+ #
743
+ def aria_describedby
744
+ get_attribute('aria-describedby')
745
+ end
746
+
747
+ # Return state of UI object's aria-live property
748
+ #
749
+ # @return [String]
750
+ # @example
751
+ # properties_list.aria_live
752
+ #
753
+ def aria_live
754
+ get_attribute('aria-live')
755
+ end
756
+
757
+ # Return state of UI object's aria-sort property
758
+ #
759
+ # @return [String]
760
+ # @example
761
+ # name_column.aria_sort
762
+ #
763
+ def aria_sort
764
+ get_attribute('aria-sort')
765
+ end
766
+
767
+ # Return state of UI object's aria-rowcount property
768
+ #
769
+ # @return [Integer]
770
+ # @example
771
+ # user_grid.aria_rowcount
772
+ #
773
+ def aria_rowcount
774
+ get_attribute('aria-rowcount')
775
+ end
776
+
777
+ # Return state of UI object's aria-colcount property
778
+ #
779
+ # @return [Integer]
780
+ # @example
781
+ # user_grid.aria_colcount
782
+ #
783
+ def aria_colcount
784
+ get_attribute('aria-colcount')
785
+ end
786
+
787
+ # Return state of UI object's aria-valuemax property
788
+ #
789
+ # @return [Integer]
790
+ # @example
791
+ # volume_slider.aria_valuemax
792
+ #
793
+ def aria_valuemax
794
+ get_attribute('aria-valuemax')
795
+ end
796
+
797
+ # Return state of UI object's aria-valuemin property
798
+ #
799
+ # @return [Integer]
800
+ # @example
801
+ # volume_slider.aria_valuemin
802
+ #
803
+ def aria_valuemin
804
+ get_attribute('aria-valuemin')
805
+ end
806
+
807
+ # Return state of UI object's aria-valuenow property
808
+ #
809
+ # @return [Integer]
810
+ # @example
811
+ # volume_slider.aria_valuenow
812
+ #
813
+ def aria_valuenow
814
+ get_attribute('aria-valuenow')
815
+ end
816
+
817
+ # Return state of UI object's aria-valuetext property
818
+ #
819
+ # @return [Integer]
820
+ # @example
821
+ # volume_slider.aria_valuetext
822
+ #
823
+ def aria_valuetext
824
+ get_attribute('aria-valuetext')
825
+ end
826
+
827
+ # Return state of UI object's aria-orientation property
828
+ #
829
+ # @return [Integer]
830
+ # @example
831
+ # volume_slider.aria_orientation
832
+ #
833
+ def aria_orientation
834
+ get_attribute('aria-orientation')
835
+ end
836
+
837
+ # Return state of UI object's aria-keyshortcuts property
838
+ #
839
+ # @return [Integer]
840
+ # @example
841
+ # play_button.aria_keyshortcuts
842
+ #
843
+ def aria_keyshortcuts
844
+ get_attribute('aria-keyshortcuts')
845
+ end
846
+
847
+ # Return state of UI object's aria-roledescription property
848
+ #
849
+ # @return [Integer]
850
+ # @example
851
+ # editor_button.aria_roledescription
852
+ #
853
+ def aria_roledescription
854
+ get_attribute('aria-roledescription')
855
+ end
856
+
857
+ # Return state of UI object's aria-autocomplete property
858
+ #
859
+ # @return [Integer]
860
+ # @example
861
+ # email_field.aria_autocomplete
862
+ #
863
+ def aria_autocomplete
864
+ get_attribute('aria-autocomplete')
865
+ end
866
+
867
+ # Return state of UI object's aria-controls property
868
+ #
869
+ # @return [Integer]
870
+ # @example
871
+ # video_menu.aria_controls
872
+ #
873
+ def aria_controls
874
+ get_attribute('aria-controls')
875
+ end
876
+
877
+ # Return state of UI object's aria-disabled property
878
+ #
879
+ # @return [Boolean]
880
+ # @example
881
+ # buy_now_button.aria_disabled?
882
+ #
883
+ def aria_disabled?
884
+ state = get_attribute('aria-disabled')
885
+ state.boolean? ? state : state == 'true'
886
+ end
887
+
888
+ # Return state of UI object's aria-selected property
889
+ #
890
+ # @return [Boolean]
891
+ # @example
892
+ # nutrition_info_tab.aria_selected?
893
+ #
894
+ def aria_selected?
895
+ state = get_attribute('aria-selected')
896
+ state.boolean? ? state : state == 'true'
897
+ end
898
+
899
+ # Return state of UI object's aria-hidden property
900
+ #
901
+ # @return [Boolean]
902
+ # @example
903
+ # nutrition_info_tab.aria_hidden?
904
+ #
905
+ def aria_hidden?
906
+ state = get_attribute('aria-hidden')
907
+ state.boolean? ? state : state == 'true'
908
+ end
909
+
910
+ # Return state of UI object's aria-expanded property
911
+ #
912
+ # @return [Boolean]
913
+ # @example
914
+ # catalog_tree.aria_expanded?
915
+ #
916
+ def aria_expanded?
917
+ state = get_attribute('aria-expanded')
918
+ state.boolean? ? state : state == 'true'
919
+ end
920
+
921
+ # Return state of UI object's aria-required property
922
+ #
923
+ # @return [Boolean]
924
+ # @example
925
+ # home_phone_field.aria_required?
926
+ #
927
+ def aria_required?
928
+ state = get_attribute('aria-required')
929
+ state.boolean? ? state : state == 'true'
930
+ end
931
+
932
+ # Return state of UI object's aria-invalid property
933
+ #
934
+ # @return [Boolean]
935
+ # @example
936
+ # home_phone_field.aria_invalid?
937
+ #
938
+ def aria_invalid?
939
+ state = get_attribute('aria-invalid')
940
+ state.boolean? ? state : state == 'true'
941
+ end
942
+
943
+ # Return state of UI object's aria-checked property
944
+ #
945
+ # @return [Boolean]
946
+ # @example
947
+ # allow_new_users_checkbox.aria_checked?
948
+ #
949
+ def aria_checked?
950
+ state = get_attribute('aria-checked')
951
+ state.boolean? ? state : state == 'true'
952
+ end
953
+
954
+ # Return state of UI object's aria-haspopup property
955
+ #
956
+ # @return [Boolean]
957
+ # @example
958
+ # user_avatar.aria_haspopup?
959
+ #
960
+ def aria_haspopup?
961
+ state = get_attribute('aria-haspopup')
962
+ state.boolean? ? state : state == 'true'
963
+ end
964
+
965
+ # Return state of UI object's aria-pressed property
966
+ #
967
+ # @return [Boolean]
968
+ # @example
969
+ # option1_button.aria_pressed?
970
+ #
971
+ def aria_pressed?
972
+ state = get_attribute('aria-pressed')
973
+ state.boolean? ? state : state == 'true'
974
+ end
975
+
976
+ # Return state of UI object's aria-readonly property
977
+ #
978
+ # @return [Boolean]
979
+ # @example
980
+ # home_phone_field.aria_readonly?
981
+ #
982
+ def aria_readonly?
983
+ state = get_attribute('aria-readonly')
984
+ state.boolean? ? state : state == 'true'
985
+ end
986
+
987
+ # Return state of UI object's aria-busy property
988
+ #
989
+ # @return [Boolean]
990
+ # @example
991
+ # home_phone_field.aria_busy?
992
+ #
993
+ def aria_busy?
994
+ state = get_attribute('aria-busy')
995
+ state.boolean? ? state : state == 'true'
996
+ end
997
+
998
+ # Return state of UI object's aria-modal property
999
+ #
1000
+ # @return [Boolean]
1001
+ # @example
1002
+ # add_user_modal.aria_modal?
1003
+ #
1004
+ def aria_modal?
1005
+ state = get_attribute('aria-modal')
1006
+ state.boolean? ? state : state == 'true'
1007
+ end
1008
+
1009
+ # Return state of UI object's aria-multiline property
1010
+ #
1011
+ # @return [Boolean]
1012
+ # @example
1013
+ # description_field.aria_multiline?
1014
+ #
1015
+ def aria_multiline?
1016
+ state = get_attribute('aria-multiline')
1017
+ state.boolean? ? state : state == 'true'
1018
+ end
1019
+
1020
+ # Return state of UI object's aria-multiselectable property
1021
+ #
1022
+ # @return [Boolean]
1023
+ # @example
1024
+ # channels_select.aria_multiselectable?
1025
+ #
1026
+ def aria_multiselectable?
1027
+ state = get_attribute('aria-multiselectable')
1028
+ state.boolean? ? state : state == 'true'
1029
+ end
1030
+
1031
+ # Return state of UI object's contenteditable property
1032
+ #
1033
+ # @return [Boolean]
1034
+ # @example
1035
+ # description_field.content_editable?
1036
+ #
1037
+ def content_editable?
1038
+ state = get_attribute('contenteditable')
1039
+ state.boolean? ? state : state == 'true'
1040
+ end
1041
+
1042
+ # Return crossorigin property
1043
+ #
1044
+ # @return crossorigin value
1045
+ # @example
1046
+ # with_creds = media_player.crossorigin == 'use-credentials'
1047
+ #
1048
+ def crossorigin
1049
+ obj, = find_element
1050
+ object_not_found_exception(obj, @type)
1051
+ obj.native.attribute('crossorigin')
1052
+ end
1053
+
1054
+ def get_attribute(attrib)
1055
+ obj, type = find_element(visible = false)
1056
+ object_not_found_exception(obj, type)
1057
+ obj[attrib]
1058
+ end
1059
+
1060
+ def get_native_attribute(attrib)
1061
+ reset_mru_cache
1062
+ obj, type = find_element(visible = false)
1063
+ object_not_found_exception(obj, type)
1064
+ obj.native.attribute(attrib)
1065
+ end
1066
+
1067
+ def find_element(visible = true)
1068
+ wait = Selenium::WebDriver::Wait.new(timeout: Capybara.default_max_wait_time)
1069
+ wait.until { find_object(visible) }
1070
+ end
1071
+
1072
+ private
1073
+
1074
+ def find_object(visible = true)
1075
+ obj_locator = @alt_locator.nil? ? @locator : @alt_locator
1076
+ parent_section = @context == :section && !@parent.get_locator.nil?
1077
+ tries ||= parent_section ? 2 : 1
1078
+ if parent_section && tries == 2
1079
+ parent_locator = @parent.get_locator
1080
+ parent_locator = parent_locator.tr('|', ' ')
1081
+ if @mru_locator == obj_locator && @mru_parent == parent_locator && !@mru_object.nil?
1082
+ return [@mru_object, @locator_type]
1083
+ end
1084
+
1085
+ parent_locator_type = @parent.get_locator_type
1086
+ obj = page.find(
1087
+ parent_locator_type,
1088
+ parent_locator,
1089
+ visible: :all,
1090
+ wait: 0.01
1091
+ ).find(
1092
+ @locator_type,
1093
+ obj_locator,
1094
+ wait: 0.01,
1095
+ visible: visible
1096
+ )
1097
+ else
1098
+ return [@mru_object, @locator_type] if @mru_locator == obj_locator && !@mru_object.nil?
1099
+
1100
+ obj = page.find(@locator_type, obj_locator, wait: 0.01, visible: visible)
1101
+ end
1102
+ @mru_object = obj
1103
+ @mru_locator = obj_locator
1104
+ @mru_parent = parent_locator
1105
+ [obj, @locator_type]
1106
+ rescue StandardError
1107
+ retry if (tries -= 1).positive?
1108
+ [nil, nil]
1109
+ end
1110
+
1111
+ def object_not_found_exception(obj, obj_type)
1112
+ locator = @alt_locator.nil? ? @locator : @alt_locator
1113
+ object_type = obj_type.nil? ? 'Object' : obj_type
1114
+ raise ObjectNotFoundError, "#{object_type} named '#{@name}' (#{locator}) not found" unless obj
1115
+ end
1116
+
1117
+ def invalid_object_type_exception(obj, obj_type)
1118
+ unless obj.tag_name == obj_type || obj.native.attribute('type') == obj_type
1119
+ locator = @alt_locator.nil? ? @locator : @alt_locator
1120
+ raise "#{locator} is not a #{obj_type} element"
1121
+ end
1122
+ end
1123
+
1124
+ def object_ref_message
1125
+ "object '#{get_name}' (#{get_locator})"
1126
+ end
1127
+
1128
+ def compare(expected, actual)
1129
+ if expected.is_a?(Hash) && expected.length == 1
1130
+ expected.each do |key, value|
1131
+ case key
1132
+ when :lt, :less_than
1133
+ actual < value
1134
+ when :lt_eq, :less_than_or_equal
1135
+ actual <= value
1136
+ when :gt, :greater_than
1137
+ actual > value
1138
+ when :gt_eq, :greater_than_or_equal
1139
+ actual >= value
1140
+ when :starts_with
1141
+ actual.start_with?(value)
1142
+ when :ends_with
1143
+ actual.end_with?(value)
1144
+ when :contains
1145
+ actual.include?(value)
1146
+ when :not_contains, :does_not_contain
1147
+ !actual.include?(value)
1148
+ when :not_equal
1149
+ actual != value
1150
+ end
1151
+ end
1152
+ else
1153
+ expected == actual
1154
+ end
1155
+ end
1156
+
1157
+ def find_component(component, component_name)
1158
+ begin
1159
+ element = @base_object.find(:css, component, visible: :all, wait: 1)
1160
+ rescue
1161
+ begin
1162
+ element = page.find(:css, component, visible: :all, wait: 2)
1163
+ rescue
1164
+ raise "Component #{component_name} (#{component}) for #{@type} named '#{@name}' (#{locator}) not found"
1165
+ end
1166
+ end
1167
+ element
1168
+ end
1169
+ end
1170
+ end
1171
+ end