watir 6.15.0 → 6.15.1

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