watir 6.19.1 → 7.0.0.beta1

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/actions/install-chrome/action.yml +1 -0
  3. data/.github/actions/setup-linux/action.yml +8 -0
  4. data/.github/workflows/tests.yml +100 -0
  5. data/CHANGES.md +7 -0
  6. data/README.md +1 -4
  7. data/Rakefile +1 -1
  8. data/lib/watir.rb +1 -45
  9. data/lib/watir/alert.rb +3 -8
  10. data/lib/watir/capabilities.rb +54 -230
  11. data/lib/watir/cell_container.rb +4 -4
  12. data/lib/watir/container.rb +4 -26
  13. data/lib/watir/elements/checkbox.rb +4 -4
  14. data/lib/watir/elements/date_field.rb +4 -4
  15. data/lib/watir/elements/date_time_field.rb +4 -4
  16. data/lib/watir/elements/element.rb +12 -49
  17. data/lib/watir/elements/file_field.rb +4 -4
  18. data/lib/watir/elements/font.rb +4 -4
  19. data/lib/watir/elements/hidden.rb +4 -4
  20. data/lib/watir/elements/html_elements.rb +444 -445
  21. data/lib/watir/elements/iframe.rb +4 -4
  22. data/lib/watir/elements/radio.rb +4 -4
  23. data/lib/watir/elements/select.rb +12 -78
  24. data/lib/watir/elements/svg_elements.rb +96 -96
  25. data/lib/watir/elements/text_field.rb +4 -4
  26. data/lib/watir/generator/base/generator.rb +4 -4
  27. data/lib/watir/generator/base/visitor.rb +0 -29
  28. data/lib/watir/generator/html/generator.rb +2 -1
  29. data/lib/watir/has_window.rb +4 -4
  30. data/lib/watir/http_client.rb +0 -8
  31. data/lib/watir/locators.rb +1 -5
  32. data/lib/watir/locators/button/matcher.rb +0 -23
  33. data/lib/watir/locators/button/selector_builder/xpath.rb +4 -15
  34. data/lib/watir/locators/element/matcher.rb +4 -19
  35. data/lib/watir/locators/element/selector_builder.rb +2 -37
  36. data/lib/watir/locators/element/selector_builder/xpath.rb +26 -41
  37. data/lib/watir/radio_set.rb +2 -2
  38. data/lib/watir/row_container.rb +4 -4
  39. data/lib/watir/version.rb +1 -1
  40. data/lib/watir/wait.rb +4 -74
  41. data/lib/watir/window.rb +6 -22
  42. data/lib/watir/window_collection.rb +5 -49
  43. data/lib/watirspec/implementation.rb +4 -0
  44. data/spec/spec_helper.rb +2 -7
  45. data/spec/unit/capabilities_spec.rb +196 -929
  46. data/spec/unit/match_elements/button_spec.rb +0 -13
  47. data/spec/unit/match_elements/element_spec.rb +38 -47
  48. data/spec/unit/match_elements/text_field_spec.rb +6 -6
  49. data/spec/unit/selector_builder/element_spec.rb +6 -23
  50. data/spec/unit/selector_builder/text_field_spec.rb +6 -7
  51. data/spec/watirspec/alert_spec.rb +4 -21
  52. data/spec/watirspec/browser_spec.rb +2 -2
  53. data/spec/watirspec/cookies_spec.rb +1 -1
  54. data/spec/watirspec/elements/button_spec.rb +0 -10
  55. data/spec/watirspec/elements/checkbox_spec.rb +10 -22
  56. data/spec/watirspec/elements/div_spec.rb +4 -34
  57. data/spec/watirspec/elements/divs_spec.rb +2 -2
  58. data/spec/watirspec/elements/element_spec.rb +38 -84
  59. data/spec/watirspec/elements/form_spec.rb +2 -4
  60. data/spec/watirspec/elements/links_spec.rb +4 -4
  61. data/spec/watirspec/elements/select_list_spec.rb +7 -108
  62. data/spec/watirspec/elements/span_spec.rb +2 -2
  63. data/spec/watirspec/elements/spans_spec.rb +1 -1
  64. data/spec/watirspec/elements/strong_spec.rb +1 -1
  65. data/spec/watirspec/html/non_control_elements.html +8 -3
  66. data/spec/watirspec/support/rspec_matchers.rb +1 -32
  67. data/spec/watirspec/window_switching_spec.rb +2 -49
  68. data/spec/watirspec_helper.rb +6 -1
  69. data/watir.gemspec +3 -4
  70. metadata +11 -32
  71. data/.github/workflows/linux.yml +0 -61
  72. data/.github/workflows/mac.yml +0 -55
  73. data/.github/workflows/unit.yml +0 -37
  74. data/.github/workflows/windows.yml +0 -39
  75. data/lib/watir/legacy_wait.rb +0 -123
  76. data/spec/unit/container_spec.rb +0 -35
  77. data/spec/watirspec/legacy_wait_spec.rb +0 -216
@@ -18,12 +18,12 @@ module Watir
18
18
  end # TextField
19
19
 
20
20
  module Container
21
- def text_field(*args)
22
- TextField.new(self, extract_selector(args).merge(tag_name: 'input'))
21
+ def text_field(opts = {})
22
+ TextField.new(self, opts.merge(tag_name: 'input'))
23
23
  end
24
24
 
25
- def text_fields(*args)
26
- TextFieldCollection.new(self, extract_selector(args).merge(tag_name: 'input'))
25
+ def text_fields(opts = {})
26
+ TextFieldCollection.new(self, opts.merge(tag_name: 'input'))
27
27
  end
28
28
  end # Container
29
29
 
@@ -84,12 +84,12 @@ module Watir
84
84
  @io.puts indent(<<~CODE, 2)
85
85
 
86
86
  # @return [#{element_class}]
87
- def #{singular}(*args)
88
- #{element_class}.new(self, extract_selector(args).merge(tag_name: #{tag_string}))
87
+ def #{singular}(opts = {})
88
+ #{element_class}.new(self, opts.merge(tag_name: #{tag_string}))
89
89
  end
90
90
  # @return [#{collection_class}]
91
- def #{plural}(*args)
92
- #{collection_class}.new(self, extract_selector(args).merge(tag_name: #{tag_string}))
91
+ def #{plural}(opts = {})
92
+ #{collection_class}.new(self, opts.merge(tag_name: #{tag_string}))
93
93
  end
94
94
  Watir.tag_to_class[#{tag.to_sym.inspect}] = #{element_class}
95
95
 
@@ -54,35 +54,6 @@ module Watir
54
54
  # ignored
55
55
  end
56
56
 
57
- # TODO: do everything in the visitor somehow?
58
- # problem is we lack the tag name info while walking the interface AST
59
- # #
60
- # # Watir generator visitor interface
61
- # #
62
- #
63
- # def visit_tag(tag_name, interface_name)
64
- # tag_string = tag.inspect
65
- # singular = Util.paramify(classify_regexp, tag)
66
- # plural = singular.pluralize
67
- # element_class = Util.classify(classify_regexp, interfaces.first.name)
68
- # collection_class = "#{element_class}Collection"
69
- #
70
- # [:defn,
71
- # :a,
72
- # [:args, :"*args"],
73
- # [:scope,
74
- # [:block,
75
- # [:call,
76
- # [:const, :Anchor],
77
- # :new,
78
- # [:arglist,
79
- # [:self],
80
- # [:call,
81
- # [:call, nil, :extract_selector, [:arglist, [:lvar, :args]]],
82
- # :merge,
83
- # [:arglist, [:hash, [:lit, :tag_name], [:str, "a"]]]]]]]]]
84
- # end
85
-
86
57
  private
87
58
 
88
59
  def element_class(name, attributes, parent)
@@ -14,7 +14,8 @@ module Watir
14
14
  end
15
15
 
16
16
  def ignored_attributes
17
- %w[cells elements hash rows span text size selected? style width height tHead tFoot link options selected]
17
+ %w[cells elements hash rows span text size selected? style
18
+ width height tHead tFoot link options selected caption]
18
19
  end
19
20
 
20
21
  def generator_implementation
@@ -9,8 +9,8 @@ module Watir
9
9
  # @return [Array<Window>]
10
10
  #
11
11
 
12
- def windows(*args)
13
- WindowCollection.new self, extract_selector(args)
12
+ def windows(opts = {})
13
+ WindowCollection.new self, opts
14
14
  end
15
15
 
16
16
  #
@@ -22,8 +22,8 @@ module Watir
22
22
  # @return [Window]
23
23
  #
24
24
 
25
- def window(*args, &blk)
26
- win = Window.new self, extract_selector(args)
25
+ def window(opts = {}, &blk)
26
+ win = Window.new self, opts
27
27
 
28
28
  win.use(&blk) if block_given?
29
29
 
@@ -1,13 +1,5 @@
1
1
  module Watir
2
2
  class HttpClient < Selenium::WebDriver::Remote::Http::Default
3
- # TODO: Remove for Watir 7; :client_timeout will be marked deprecated in 6.19
4
- # :open_timeout should have been changed in Selenium a while back, is in 4.beta2
5
- def initialize(open_timeout: nil, read_timeout: nil, client_timeout: nil)
6
- read_timeout ||= client_timeout
7
- open_timeout ||= client_timeout || 60
8
- super(open_timeout: open_timeout, read_timeout: read_timeout)
9
- end
10
-
11
3
  def request(verb, url, headers, payload, redirects = 0)
12
4
  headers['User-Agent'] = "#{headers['User-Agent']} watir/#{Watir::VERSION}"
13
5
 
@@ -26,11 +26,7 @@ require 'watir/locators/text_field/matcher'
26
26
 
27
27
  module Watir
28
28
  module Locators
29
- W3C_FINDERS = %i[css
30
- link
31
- link_text
32
- partial_link_text
33
- xpath].freeze
29
+ W3C_FINDERS = %i[css xpath].freeze
34
30
 
35
31
  module ClassHelpers
36
32
  def locator_class
@@ -2,29 +2,6 @@ module Watir
2
2
  module Locators
3
3
  class Button
4
4
  class Matcher < Element::Matcher
5
- def elements_match?(element, values_to_match)
6
- copy_values_to_match = values_to_match.dup
7
- value = copy_values_to_match.delete(:value)
8
-
9
- if value
10
- matching = matches_values?(fetch_value(element, :text), value)
11
- deprecate_value_button if matching
12
-
13
- matching ||= matches_values?(fetch_value(element, :value), value)
14
-
15
- return false unless matching
16
- return true if copy_values_to_match.empty?
17
- end
18
-
19
- super(element, copy_values_to_match)
20
- end
21
-
22
- def deprecate_value_button
23
- Watir.logger.deprecate(':value locator key for finding button text',
24
- 'use :text locator',
25
- ids: [:value_button])
26
- end
27
-
28
5
  def validate_tag(element, _expected)
29
6
  tag_name = fetch_value(element, :tag_name)
30
7
  return unless %w[input button].include?(tag_name)
@@ -44,22 +44,11 @@ module Watir
44
44
  # :text locator is already dealt with in #tag_name_string
45
45
  value = @selector.delete(:value)
46
46
 
47
- case value
48
- when nil
49
- ''
50
- when Regexp
51
- res = "[#{predicate_conversion(:text, value)} or #{predicate_conversion(:value, value)}]"
52
- @built.delete(:text)
53
- res
54
- else
55
- "[#{predicate_expression(:text, value)} or #{predicate_expression(:value, value)}]"
56
- end
57
- end
47
+ return '' if value.nil?
58
48
 
59
- def predicate_conversion(key, regexp)
60
- res = key == :text ? super(:contains_text, regexp) : super
61
- @built[key] = @built.delete(:contains_text) if @built.key?(:contains_text)
62
- res
49
+ result = value.nil? ? '' : "[#{process_attribute(:text, value)} or #{process_attribute(:value, value)}]"
50
+ @built.delete(:text)
51
+ result
63
52
  end
64
53
 
65
54
  def input_types(type = nil)
@@ -3,6 +3,7 @@ module Watir
3
3
  class Element
4
4
  class Matcher
5
5
  include Exception
6
+ include JSSnippets
6
7
 
7
8
  attr_reader :query_scope, :selector
8
9
 
@@ -55,10 +56,10 @@ module Watir
55
56
  end
56
57
 
57
58
  def elements_match?(element, values_to_match)
58
- matches = values_to_match.all? do |how, expected|
59
+ values_to_match.all? do |how, expected|
59
60
  if how == :tag_name
60
61
  validate_tag(element, expected)
61
- # TODO: Can this be class_name here or does that get converted?
62
+ # TODO: Can this be class_name here or does that get converted?
62
63
  elsif %i[class class_name].include?(how)
63
64
  value = fetch_value(element, how)
64
65
  [expected].flatten.all? do |match|
@@ -70,10 +71,6 @@ module Watir
70
71
  matches_values?(fetch_value(element, how), expected)
71
72
  end
72
73
  end
73
-
74
- deprecate_text_regexp(element, values_to_match) if values_to_match[:text] && matches
75
-
76
- matches
77
74
  end
78
75
 
79
76
  def matches_values?(found, expected)
@@ -85,7 +82,7 @@ module Watir
85
82
  when :tag_name
86
83
  element.tag_name.downcase
87
84
  when :text
88
- element.text
85
+ execute_js(:getTextContent, element)
89
86
  when :visible
90
87
  element.displayed?
91
88
  when :visible_text
@@ -112,18 +109,6 @@ module Watir
112
109
  tag_name = fetch_value(element, :tag_name)
113
110
  matches_values?(tag_name, expected)
114
111
  end
115
-
116
- def deprecate_text_regexp(element, selector)
117
- new_element = Watir::Element.new(@query_scope, element: element)
118
- text_content = new_element.text_content
119
-
120
- return if text_content =~ /#{selector[:text]}/
121
-
122
- key = @selector.key?(:text) ? 'text' : 'label'
123
- selector_text = selector[:text].inspect
124
- dep = "Using :#{key} locator with RegExp #{selector_text} to match an element that includes hidden text"
125
- Watir.logger.deprecate(dep, ":visible_#{key}", ids: [:text_regexp])
126
- end
127
112
  end
128
113
  end
129
114
  end
@@ -25,7 +25,6 @@ module Watir
25
25
  def build(selector)
26
26
  @selector = selector
27
27
 
28
- deprecated_locators
29
28
  normalize_selector
30
29
  inspected = selector.inspect
31
30
  scope = @query_scope unless @selector.key?(:scope) || @query_scope.is_a?(Watir::Browser)
@@ -45,16 +44,13 @@ module Watir
45
44
  private
46
45
 
47
46
  def normalize_selector
47
+ # TODO: This can be more specific now that only 2
48
48
  raise LocatorException, "Can not locate element with #{wd_locators}" if wd_locators.size > 1
49
49
 
50
50
  @selector[:scope] = @query_scope.selector_builder.built if merge_scope?
51
51
 
52
52
  if @selector.key?(:class) || @selector.key?(:class_name)
53
- classes = ([@selector[:class]].flatten + [@selector.delete(:class_name)].flatten).compact
54
-
55
- deprecate_class_array(classes)
56
-
57
- @selector[:class] = classes
53
+ @selector[:class] = ([@selector[:class]].flatten + [@selector.delete(:class_name)].flatten).compact
58
54
  end
59
55
 
60
56
  if @selector[:adjacent] == :ancestor && @selector.key?(:text)
@@ -78,17 +74,6 @@ module Watir
78
74
  scope_invalid_locators.empty?
79
75
  end
80
76
 
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
90
- end
91
-
92
77
  def check_type(how, what)
93
78
  if %i[class class_name].include? how
94
79
  [what].flatten.each { |value| raise_unless(value, VALID_WHATS[how]) }
@@ -114,20 +99,12 @@ module Watir
114
99
  when :class
115
100
  what = false if what.tap { |arr| arr.delete('') }.empty?
116
101
  [how, what]
117
- when :link
118
- [:link_text, what]
119
102
  when :label, :visible_label
120
103
  if should_use_label_element?
121
104
  ["#{how}_element".to_sym, what]
122
105
  else
123
106
  [how, what]
124
107
  end
125
- when :caption
126
- # This allows any element to be located with 'caption' instead of 'text'
127
- # It is deprecated because caption is a valid attribute on a Table
128
- # It is also a valid Element, so it also needs to be removed from the Table attributes list
129
- Watir.logger.deprecate('Locating elements with :caption', ':text locator', ids: [:caption])
130
- [:text, what]
131
108
  else
132
109
  check_custom_attribute how
133
110
  [how, what]
@@ -174,18 +151,6 @@ module Watir
174
151
 
175
152
  raise TypeError, "expected one of #{types}, got #{what.inspect}:#{what.class}"
176
153
  end
177
-
178
- def deprecated_locators
179
- %i[partial_link_text link_text link].each do |locator|
180
- next unless @selector.key?(locator)
181
-
182
- Watir.logger.deprecate(":#{locator} locator", ':visible_text', ids: [:link_text])
183
- tag = @selector[:tag_name]
184
- next if tag.nil? || tag == 'a'
185
-
186
- raise LocatorException, "Can not use #{locator} locator to find a #{tag} element"
187
- end
188
- end
189
154
  end
190
155
  end
191
156
  end
@@ -120,37 +120,24 @@ module Watir
120
120
  end
121
121
 
122
122
  def text_string
123
- text = @selector.delete :text
124
-
125
- case text
126
- when nil
127
- ''
128
- when Regexp
129
- @built[:text] = text
130
- ''
131
- else
132
- "[#{predicate_expression(:text, text)}]"
133
- end
123
+ result = equal_or_contains(@selector.delete(:text))
124
+ result.nil? ? '' : "[#{result}]"
134
125
  end
135
126
 
136
127
  def label_element_string
137
- label = @selector.delete :label_element
138
-
139
- return '' if label.nil?
128
+ text = @selector.delete(:label_element)
129
+ return '' if text.nil?
140
130
 
141
- key = label.is_a?(Regexp) ? :contains_text : :text
131
+ result = equal_or_contains(text)
132
+ @built[:label_element] = @built.delete(:text) if @built.key?(:text)
142
133
 
143
- value = process_attribute(key, label)
134
+ result.nil? ? '' : "[@id=//label[#{result}]/@for or parent::label[#{result}]]"
135
+ end
144
136
 
145
- @built[:label_element] = @built.delete :contains_text if @built.key?(:contains_text)
137
+ def equal_or_contains(value)
138
+ return nil if value.nil? || value.is_a?(String) && value.empty? || value.inspect == '//'
146
139
 
147
- # TODO: This conditional can be removed when we remove this deprecation
148
- if label.is_a?(Regexp)
149
- @built[:label_element] = label
150
- ''
151
- else
152
- "[@id=//label[#{value}]/@for or parent::label[#{value}]]"
153
- end
140
+ process_attribute(:text, value)
154
141
  end
155
142
 
156
143
  def attribute_string
@@ -203,23 +190,21 @@ module Watir
203
190
  end
204
191
 
205
192
  def lhs_for(key, downcase: false)
206
- case key
207
- when String
208
- process_string key
209
- when :tag_name
210
- 'local-name()'
211
- when :href
212
- 'normalize-space(@href)'
213
- when :text
214
- 'normalize-space()'
215
- when :contains_text
216
- 'normalize-space()'
217
- when ::Symbol
218
- lhs = process_string key.to_s.tr('_', '-')
219
- downcase ? XpathSupport.downcase(lhs) : lhs
220
- else
221
- raise LocatorException, "Unable to build XPath using #{key}:#{key.class}"
222
- end
193
+ lhs = case key
194
+ when String
195
+ process_string key
196
+ when :tag_name
197
+ 'local-name()'
198
+ when :href
199
+ 'normalize-space(@href)'
200
+ when :text
201
+ 'normalize-space()'
202
+ when ::Symbol
203
+ process_string key.to_s.tr('_', '-')
204
+ else
205
+ raise LocatorException, "Unable to build XPath using #{key}:#{key.class}"
206
+ end
207
+ downcase ? XpathSupport.downcase(lhs) : lhs
223
208
  end
224
209
 
225
210
  def attribute_presence(attribute)
@@ -218,8 +218,8 @@ module Watir
218
218
  end # RadioSet
219
219
 
220
220
  module Container
221
- def radio_set(*args)
222
- RadioSet.new(self, extract_selector(args).merge(tag_name: 'input', type: 'radio'))
221
+ def radio_set(opts = {})
222
+ RadioSet.new(self, opts.merge(tag_name: 'input', type: 'radio'))
223
223
  end
224
224
 
225
225
  Watir.tag_to_class[:radio_set] = RadioSet
@@ -4,16 +4,16 @@ module Watir
4
4
  # Returns table row.
5
5
  #
6
6
 
7
- def row(*args)
8
- Row.new(self, extract_selector(args))
7
+ def row(opts)
8
+ Row.new(self, opts)
9
9
  end
10
10
 
11
11
  #
12
12
  # Returns table rows collection.
13
13
  #
14
14
 
15
- def rows(*args)
16
- RowCollection.new(self, extract_selector(args))
15
+ def rows(opts = {})
16
+ RowCollection.new(self, opts)
17
17
  end
18
18
 
19
19
  #