testcentricity_web 4.3.1 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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