watir 6.19.1 → 7.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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
  #