watir 6.15.0 → 6.15.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d985e9510a911174b9ba60c5d3174eb67fc612b5
4
- data.tar.gz: f7a5ba01ad7bea4f7fe8222c55f571e5340d8725
3
+ metadata.gz: 8c5ac0f3aeeeec1422ef300b30ffc292113a053e
4
+ data.tar.gz: a12b41e42257e86152ffb7e8b3c4e5777e657acf
5
5
  SHA512:
6
- metadata.gz: 4ba1c06f7bfeaa9c8ac573ca2b023a23dc5222741d925db41d77e72bfaeb46d5a7f217560337d1afbf8e8d9204b050dcded5ff50218ba5129ba48ceff3adf3db
7
- data.tar.gz: 98e3d14ab62868105c6e7af9108fbfd341fbc6f181baae28235b2aca8bafdad9c0c9e9b5c664c809a69bb84b80320e08adfd217751df0f4a87f2c30b794b5a04
6
+ metadata.gz: 057b9175188bef9121cb6bd5970db44ef90119e57e0c910c4fcec06762f66b9a8f8eebccc627826da31ba40bba0cf999fcf4110d07273b120879f3e93a3f7112
7
+ data.tar.gz: c7e93f0e181b4c11d3b20d7bd91e5a9f3fb4bb0fe8c0a98ad49a8daee676cf8fa35cb41c5d622714c6ee4c5dfc991b6dfbf010bd885b132e55d25d4d7df8c276
data/.rubocop.yml CHANGED
@@ -35,6 +35,10 @@ Lint/HandleExceptions:
35
35
  Exclude:
36
36
  - 'lib/watirspec.rb'
37
37
 
38
+ Lint/UnifiedInteger:
39
+ Exclude:
40
+ - 'lib/watir/locators/element/selector_builder.rb'
41
+
38
42
  # Configuration parameters: CountComments, ExcludedMethods.
39
43
  # ExcludedMethods: refine
40
44
  Metrics/BlockLength:
data/.travis.yml CHANGED
@@ -20,13 +20,13 @@ script: bundle exec rake $RAKE_TASK
20
20
  _version:
21
21
  three: &three
22
22
  language: ruby
23
- rvm: 2.3.7
23
+ rvm: 2.3.8
24
24
  four: &four
25
25
  language: ruby
26
- rvm: 2.4.4
26
+ rvm: 2.4.5
27
27
  five: &five
28
28
  language: ruby
29
- rvm: 2.5.1
29
+ rvm: 2.5.3
30
30
 
31
31
  _browsers:
32
32
  firefox: &firefox-latest
data/CHANGES.md CHANGED
@@ -1,3 +1,11 @@
1
+ ### 6.15.1 (2018-12-04)
2
+
3
+ * Locator value type check error message now returns array of allowed class types
4
+ * Wire calls for `:label` locator happen after Selector is built
5
+ * Improve error message for `Watir::Option` element when not found (#823)
6
+ * Wrap `#wd` with `#element_call` to wait for element to exist (#813)
7
+ * Remove automatic element reset in wait loop (#819)
8
+
1
9
  ### 6.15.0 (2018-11-07)
2
10
 
3
11
  * Add `Element#selected_text`
@@ -436,8 +436,7 @@ module Watir
436
436
  #
437
437
 
438
438
  def wd
439
- assert_exists
440
- @element
439
+ element_call { @element }
441
440
  end
442
441
 
443
442
  #
@@ -159,7 +159,7 @@ module Watir
159
159
  return selected_options.first.text if matching_option?(approach.downcase, str_or_rx)
160
160
  end
161
161
 
162
- raise NoValueFoundException, "#{str_or_rx.inspect} not found in select list"
162
+ raise_no_value_found(str_or_rx)
163
163
  end
164
164
 
165
165
  def process_str_or_rx(str_or_rx)
@@ -213,9 +213,13 @@ module Watir
213
213
  # TODO: Remove conditional when remove relaxed_locate toggle
214
214
  return @found unless @found.empty?
215
215
 
216
- raise NoValueFoundException, "#{str_or_rx.inspect} not found in select list"
216
+ raise_no_value_found(str_or_rx)
217
217
  rescue Wait::TimeoutError
218
- raise NoValueFoundException, "#{str_or_rx.inspect} not found in select list"
218
+ raise_no_value_found(str_or_rx)
219
+ end
220
+
221
+ def raise_no_value_found(str_or_rx)
222
+ raise NoValueFoundException, "#{str_or_rx.inspect} not found in #{inspect}"
219
223
  end
220
224
 
221
225
  def select_matching(elements)
@@ -32,7 +32,7 @@ module Watir
32
32
  string << " and (#{input_types(type)})"
33
33
  if text
34
34
  string << " and #{process_attribute(:value, text)}"
35
- @requires_matches.delete(:value)
35
+ @built.delete(:value)
36
36
  end
37
37
  string
38
38
  end
@@ -49,7 +49,7 @@ module Watir
49
49
  ''
50
50
  when Regexp
51
51
  res = "[#{predicate_conversion(:text, value)} or #{predicate_conversion(:value, value)}]"
52
- @requires_matches.delete(:text)
52
+ @built.delete(:text)
53
53
  res
54
54
  else
55
55
  "[#{predicate_expression(:text, value)} or #{predicate_expression(:value, value)}]"
@@ -58,7 +58,7 @@ module Watir
58
58
 
59
59
  def predicate_conversion(key, regexp)
60
60
  res = key == :text ? super(:contains_text, regexp) : super
61
- @requires_matches[key] = @requires_matches.delete(:contains_text) if @requires_matches.key?(:contains_text)
61
+ @built[key] = @built.delete(:contains_text) if @built.key?(:contains_text)
62
62
  res
63
63
  end
64
64
 
@@ -51,33 +51,30 @@ module Watir
51
51
  end
52
52
 
53
53
  def using_watir(filter = :first)
54
- raise ArgumentError, "can't locate all elements by :index" if @selector.key?(:index) && filter == :all
54
+ selector = @selector.dup
55
+ raise ArgumentError, "can't locate all elements by :index" if selector.key?(:index) && filter == :all
55
56
 
56
- begin
57
- generate_scope
58
- rescue LocatorException
59
- return nil
60
- end
57
+ @driver_scope ||= @query_scope.wd
58
+
59
+ built = selector_builder.build(selector)
61
60
 
62
- selector, values_to_match = selector_builder.build(@selector)
61
+ validate_built_selector(built)
63
62
 
64
- validate_built_selector(selector, values_to_match)
63
+ wd_locator = built.select { |key, _value| %i[css xpath link_text partial_link_text].include? key }
64
+ values_to_match = built.reject { |key, _value| %i[css xpath link_text partial_link_text].include? key }
65
65
 
66
66
  if filter == :all || values_to_match.any?
67
- locate_matching_elements(selector, values_to_match, filter)
67
+ locate_matching_elements(wd_locator, values_to_match, filter)
68
68
  else
69
- locate_element(selector.keys.first, selector.values.first, @driver_scope)
69
+ locate_element(wd_locator.keys.first, wd_locator.values.first, @driver_scope)
70
70
  end
71
71
  end
72
72
 
73
- def validate_built_selector(selector, values_to_match)
74
- if selector.nil?
75
- msg = "#{selector_builder.class} was unable to build selector from #{@selector.inspect}"
76
- raise LocatorException, msg
77
- elsif values_to_match.nil?
78
- msg = "#{selector_builder.class}#build is not returning expected responses for the current version of Watir"
79
- raise LocatorException, msg
80
- end
73
+ def validate_built_selector(built)
74
+ return unless built.nil? || built.empty?
75
+
76
+ msg = "#{selector_builder.class} was unable to build selector from #{@selector.inspect}"
77
+ raise LocatorException, msg
81
78
  end
82
79
 
83
80
  def fetch_value(element, how)
@@ -98,6 +95,19 @@ module Watir
98
95
  end
99
96
  end
100
97
 
98
+ def matching_labels(elements, values_to_match, scope)
99
+ label_key = values_to_match.key?(:label_element) ? :label_element : :visible_label_element
100
+ label_value = values_to_match.delete(:label_element) || values_to_match.delete(:visible_label_element)
101
+ locator_key = label_key.to_s.gsub('label', 'text').gsub('_element', '').to_sym
102
+
103
+ Watir::LabelCollection.new(scope, tag_name: 'label').map { |label|
104
+ next unless matches_values?(label.wd, locator_key => label_value)
105
+
106
+ input = label.for.empty? ? label.input : Watir::Input.new(scope, id: label.for)
107
+ input.wd if elements.include?(input.wd)
108
+ }.compact
109
+ end
110
+
101
111
  def matching_elements(elements, values_to_match, filter: :first)
102
112
  if filter == :first
103
113
  idx = element_index(elements, values_to_match)
@@ -124,44 +134,6 @@ module Watir
124
134
  idx.abs - 1
125
135
  end
126
136
 
127
- def generate_scope
128
- return @driver_scope if @driver_scope
129
-
130
- @driver_scope = @query_scope.wd
131
-
132
- if @selector.key?(:label)
133
- process_label :label
134
- elsif @selector.key?(:visible_label)
135
- process_label :visible_label
136
- end
137
- end
138
-
139
- def process_label(label_key)
140
- regexp = @selector[label_key].is_a?(Regexp)
141
- return unless (regexp || label_key == :visible_label) && selector_builder.should_use_label_element?
142
-
143
- label = label_from_text(label_key)
144
- msg = "Unable to locate element with label #{label_key}: #{@selector[label_key]}"
145
- raise LocatorException, msg unless label
146
-
147
- id = label.attribute('for')
148
- if id
149
- @selector[:id] = id
150
- else
151
- @driver_scope = label
152
- end
153
- end
154
-
155
- def label_from_text(label_key)
156
- # TODO: this won't work correctly if @wd is a sub-element, write spec
157
- # TODO: Figure out how to do this with find_element
158
- label_text = @selector.delete(label_key)
159
- locator_key = label_key.to_s.gsub('label', 'text').gsub('_element', '').to_sym
160
- locate_elements(:tag_name, 'label', @driver_scope).find do |el|
161
- matches_values?(el, locator_key => label_text)
162
- end
163
- end
164
-
165
137
  def matches_values?(element, values_to_match)
166
138
  matches = values_to_match.all? do |how, what|
167
139
  if how == :tag_name && what.is_a?(String)
@@ -201,6 +173,9 @@ module Watir
201
173
  retries = 0
202
174
  begin
203
175
  elements = locate_elements(selector.keys.first, selector.values.first, @driver_scope) || []
176
+ if values_to_match.key?(:label_element) || values_to_match.key?(:visible_label_element)
177
+ elements = matching_labels(elements, values_to_match, @query_scope)
178
+ end
204
179
  matching_elements(elements, values_to_match, filter: filter)
205
180
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
206
181
  retries += 1
@@ -5,8 +5,16 @@ module Watir
5
5
  include Exception
6
6
  attr_reader :custom_attributes
7
7
 
8
- VALID_WHATS = [String, Regexp, TrueClass, FalseClass].freeze
9
- WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/
8
+ WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/.freeze
9
+ INTEGER_CLASS = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4') ? Fixnum : Integer
10
+ VALID_WHATS = Hash.new([String, Regexp, TrueClass, FalseClass]).merge(adjacent: [::Symbol],
11
+ xpath: [String],
12
+ css: [String],
13
+ index: [INTEGER_CLASS],
14
+ visible: [TrueClass, FalseClass],
15
+ tag_name: [String, Regexp, ::Symbol],
16
+ visible_text: [String, Regexp],
17
+ text: [String, Regexp]).freeze
10
18
 
11
19
  def initialize(valid_attributes)
12
20
  @valid_attributes = valid_attributes
@@ -18,21 +26,16 @@ module Watir
18
26
  @selector = selector
19
27
  normalize_selector
20
28
 
21
- xpath_css = (@selector.keys & %i[xpath css]).each_with_object({}) do |key, hash|
22
- hash[key] = @selector.delete(key)
23
- end
29
+ xpath_css = @selector.select { |key, _value| %i[xpath css].include? key }
30
+
31
+ raise LocatorException, ":xpath and :css cannot be combined (#{xpath_css})" if xpath_css.size > 1
24
32
 
25
- built = if xpath_css.empty?
26
- build_wd_selector(@selector)
27
- else
28
- process_xpath_css(xpath_css)
29
- xpath_css
30
- end
33
+ built = xpath_css.empty? ? build_wd_selector(@selector) : @selector
31
34
 
32
- @selector.delete(:index) if @selector[:index]&.zero?
35
+ built.delete(:index) if built[:index]&.zero?
33
36
 
34
37
  Watir.logger.debug "Converted #{inspected} to #{built}, with #{@selector.inspect} to match"
35
- [built, @selector]
38
+ built
36
39
  end
37
40
 
38
41
  def normalize_selector
@@ -53,33 +56,14 @@ module Watir
53
56
  end
54
57
 
55
58
  def check_type(how, what)
56
- case how
57
- when :adjacent
58
- return raise_unless(what, ::Symbol)
59
- when :xpath, :css
60
- return raise_unless(what, String)
61
- when :index
62
- return raise_unless(what, Integer)
63
- when :visible
64
- return raise_unless(what, :boolean)
65
- when :tag_name
66
- return raise_unless(what, :string_or_regexp_or_symbol)
67
- when :visible_text, :text
68
- return raise_unless(what, :string_or_regexp)
69
- when :class, :class_name
70
- if what.is_a?(Array)
71
- raise LocatorException, 'Can not locate elements with an empty Array for :class' if what.empty?
72
-
73
- what.each do |klass|
74
- raise_unless(klass, :string_or_regexp)
75
- end
76
- return
77
- end
78
- end
79
-
80
- return if VALID_WHATS.any? { |t| what.is_a? t }
59
+ if %i[class class_name].include?(how)
60
+ classes = [what].flatten
61
+ raise LocatorException, "Can not locate elements with an empty Array for :#{how}" if classes.empty?
81
62
 
82
- raise TypeError, "expected one of #{VALID_WHATS.inspect}, got #{what.inspect}:#{what.class}"
63
+ classes.each { |value| raise_unless(value, VALID_WHATS[how]) }
64
+ else
65
+ raise_unless(what, VALID_WHATS[how])
66
+ end
83
67
  end
84
68
 
85
69
  def should_use_label_element?
@@ -91,14 +75,14 @@ module Watir
91
75
  def normalize_locator(how, what)
92
76
  case how
93
77
  when 'text'
94
- Watir.logger.deprecate "String 'text' as a locator", 'Symbol :text', ids: ['text_string']
78
+ Watir.logger.deprecate "String 'text' as a locator", 'Symbol :text', ids: [:text_string]
95
79
  [:text, what]
96
80
  when :tag_name
97
81
  what = what.to_s if what.is_a?(::Symbol)
98
82
  [how, what]
99
83
  when :text, :xpath, :index, :class, :css, :visible, :visible_text, :adjacent
100
84
  [how, what]
101
- when :label
85
+ when :label, :visible_label
102
86
  if should_use_label_element?
103
87
  ["#{how}_element".to_sym, what]
104
88
  else
@@ -122,15 +106,6 @@ module Watir
122
106
  @custom_attributes << attribute.to_s
123
107
  end
124
108
 
125
- def process_xpath_css(xpath_css)
126
- raise LocatorException, ":xpath and :css cannot be combined (#{xpath_css})" if xpath_css.size > 1
127
-
128
- return if combine_with_xpath_or_css?(@selector)
129
-
130
- msg = "#{xpath_css.keys.first} cannot be combined with all of these locators (#{@selector.inspect})"
131
- raise LocatorException, msg
132
- end
133
-
134
109
  # Implement this method when creating a different selector builder
135
110
  def build_wd_selector(selector)
136
111
  Kernel.const_get("#{self.class.name}::XPath").new.build(selector)
@@ -152,19 +127,10 @@ module Watir
152
127
  end
153
128
  end
154
129
 
155
- def raise_unless(what, type)
156
- valid = if type == :boolean
157
- [TrueClass, FalseClass].include?(what.class)
158
- elsif type == :string_or_regexp
159
- [String, Regexp].include?(what.class)
160
- elsif type == :string_or_regexp_or_symbol
161
- [String, Regexp, ::Symbol].include?(what.class)
162
- else
163
- what.is_a?(type)
164
- end
165
- return if valid
166
-
167
- raise TypeError, "expected #{type}, got #{what.inspect}:#{what.class}"
130
+ def raise_unless(what, types)
131
+ return if types.include?(what.class)
132
+
133
+ raise TypeError, "expected one of #{types}, got #{what.inspect}:#{what.class}"
168
134
  end
169
135
  end
170
136
  end
@@ -6,12 +6,12 @@ module Watir
6
6
  include Exception
7
7
  include XpathSupport
8
8
 
9
- CAN_NOT_BUILD = %i[visible visible_text].freeze
9
+ CAN_NOT_BUILD = %i[visible visible_text visible_label_element].freeze
10
10
 
11
11
  def build(selector)
12
12
  @selector = selector
13
13
 
14
- @requires_matches = (@selector.keys & CAN_NOT_BUILD).each_with_object({}) do |key, hash|
14
+ @built = (@selector.keys & CAN_NOT_BUILD).each_with_object({}) do |key, hash|
15
15
  hash[key] = @selector.delete(key)
16
16
  end
17
17
 
@@ -24,13 +24,11 @@ module Watir
24
24
  xpath << class_string
25
25
  xpath << text_string
26
26
  xpath << additional_string
27
+ xpath << label_element_string
27
28
  xpath << attribute_string
28
29
 
29
- xpath = index ? add_index(xpath, index) : xpath
30
-
31
- @selector.merge! @requires_matches
32
-
33
- {xpath: xpath}
30
+ @built[:xpath] = index ? add_index(xpath, index) : xpath
31
+ @built
34
32
  end
35
33
 
36
34
  private
@@ -108,11 +106,11 @@ module Watir
108
106
 
109
107
  deprecate_class_array(class_name) if class_name.is_a?(String) && class_name.strip.include?(' ')
110
108
 
111
- @requires_matches[:class] = []
109
+ @built[:class] = []
112
110
 
113
111
  predicates = [class_name].flatten.map { |value| process_attribute(:class, value) }.compact
114
112
 
115
- @requires_matches.delete(:class) if @requires_matches[:class].empty?
113
+ @built.delete(:class) if @built[:class].empty?
116
114
 
117
115
  predicates.empty? ? '' : "[#{predicates.join(' and ')}]"
118
116
  end
@@ -124,13 +122,38 @@ module Watir
124
122
  when nil
125
123
  ''
126
124
  when Regexp
127
- @requires_matches[:text] = text
125
+ @built[:text] = text
128
126
  ''
129
127
  else
130
128
  "[#{predicate_expression(:text, text)}]"
131
129
  end
132
130
  end
133
131
 
132
+ def label_element_string
133
+ label = @selector.delete :label_element
134
+
135
+ return '' if label.nil?
136
+
137
+ key = label.is_a?(Regexp) ? :contains_text : :text
138
+
139
+ value = process_attribute(key, label)
140
+
141
+ @built[:label_element] = @built.delete :contains_text if @built.key?(:contains_text)
142
+
143
+ # TODO: This conditional can be removed when we remove this deprecation
144
+ if label.is_a?(Regexp)
145
+ if @built.key?(:label_element)
146
+ dep = "Using :label locator with RegExp #{label} to match an element that includes hidden text"
147
+ Watir.logger.deprecate(dep, ":visible_#{key}", ids: [:text_regexp])
148
+ end
149
+
150
+ @built[:label_element] = label
151
+ ''
152
+ else
153
+ "[@id=//label[#{value}]/@for or parent::label[#{value}]]"
154
+ end
155
+ end
156
+
134
157
  def attribute_string
135
158
  attributes = @selector.keys.map { |key|
136
159
  process_attribute(key, @selector.delete(key))
@@ -143,18 +166,17 @@ module Watir
143
166
  ''
144
167
  end
145
168
 
146
- # TODO: Remove this on refactor of index
147
169
  def add_index(xpath, index)
148
170
  if @adjacent
149
171
  "#{xpath}[#{index + 1}]"
150
- elsif index&.positive? && @requires_matches.empty?
172
+ elsif index&.positive? && @built.empty?
151
173
  "(#{xpath})[#{index + 1}]"
152
- elsif index&.negative? && @requires_matches.empty?
174
+ elsif index&.negative? && @built.empty?
153
175
  last_value = 'last()'
154
176
  last_value << (index + 1).to_s if index < -1
155
177
  "(#{xpath})[#{last_value}]"
156
178
  else
157
- @requires_matches[:index] = index
179
+ @built[:index] = index
158
180
  xpath
159
181
  end
160
182
  end
@@ -167,7 +189,7 @@ module Watir
167
189
  end
168
190
 
169
191
  def visible?
170
- !(@requires_matches.keys & CAN_NOT_BUILD).empty?
192
+ !(@built.keys & CAN_NOT_BUILD).empty?
171
193
  end
172
194
 
173
195
  def starts_with?(results, regexp)
@@ -178,9 +200,9 @@ module Watir
178
200
  return unless results.nil? || requires_matching?(results, regexp)
179
201
 
180
202
  if key == :class
181
- @requires_matches[key] << regexp
203
+ @built[key] << regexp
182
204
  else
183
- @requires_matches[key] = regexp
205
+ @built[key] = regexp
184
206
  end
185
207
  end
186
208
 
@@ -218,11 +240,7 @@ module Watir
218
240
  end
219
241
 
220
242
  def equal_pair(key, value)
221
- if key == :label_element
222
- # we assume :label means a corresponding label element, not the attribute
223
- text = "#{lhs_for(:text)}=#{XpathSupport.escape value}"
224
- "(@id=//label[#{text}]/@for or parent::label[#{text}])"
225
- elsif key == :class
243
+ if key == :class
226
244
  negate_xpath = value =~ /^!/ && value.slice!(0)
227
245
  expression = "contains(concat(' ', @class, ' '), #{XpathSupport.escape " #{value} "})"
228
246