watir 6.16.5 → 6.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +32 -107
  3. data/.travis.yml +24 -21
  4. data/CHANGES.md +16 -0
  5. data/Gemfile +3 -1
  6. data/Rakefile +3 -3
  7. data/appveyor.yml +2 -1
  8. data/lib/watir-webdriver.rb +1 -1
  9. data/lib/watir.rb +0 -1
  10. data/lib/watir/adjacent.rb +8 -10
  11. data/lib/watir/after_hooks.rb +4 -4
  12. data/lib/watir/browser.rb +5 -1
  13. data/lib/watir/capabilities.rb +9 -6
  14. data/lib/watir/cookies.rb +1 -1
  15. data/lib/watir/elements/element.rb +59 -46
  16. data/lib/watir/elements/file_field.rb +1 -0
  17. data/lib/watir/elements/iframe.rb +2 -2
  18. data/lib/watir/elements/link.rb +0 -9
  19. data/lib/watir/elements/radio.rb +1 -1
  20. data/lib/watir/elements/select.rb +2 -2
  21. data/lib/watir/generator/base/spec_extractor.rb +4 -4
  22. data/lib/watir/js_execution.rb +1 -1
  23. data/lib/watir/legacy_wait.rb +1 -1
  24. data/lib/watir/locators/element/selector_builder.rb +11 -12
  25. data/lib/watir/locators/element/selector_builder/xpath.rb +40 -13
  26. data/lib/watir/locators/text_field/selector_builder/xpath.rb +3 -1
  27. data/lib/watir/logger.rb +5 -2
  28. data/lib/watir/version.rb +1 -1
  29. data/lib/watir/window.rb +1 -1
  30. data/lib/watirspec.rb +1 -1
  31. data/lib/watirspec/guards.rb +1 -1
  32. data/lib/watirspec/rake_tasks.rb +2 -0
  33. data/lib/watirspec/runner.rb +4 -0
  34. data/spec/unit/container_spec.rb +1 -1
  35. data/spec/unit/logger_spec.rb +5 -7
  36. data/spec/unit/selector_builder/button_spec.rb +16 -15
  37. data/spec/unit/selector_builder/element_spec.rb +58 -9
  38. data/spec/unit/selector_builder/text_field_spec.rb +14 -14
  39. data/spec/watirspec/after_hooks_spec.rb +64 -78
  40. data/spec/watirspec/alert_spec.rb +69 -79
  41. data/spec/watirspec/browser_spec.rb +48 -46
  42. data/spec/watirspec/cookies_spec.rb +52 -37
  43. data/spec/watirspec/drag_and_drop_spec.rb +14 -38
  44. data/spec/watirspec/elements/button_spec.rb +2 -0
  45. data/spec/watirspec/elements/buttons_spec.rb +1 -1
  46. data/spec/watirspec/elements/checkbox_spec.rb +8 -4
  47. data/spec/watirspec/elements/date_field_spec.rb +18 -9
  48. data/spec/watirspec/elements/date_time_field_spec.rb +3 -4
  49. data/spec/watirspec/elements/div_spec.rb +62 -54
  50. data/spec/watirspec/elements/element_spec.rb +60 -78
  51. data/spec/watirspec/elements/elements_spec.rb +12 -3
  52. data/spec/watirspec/elements/filefield_spec.rb +25 -50
  53. data/spec/watirspec/elements/form_spec.rb +6 -8
  54. data/spec/watirspec/elements/frame_spec.rb +10 -13
  55. data/spec/watirspec/elements/iframe_spec.rb +17 -12
  56. data/spec/watirspec/elements/iframes_spec.rb +2 -2
  57. data/spec/watirspec/elements/link_spec.rb +18 -9
  58. data/spec/watirspec/elements/links_spec.rb +11 -3
  59. data/spec/watirspec/elements/option_spec.rb +15 -17
  60. data/spec/watirspec/elements/select_list_spec.rb +66 -80
  61. data/spec/watirspec/elements/text_field_spec.rb +8 -4
  62. data/spec/watirspec/elements/tr_spec.rb +0 -9
  63. data/spec/watirspec/html/forms_with_input_elements.html +1 -0
  64. data/spec/watirspec/html/iframes.html +3 -0
  65. data/spec/watirspec/html/non_control_elements.html +4 -4
  66. data/spec/watirspec/html/right_click.html +12 -0
  67. data/spec/watirspec/html/wait.html +1 -1
  68. data/spec/watirspec/relaxed_locate_spec.rb +16 -20
  69. data/spec/watirspec/support/rspec_matchers.rb +7 -6
  70. data/spec/watirspec/user_editable_spec.rb +1 -1
  71. data/spec/watirspec/wait_spec.rb +13 -17
  72. data/spec/watirspec/window_switching_spec.rb +162 -163
  73. data/spec/watirspec_helper.rb +7 -5
  74. data/watir.gemspec +4 -5
  75. metadata +14 -16
  76. data/lib/watir/elements/area.rb +0 -10
@@ -61,7 +61,7 @@ module Watir
61
61
  cookie[:path] = opts[:path] if opts.key?(:path)
62
62
  expires = opts[:expires]
63
63
  if expires
64
- cookie[:expires] = ::Time.parse(expires) if expires.is_a?(String) else expires
64
+ cookie[:expires] = expires.is_a?(String) ? ::Time.parse(expires) : expires
65
65
  end
66
66
  cookie[:domain] = opts[:domain] if opts.key?(:domain)
67
67
 
@@ -18,6 +18,17 @@ module Watir
18
18
  attr_accessor :keyword
19
19
  attr_reader :selector
20
20
 
21
+ # https://www.w3.org/TR/html52/single-page.html#casesensitivity
22
+ CASE_INSENSITIVE_ATTRIBUTES = %i[accept accept_charset align alink axis
23
+ bgcolor charset checked clear codetype
24
+ color compact declare defer dir direction
25
+ disabled enctype face frame hreflang
26
+ http_equiv lang language link media
27
+ method multiple nohref noresize noshade
28
+ nowrap readonly rel rev rules scope
29
+ scrolling selected shape target text
30
+ type valign valuetype vlink].freeze
31
+
21
32
  #
22
33
  # temporarily add :id and :class_name manually since they're no longer specified in the HTML spec.
23
34
  #
@@ -54,12 +65,9 @@ module Watir
54
65
 
55
66
  def exists?
56
67
  if located? && stale?
57
- Watir.logger.deprecate 'Checking `#exists? == false` to determine a stale element',
58
- '`#stale? == true`',
59
- reference: 'http://watir.com/staleness-changes',
60
- ids: [:stale_exists]
61
- # TODO: Change this to `reset!` after removing deprecation
62
- return false
68
+ reset!
69
+ elsif located?
70
+ return true
63
71
  end
64
72
 
65
73
  assert_exists
@@ -196,15 +204,35 @@ module Watir
196
204
  end
197
205
 
198
206
  #
199
- # Right clicks the element.
200
- # Note that browser support may vary.
207
+ # Right clicks the element, optionally while pressing the given modifier keys.
208
+ # Note that support for holding a modifier key is currently experimental,
209
+ # and may not work at all. Also, the browser support may vary.
201
210
  #
202
211
  # @example
203
212
  # browser.element(name: "new_user_button").right_click
204
213
  #
214
+ # @example Right click an element with shift key pressed
215
+ # browser.element(name: "new_user_button").right_click(:shift)
216
+ #
217
+ # @example Click an element with several modifier keys pressed
218
+ # browser.element(name: "new_user_button").right_click(:shift, :alt)
219
+ #
220
+ # @param [:shift, :alt, :control, :command, :meta] modifiers to press while right clicking.
221
+ #
222
+
223
+ def right_click(*modifiers)
224
+ element_call(:wait_for_present) do
225
+ action = driver.action
226
+ if modifiers.any?
227
+ modifiers.each { |mod| action.key_down mod }
228
+ action.context_click(@element)
229
+ modifiers.each { |mod| action.key_up mod }
230
+ action.perform
231
+ else
232
+ action.context_click(@element).perform
233
+ end
234
+ end
205
235
 
206
- def right_click
207
- element_call(:wait_for_present) { driver.action.context_click(@element).perform }
208
236
  browser.after_hooks.run
209
237
  end
210
238
 
@@ -247,7 +275,7 @@ module Watir
247
275
  # Note that browser support may vary.
248
276
  #
249
277
  # @example
250
- # browser.div(id: "draggable").drag_and_drop_by 100, -200
278
+ # browser.div(id: "draggable").drag_and_drop_by 100, 25
251
279
  #
252
280
  # @param [Integer] right_by
253
281
  # @param [Integer] down_by
@@ -358,6 +386,8 @@ module Watir
358
386
  #
359
387
 
360
388
  def scroll_into_view
389
+ Watir.logger.deprecate 'Element#scroll_into_view', 'Element#scroll methods', ids: [:scroll_into_view]
390
+
361
391
  element_call { @element.location_once_scrolled_into_view }
362
392
  end
363
393
 
@@ -460,16 +490,7 @@ module Watir
460
490
  msg = '#visible? behavior will be changing slightly, consider switching to #present? ' \
461
491
  '(more details: http://watir.com/element-existentialism/)'
462
492
  Watir.logger.warn msg, ids: [:visible_element]
463
- displayed = display_check
464
- if displayed.nil? && display_check
465
- Watir.logger.deprecate 'Checking `#visible? == false` to determine a stale element',
466
- '`#stale? == true`',
467
- reference: 'http://watir.com/staleness-changes',
468
- ids: [:stale_visible]
469
- end
470
- raise unknown_exception if displayed.nil?
471
-
472
- displayed
493
+ display_check
473
494
  end
474
495
 
475
496
  #
@@ -492,14 +513,7 @@ module Watir
492
513
  #
493
514
 
494
515
  def present?
495
- displayed = display_check
496
- if displayed.nil? && display_check
497
- Watir.logger.deprecate 'Checking `#present? == false` to determine a stale element',
498
- '`#stale? == true`',
499
- reference: 'http://watir.com/staleness-changes',
500
- ids: [:stale_present]
501
- end
502
- displayed
516
+ display_check
503
517
  rescue UnknownObjectException, UnknownFrameException
504
518
  false
505
519
  end
@@ -547,7 +561,7 @@ module Watir
547
561
  #
548
562
 
549
563
  def to_subtype
550
- tag = tag_name()
564
+ tag = tag_name
551
565
  klass = if tag == 'input'
552
566
  case attribute_value(:type)
553
567
  when 'checkbox'
@@ -595,7 +609,7 @@ module Watir
595
609
  def stale_in_context?
596
610
  @element.css_value('staleness_check') # any wire call will check for staleness
597
611
  false
598
- rescue Selenium::WebDriver::Error::ObsoleteElementError
612
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError
599
613
  true
600
614
  end
601
615
 
@@ -720,7 +734,9 @@ module Watir
720
734
  end
721
735
 
722
736
  def ensure_context
723
- @query_scope.locate if @query_scope.is_a?(Browser) || @query_scope.located? && @query_scope.stale?
737
+ if @query_scope.is_a?(Browser) || !@query_scope.located? || @query_scope.located? && @query_scope.stale?
738
+ @query_scope.locate
739
+ end
724
740
  @query_scope.switch_to! if @query_scope.is_a?(IFrame)
725
741
  end
726
742
 
@@ -770,26 +786,25 @@ module Watir
770
786
  @element.displayed?
771
787
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
772
788
  reset!
773
- nil
789
+ retry
774
790
  end
775
791
 
776
792
  # TODO: - this will get addressed with Watir::Executor implementation
777
793
  # rubocop:disable Metrics/AbcSize
778
794
  # rubocop:disable Metrics/MethodLength
779
- # rubocop:disable Metrics/PerceivedComplexity
780
795
  # rubocop:disable Metrics/CyclomaticComplexity:
781
796
  def element_call(precondition = nil, &block)
782
797
  caller = caller_locations(1, 1)[0].label
783
- already_locked = Wait.timer.locked?
784
- Wait.timer = Wait::Timer.new(timeout: Watir.default_timeout) unless already_locked
798
+ already_locked = browser.timer.locked?
799
+ browser.timer = Wait::Timer.new(timeout: Watir.default_timeout) unless already_locked
785
800
 
786
801
  begin
787
802
  check_condition(precondition, caller)
788
803
  Watir.logger.debug "-> `Executing #{inspect}##{caller}`"
789
804
  yield
790
- rescue unknown_exception => ex
805
+ rescue unknown_exception => e
791
806
  element_call(:wait_for_exists, &block) if precondition.nil?
792
- msg = ex.message
807
+ msg = e.message
793
808
  msg += '; Maybe look in an iframe?' if @query_scope.iframe.exists?
794
809
  custom_attributes = @locator.nil? ? [] : selector_builder.custom_attributes
795
810
  unless custom_attributes.empty?
@@ -799,24 +814,22 @@ module Watir
799
814
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
800
815
  reset!
801
816
  retry
802
- rescue Selenium::WebDriver::Error::ElementNotVisibleError, Selenium::WebDriver::Error::ElementNotInteractableError
803
- raise_present unless Wait.timer.remaining_time.positive?
817
+ # TODO: - InvalidElementStateError is deprecated, so no longer calling `raise_disabled`
818
+ # need a better way to handle this
819
+ rescue Selenium::WebDriver::Error::ElementNotInteractableError
820
+ raise_present unless browser.timer.remaining_time.positive?
804
821
  raise_present unless %i[wait_for_present wait_for_enabled wait_for_writable].include?(precondition)
805
822
  retry
806
- rescue Selenium::WebDriver::Error::InvalidElementStateError
807
- raise_disabled unless Wait.timer.remaining_time.positive?
808
- raise_disabled unless %i[wait_for_present wait_for_enabled wait_for_writable].include?(precondition)
809
- retry
810
823
  rescue Selenium::WebDriver::Error::NoSuchWindowError
811
824
  raise NoMatchingWindowFoundException, 'browser window was closed'
812
825
  ensure
813
826
  Watir.logger.debug "<- `Completed #{inspect}##{caller}`"
814
- Wait.timer.reset! unless already_locked
827
+ browser.timer.reset! unless already_locked
815
828
  end
816
829
  end
817
830
  # rubocop:enable Metrics/AbcSize
818
831
  # rubocop:enable Metrics/MethodLength
819
- # rubocop:enable Metrics/PerceivedComplexity
832
+
820
833
  # rubocop:enable Metrics/CyclomaticComplexity:
821
834
 
822
835
  def check_condition(condition, caller)
@@ -12,6 +12,7 @@ module Watir
12
12
 
13
13
  self.value = path
14
14
  end
15
+ alias upload set
15
16
 
16
17
  #
17
18
  # Sets the file field to the given path
@@ -134,8 +134,8 @@ module Watir
134
134
  @element
135
135
  end
136
136
 
137
- def respond_to_missing?(meth, _include_private = false)
138
- @driver.respond_to?(meth) || @element.respond_to?(meth) || super
137
+ def respond_to_missing?(meth, _include_private)
138
+ @driver.respond_to?(meth) || @element.respond_to?(meth) || super(meth, false)
139
139
  end
140
140
 
141
141
  def method_missing(meth, *args, &blk)
@@ -3,13 +3,4 @@ module Watir
3
3
  alias link a
4
4
  alias links as
5
5
  end # Container
6
-
7
- class Anchor < HTMLElement
8
- #
9
- # @todo temporarily add href attribute
10
- #
11
- # @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=23192
12
- #
13
- attribute String, :href, :href
14
- end # Anchor
15
6
  end # Watir
@@ -33,7 +33,7 @@ module Watir
33
33
  #
34
34
 
35
35
  def text
36
- l = label()
36
+ l = label
37
37
  l.exist? ? l.text : ''
38
38
  end
39
39
  end # Radio
@@ -170,8 +170,8 @@ module Watir
170
170
  str_or_rx.inspect.sub('\\A', '^')
171
171
  .sub('\\Z', '$')
172
172
  .sub('\\z', '$')
173
- .sub(%r{^\/}, '')
174
- .sub(%r{\/[a-z]*$}, '')
173
+ .sub(%r{^/}, '')
174
+ .sub(%r{/[a-z]*$}, '')
175
175
  .gsub(/\(\?#.+\)/, '')
176
176
  .gsub(/\(\?-\w+:/, '(')
177
177
  else
@@ -109,8 +109,8 @@ module Watir
109
109
  begin
110
110
  intf = fetch_interface(implementor_name).first
111
111
  intf.implements << fetch_interface(implementee_name).first
112
- rescue InterfaceNotFound => ex
113
- puts ex.message
112
+ rescue InterfaceNotFound => e
113
+ puts e.message
114
114
  end
115
115
  end
116
116
  end
@@ -123,8 +123,8 @@ module Watir
123
123
  begin
124
124
  intf = fetch_interface(includer_name).first
125
125
  intf.includes << fetch_interface(includee_name).first
126
- rescue InterfaceNotFound => ex
127
- puts ex.message
126
+ rescue InterfaceNotFound => e
127
+ puts e.message
128
128
  end
129
129
  end
130
130
  end
@@ -54,7 +54,7 @@ module Watir
54
54
  long: {flashes: 5, delay: 0.5},
55
55
  rainbow: {flashes: 5, color: %w[red orange yellow green blue indigo violet]}
56
56
  }
57
- return flash(presets[preset]) unless presets[preset].nil?
57
+ return flash(**presets[preset]) unless presets[preset].nil?
58
58
 
59
59
  background_color = original_color = style('background-color')
60
60
  background_color = 'white' if background_color.empty?
@@ -10,7 +10,7 @@ module Watir
10
10
  end
11
11
 
12
12
  def respond_to_missing?(*args)
13
- @element.respond_to?(*args)
13
+ @element.respond_to?(*args) || super
14
14
  end
15
15
 
16
16
  def method_missing(method, *args, &block)
@@ -6,11 +6,10 @@ module Watir
6
6
  attr_reader :custom_attributes, :built
7
7
 
8
8
  WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/.freeze
9
- INTEGER_CLASS = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4') ? Fixnum : Integer
10
9
  VALID_WHATS = Hash.new([String, Regexp, TrueClass, FalseClass]).merge(adjacent: [::Symbol],
11
10
  xpath: [String],
12
11
  css: [String],
13
- index: [INTEGER_CLASS],
12
+ index: [Integer],
14
13
  visible: [TrueClass, FalseClass],
15
14
  tag_name: [String, Regexp, ::Symbol],
16
15
  visible_text: [String, Regexp],
@@ -53,11 +52,7 @@ module Watir
53
52
  if @selector.key?(:class) || @selector.key?(:class_name)
54
53
  classes = ([@selector[:class]].flatten + [@selector.delete(:class_name)].flatten).compact
55
54
 
56
- classes.each do |class_name|
57
- next unless class_name.is_a?(String) && class_name.strip.include?(' ')
58
-
59
- deprecate_class_array(class_name)
60
- end
55
+ deprecate_class_array(classes)
61
56
 
62
57
  @selector[:class] = classes
63
58
  end
@@ -83,11 +78,15 @@ module Watir
83
78
  scope_invalid_locators.empty?
84
79
  end
85
80
 
86
- def deprecate_class_array(class_name)
87
- dep = "Using the :class locator to locate multiple classes with a String value (i.e. \"#{class_name}\")"
88
- Watir.logger.deprecate dep,
89
- "Array (e.g. #{class_name.split})",
90
- ids: [:class_array]
81
+ def deprecate_class_array(class_array)
82
+ class_array.each do |class_name|
83
+ next unless class_name.is_a?(String) && class_name.strip.include?(' ')
84
+
85
+ dep = "Using the :class locator to locate multiple classes with a String value (i.e. \"#{class_name}\")"
86
+ Watir.logger.deprecate dep,
87
+ "Array (e.g. #{class_name.split})",
88
+ ids: [:class_array]
89
+ end
91
90
  end
92
91
 
93
92
  def check_type(how, what)
@@ -12,6 +12,7 @@ module Watir
12
12
 
13
13
  def build(selector)
14
14
  @selector = selector
15
+ @valid_attributes = build_valid_attributes
15
16
 
16
17
  @built = (@selector.keys & CAN_NOT_BUILD).each_with_object({}) do |key, hash|
17
18
  hash[key] = @selector.delete(key)
@@ -45,9 +46,10 @@ module Watir
45
46
  end
46
47
 
47
48
  def predicate_expression(key, val)
48
- if val.eql? true
49
+ case val
50
+ when true
49
51
  attribute_presence(key)
50
- elsif val.eql? false
52
+ when false
51
53
  attribute_absence(key)
52
54
  else
53
55
  equal_pair(key, val)
@@ -55,11 +57,9 @@ module Watir
55
57
  end
56
58
 
57
59
  def predicate_conversion(key, regexp)
58
- # type attributes can be upper case - downcase them
59
- # https://github.com/watir/watir/issues/72
60
- downcase = key == :type || regexp.casefold?
60
+ downcase = case_insensitive_attribute?(key) || regexp.casefold?
61
61
 
62
- lhs = lhs_for(key, downcase)
62
+ lhs = lhs_for(key, downcase: downcase)
63
63
 
64
64
  results = RegexpDisassembler.new(regexp).substrings
65
65
 
@@ -72,8 +72,10 @@ module Watir
72
72
 
73
73
  add_to_matching(key, regexp, results)
74
74
 
75
- results.map { |substring|
76
- "contains(#{lhs}, '#{substring}')"
75
+ results.map { |rhs|
76
+ rhs = "'#{rhs}'"
77
+ rhs = XpathSupport.downcase(rhs) if downcase
78
+ "contains(#{lhs}, #{rhs})"
77
79
  }.flatten.compact.join(' and ')
78
80
  end
79
81
 
@@ -200,7 +202,7 @@ module Watir
200
202
  regexp.casefold? ? !results.first.casecmp(regexp.source).zero? : results.first != regexp.source
201
203
  end
202
204
 
203
- def lhs_for(key, downcase = false)
205
+ def lhs_for(key, downcase: false)
204
206
  case key
205
207
  when String
206
208
  "@#{key}"
@@ -211,7 +213,7 @@ module Watir
211
213
  when :text
212
214
  'normalize-space()'
213
215
  when :contains_text
214
- 'text()'
216
+ 'normalize-space()'
215
217
  when ::Symbol
216
218
  lhs = "@#{key.to_s.tr('_', '-')}"
217
219
  downcase ? XpathSupport.downcase(lhs) : lhs
@@ -221,11 +223,11 @@ module Watir
221
223
  end
222
224
 
223
225
  def attribute_presence(attribute)
224
- lhs_for(attribute, false)
226
+ lhs_for(attribute)
225
227
  end
226
228
 
227
229
  def attribute_absence(attribute)
228
- lhs = lhs_for(attribute, false)
230
+ lhs = lhs_for(attribute)
229
231
  "not(#{lhs})"
230
232
  end
231
233
 
@@ -236,9 +238,34 @@ module Watir
236
238
 
237
239
  negate_xpath ? "not(#{expression})" : expression
238
240
  else
239
- "#{lhs_for(key, key == :type)}=#{XpathSupport.escape value}"
241
+ downcase = case_insensitive_attribute?(key)
242
+
243
+ lhs = lhs_for(key, downcase: downcase)
244
+ rhs = XpathSupport.escape(value)
245
+ rhs = XpathSupport.downcase(rhs) if downcase
246
+
247
+ "#{lhs}=#{rhs}"
248
+ end
249
+ end
250
+
251
+ def build_valid_attributes
252
+ tag_name = @selector[:tag_name]
253
+ if tag_name.is_a?(String) && Watir.tag_to_class[tag_name.to_sym]
254
+ Watir.tag_to_class[tag_name.to_sym].attribute_list
255
+ else
256
+ Watir::HTMLElement.attribute_list
240
257
  end
241
258
  end
259
+
260
+ def case_insensitive_attribute?(attribute)
261
+ # type attributes can be upper case - downcase them
262
+ # https://github.com/watir/watir/issues/72
263
+ return true if attribute == :type
264
+ return true if Watir::Element::CASE_INSENSITIVE_ATTRIBUTES.include?(attribute) &&
265
+ @valid_attributes.include?(attribute)
266
+
267
+ false
268
+ end
242
269
  end
243
270
  end
244
271
  end