watir 6.9.1 → 6.10.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +8 -0
  3. data/lib/watir/capabilities.rb +2 -2
  4. data/lib/watir/elements/element.rb +5 -3
  5. data/lib/watir/elements/iframe.rb +2 -2
  6. data/lib/watir/exception.rb +0 -1
  7. data/lib/watir/locators/button/locator.rb +3 -10
  8. data/lib/watir/locators/button/validator.rb +1 -1
  9. data/lib/watir/locators/cell/locator.rb +3 -5
  10. data/lib/watir/locators/element/locator.rb +61 -105
  11. data/lib/watir/locators/element/selector_builder.rb +11 -4
  12. data/lib/watir/locators/row/locator.rb +3 -5
  13. data/lib/watir/locators/text_field/locator.rb +2 -13
  14. data/spec/browser_spec.rb +4 -3
  15. data/spec/element_locator_spec.rb +50 -19
  16. data/spec/locator_spec_helper.rb +1 -1
  17. data/spec/spec_helper.rb +1 -1
  18. data/spec/watirspec/elements/area_spec.rb +0 -4
  19. data/spec/watirspec/elements/button_spec.rb +0 -4
  20. data/spec/watirspec/elements/checkbox_spec.rb +0 -4
  21. data/spec/watirspec/elements/dd_spec.rb +0 -4
  22. data/spec/watirspec/elements/del_spec.rb +0 -4
  23. data/spec/watirspec/elements/div_spec.rb +30 -8
  24. data/spec/watirspec/elements/divs_spec.rb +1 -1
  25. data/spec/watirspec/elements/dl_spec.rb +0 -4
  26. data/spec/watirspec/elements/dt_spec.rb +0 -4
  27. data/spec/watirspec/elements/element_spec.rb +25 -12
  28. data/spec/watirspec/elements/elements_spec.rb +11 -0
  29. data/spec/watirspec/elements/em_spec.rb +0 -4
  30. data/spec/watirspec/elements/filefield_spec.rb +0 -4
  31. data/spec/watirspec/elements/form_spec.rb +0 -4
  32. data/spec/watirspec/elements/frame_spec.rb +0 -4
  33. data/spec/watirspec/elements/hidden_spec.rb +0 -4
  34. data/spec/watirspec/elements/hn_spec.rb +0 -4
  35. data/spec/watirspec/elements/iframe_spec.rb +0 -4
  36. data/spec/watirspec/elements/image_spec.rb +0 -4
  37. data/spec/watirspec/elements/ins_spec.rb +0 -4
  38. data/spec/watirspec/elements/label_spec.rb +0 -4
  39. data/spec/watirspec/elements/li_spec.rb +0 -4
  40. data/spec/watirspec/elements/link_spec.rb +2 -5
  41. data/spec/watirspec/elements/links_spec.rb +1 -1
  42. data/spec/watirspec/elements/map_spec.rb +0 -4
  43. data/spec/watirspec/elements/ol_spec.rb +4 -6
  44. data/spec/watirspec/elements/option_spec.rb +0 -13
  45. data/spec/watirspec/elements/p_spec.rb +0 -4
  46. data/spec/watirspec/elements/pre_spec.rb +0 -4
  47. data/spec/watirspec/elements/radio_spec.rb +0 -4
  48. data/spec/watirspec/elements/select_list_spec.rb +4 -6
  49. data/spec/watirspec/elements/span_spec.rb +2 -5
  50. data/spec/watirspec/elements/spans_spec.rb +1 -1
  51. data/spec/watirspec/elements/strong_spec.rb +0 -4
  52. data/spec/watirspec/elements/table_spec.rb +6 -6
  53. data/spec/watirspec/elements/tbody_spec.rb +0 -5
  54. data/spec/watirspec/elements/td_spec.rb +2 -5
  55. data/spec/watirspec/elements/text_field_spec.rb +0 -4
  56. data/spec/watirspec/elements/tfoot_spec.rb +0 -5
  57. data/spec/watirspec/elements/thead_spec.rb +0 -5
  58. data/spec/watirspec/elements/tr_spec.rb +0 -4
  59. data/spec/watirspec/elements/ul_spec.rb +2 -5
  60. data/spec/watirspec/html/multiple_ids.html +1 -0
  61. data/spec/watirspec/html/non_control_elements.html +6 -1
  62. data/spec/watirspec/radio_set_spec.rb +0 -4
  63. data/watir.gemspec +1 -1
  64. metadata +2 -4
  65. data/spec/watirspec/html/ng_attributes.html +0 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df2330f15858aff8a2b86569ddc5d912bf98b0a0
4
- data.tar.gz: '087b9e1452a7aba1db5ca249f37d0a54652a8b72'
3
+ metadata.gz: ec3596aac49e15a562c7085702a1e246fa0abb5e
4
+ data.tar.gz: c2d652b8c6de8895b0004581ca07fc69e42d4d8f
5
5
  SHA512:
6
- metadata.gz: c9e0205fed4dda96b3fbff910a607cff10df4043c5c3c16c6a28a51d4ff46d6567a7ff14f1aa2707b4568e81e23a6d58ed7ad831174081964e3d01c398e1a5dc
7
- data.tar.gz: b481f2d349f880e1c23d7d74a5309848200da4e311e158ad1b592f7ac680c894183336b7499b88543702f458a62eea5cc456404e4d812ecc7fd33d5a0f151102
6
+ metadata.gz: f8a5cca75e7d14deaaa47f6eba851a2ff082b6522bfef4f7d2b485f97040e2086142d58641ee51939e320c272bbc5a7fb3a6325b99db1c9cbbfc368b2ef95eb2
7
+ data.tar.gz: fe12a324b627005ddd14f8473c00f7b8fd6fa3ccac864196af5220cdbd07aaf9150bbe52d87e148b5741dae2b50735fcf6c28b7ac69324b55fd662975f3382a4
data/CHANGES.md CHANGED
@@ -1,3 +1,11 @@
1
+ ### 6.10.0 (2017-11-23)
2
+
3
+ * Add support for locating elements with custom attributes
4
+ * Add new `:visible_text` locator (thanks Justin Ko)
5
+ * Deprecate Selenium locators `:link`, `:link_text` and `:partial_link_text` in favor of `:visible_text`
6
+ * Deprecate finding only visible text for `RegExp` values on `:text` locator (#342)
7
+ * Improve support for finding elements with `:tag_name` along with `:xpath` or `:css` locators
8
+
1
9
  ### 6.9.1 (2017-11-20)
2
10
 
3
11
  * Fix bug preventing the use of `#exectue_script` in `AfterHook` (#684)
@@ -50,7 +50,7 @@ module Watir
50
50
 
51
51
  %i(open_timeout read_timeout client_timeout).each do |t|
52
52
  next if http_client.nil? || !respond_to?(t)
53
- warn "You can now pass #{t} value directly into Watir::Browser opt without needing to use :http_client"
53
+ Watir.logger.warn "You can now pass #{t} value directly into Watir::Browser opt without needing to use :http_client"
54
54
  end
55
55
 
56
56
  http_client ||= Selenium::WebDriver::Remote::Http::Default.new
@@ -111,7 +111,7 @@ module Watir
111
111
  caps = @options.delete(:desired_capabilities)
112
112
 
113
113
  if caps
114
- warn 'You can now pass values directly into Watir::Browser opt without needing to use :desired_capabilities'
114
+ Watir.logger.warn 'You can now pass values directly into Watir::Browser opt without needing to use :desired_capabilities'
115
115
  @selenium_opts.merge!(@options)
116
116
  else
117
117
  caps = Selenium::WebDriver::Remote::Capabilities.send @browser, @options
@@ -587,9 +587,9 @@ module Watir
587
587
 
588
588
  element_validator = element_validator_class.new
589
589
  selector_builder = selector_builder_class.new(@query_scope, @selector.dup, self.class.attribute_list)
590
- locator = locator_class.new(@query_scope, @selector.dup, selector_builder, element_validator)
590
+ @locator = locator_class.new(@query_scope, @selector.dup, selector_builder, element_validator)
591
591
 
592
- @element = locator.locate
592
+ @element = @locator.locate
593
593
  end
594
594
 
595
595
  def selector_string
@@ -657,7 +657,9 @@ module Watir
657
657
  yield
658
658
  rescue unknown_exception => ex
659
659
  msg = ex.message
660
- msg += ". Maybe look in an iframe?" if @query_scope.iframes.count > 0
660
+ msg += "; Maybe look in an iframe?" if @query_scope.iframes.count > 0
661
+ custom_attributes = @locator.selector_builder.custom_attributes
662
+ msg += "; Watir treated #{custom_attributes} as a non-HTML compliant attribute, ensure that was intended" unless custom_attributes.empty?
661
663
  raise unknown_exception, msg
662
664
  rescue Selenium::WebDriver::Error::StaleElementReferenceError
663
665
  retry
@@ -8,9 +8,9 @@ module Watir
8
8
  selector = @selector.merge(tag_name: frame_tag)
9
9
  element_validator = element_validator_class.new
10
10
  selector_builder = selector_builder_class.new(@query_scope, selector, self.class.attribute_list)
11
- locator = locator_class.new(@query_scope, selector, selector_builder, element_validator)
11
+ @locator = locator_class.new(@query_scope, selector, selector_builder, element_validator)
12
12
 
13
- element = locator.locate
13
+ element = @locator.locate
14
14
  element or raise unknown_exception, "unable to locate #{@selector[:tag_name]} using #{selector_string}"
15
15
 
16
16
  @element = FramedDriver.new(element, browser)
@@ -7,7 +7,6 @@ module Watir
7
7
  class ObjectDisabledException < Error; end
8
8
  class ObjectReadOnlyException < Error; end
9
9
  class NoValueFoundException < Error; end
10
- class MissingWayOfFindingObjectException < Error; end
11
10
  class UnknownCellException < Error; end
12
11
  class NoMatchingWindowFoundException < Error; end
13
12
  class UnknownFrameException < Error; end
@@ -2,19 +2,12 @@ module Watir
2
2
  module Locators
3
3
  class Button
4
4
  class Locator < Element::Locator
5
- def locate_all
6
- find_all_by_multiple
7
- end
8
5
 
9
- private
10
6
 
11
- def wd_find_first_by(how, what)
12
- if how == :tag_name
13
- how = :xpath
14
- what = ".//button | .//input[#{selector_builder.xpath_builder.attribute_expression(:input, type: Watir::Button::VALID_TYPES)}]"
15
- end
7
+ private
16
8
 
17
- super
9
+ def using_selenium(*)
10
+ # force watir usage
18
11
  end
19
12
 
20
13
  def can_convert_regexp_to_contains?
@@ -2,7 +2,7 @@ module Watir
2
2
  module Locators
3
3
  class Button
4
4
  class Validator < Element::Validator
5
- def validate(element, selector)
5
+ def validate(element, _selector)
6
6
  return unless %w[input button].include?(element.tag_name.downcase)
7
7
  # TODO - Verify this is desired behavior based on https://bugzilla.mozilla.org/show_bug.cgi?id=1290963
8
8
  return if element.tag_name.downcase == "input" && !Watir::Button::VALID_TYPES.include?(element.attribute(:type).downcase)
@@ -2,15 +2,13 @@ module Watir
2
2
  module Locators
3
3
  class Cell
4
4
  class Locator < Element::Locator
5
- def locate_all
6
- find_all_by_multiple
7
- end
8
5
 
9
6
  private
10
7
 
11
- def by_id
12
- nil
8
+ def using_selenium(*)
9
+ # force watir usage
13
10
  end
11
+
14
12
  end
15
13
  end
16
14
  end
@@ -36,124 +36,82 @@ module Watir
36
36
  end
37
37
 
38
38
  def locate
39
- e = by_id and return e # short-circuit if :id is given
40
-
41
- element = if @selector.size == 1
42
- find_first_by_one
43
- else
44
- find_first_by_multiple
45
- end
46
-
47
- # Validation not necessary if Watir builds the xpath
48
- return element unless @selector.key?(:xpath) || @selector.key?(:css)
49
- element_validator.validate(element, @selector) if element
39
+ using_selenium(:first) || using_watir(:first)
50
40
  rescue Selenium::WebDriver::Error::NoSuchElementError, Selenium::WebDriver::Error::StaleElementReferenceError
51
41
  nil
52
42
  end
53
43
 
54
44
  def locate_all
55
- if @selector.size == 1
56
- find_all_by_one
57
- else
58
- find_all_by_multiple
59
- end
45
+ return [@selector[:element]] if @selector.key?(:element)
46
+ using_selenium(:all) || using_watir(:all)
60
47
  end
61
48
 
62
49
  private
63
50
 
64
- def by_id
51
+ def using_selenium(filter = :first)
65
52
  selector = @selector.dup
66
- id = selector.delete(:id)
67
- return if !id.is_a?(String) || selector[:adjacent]
68
-
69
- tag_name = selector.delete(:tag_name)
70
- return unless selector.empty? # multiple attributes
71
-
72
- element = locate_element(:id, id)
73
- return if tag_name && !element_validator.validate(element, {tag_name: tag_name})
74
-
75
- element
76
- end
77
-
78
- def find_first_by_one
79
- how, what = @selector.to_a.first
80
- selector_builder.check_type(how, what)
81
-
82
- if wd_supported?(how, what)
83
- wd_find_first_by(how, what)
84
- else
85
- find_first_by_multiple
53
+ tag_name = selector[:tag_name].is_a?(::Symbol) ? selector[:tag_name].to_s : selector[:tag_name]
54
+ selector.delete(:tag_name) if selector.size > 1
55
+
56
+ WD_FINDERS.each do |sel|
57
+ next unless (value = selector.delete(sel))
58
+ return unless selector.empty? && wd_supported?(sel, value)
59
+ if filter == :all
60
+ found = locate_elements(sel, value)
61
+ return filter_elements_by_locator(found, tag_name: tag_name, filter: filter).compact
62
+ else
63
+ found = locate_element(sel, value)
64
+ return sel != :tag_name && tag_name && !validate([found], tag_name) ? nil : found
65
+ end
86
66
  end
67
+ nil
87
68
  end
88
69
 
89
- def find_first_by_multiple
70
+ def using_watir(filter = :first)
90
71
  selector = selector_builder.normalized_selector
72
+ visible = selector.delete(:visible)
73
+ visible_text = selector.delete(:visible_text)
74
+ tag_name = selector[:tag_name].is_a?(::Symbol) ? selector[:tag_name].to_s : selector[:tag_name]
75
+ validation_required = (selector.key?(:css) || selector.key?(:xpath)) && tag_name
91
76
 
77
+ if selector.key?(:index) && filter == :all
78
+ raise ArgumentError, "can't locate all elements by :index"
79
+ end
92
80
  idx = selector.delete(:index) unless selector[:adjacent]
93
- visible = selector.delete(:visible)
94
81
 
95
82
  how, what = selector_builder.build(selector)
96
83
 
97
- if how
98
- # could build xpath/css for selector
99
- if idx && idx != 0 || !visible.nil?
100
- elements = locate_elements(how, what)
101
- filter_elements elements, visible, idx, :single
102
- else
103
- locate_element(how, what)
104
- end
105
- else
106
- # can't use xpath, probably a regexp in there
107
- if idx && idx != 0 || !visible.nil?
108
- elements = wd_find_by_regexp_selector(selector, :select)
109
- filter_elements elements, visible, idx, :single
110
- else
111
- wd_find_by_regexp_selector(selector, :find)
112
- end
113
- end
114
- end
84
+ needs_filtering = idx && idx != 0 || !visible.nil? || !visible_text.nil? || validation_required || filter == :all
115
85
 
116
- def find_all_by_one
117
- how, what = @selector.to_a.first
118
- return [what] if how == :element
119
- selector_builder.check_type how, what
120
-
121
- if wd_supported?(how, what)
122
- wd_find_all_by(how, what)
86
+ if needs_filtering
87
+ matching = matching_elements(how, what, selector)
88
+ return filter_elements_by_locator(matching, visible, visible_text, idx, tag_name: tag_name, filter: filter)
89
+ elsif how
90
+ locate_element(how, what)
123
91
  else
124
- find_all_by_multiple
92
+ wd_find_by_regexp_selector(selector, :first)
125
93
  end
126
94
  end
127
95
 
128
- def find_all_by_multiple
129
- selector = selector_builder.normalized_selector
130
- visible = selector.delete(:visible)
131
-
132
- if selector.key? :index
133
- raise ArgumentError, "can't locate all elements by :index"
134
- end
135
-
136
- how, what = selector_builder.build(selector)
137
- found = if how
138
- locate_elements(how, what)
139
- else
140
- wd_find_by_regexp_selector(selector, :select)
141
- end
142
- return [] if found.nil?
143
- filter_elements found, visible, nil, :multiple
96
+ def validate(elements, tag_name)
97
+ elements.compact.all? { |element| element_validator.validate(element, {tag_name: tag_name}) }
144
98
  end
145
99
 
146
- def wd_find_all_by(how, what)
147
- if what.is_a? String
148
- locate_elements(how, what)
149
- else
150
- all_elements.select { |element| fetch_value(element, how) =~ what }
151
- end
100
+ def matching_elements(how, what, selector = nil)
101
+ found = how ? locate_elements(how, what) : wd_find_by_regexp_selector(selector, :all)
102
+ found || []
152
103
  end
153
104
 
154
105
  def fetch_value(element, how)
155
106
  case how
156
107
  when :text
108
+ vis = element.text
109
+ all = Watir::Element.new(@query_scope, element: element).send(:execute_js, :getTextContent, element).strip
110
+ unless all == vis.strip
111
+ Watir.logger.deprecate(':text locator with RegExp values to find elements based on only visible text', ":visible_text")
112
+ end
113
+ vis
114
+ when :visible_text
157
115
  element.text
158
116
  when :tag_name
159
117
  element.tag_name.downcase
@@ -168,15 +126,7 @@ module Watir
168
126
  locate_elements(:xpath, ".//*")
169
127
  end
170
128
 
171
- def wd_find_first_by(how, what)
172
- if what.is_a? String
173
- locate_element(how, what)
174
- else
175
- all_elements.find { |element| fetch_value(element, how) =~ what }
176
- end
177
- end
178
-
179
- def wd_find_by_regexp_selector(selector, method = :find)
129
+ def wd_find_by_regexp_selector(selector, filter)
180
130
  query_scope = ensure_scope_context
181
131
  rx_selector = delete_regexps_from(selector)
182
132
 
@@ -205,15 +155,18 @@ module Watir
205
155
  end
206
156
 
207
157
  elements = locate_elements(how, what, query_scope)
208
- filter_elements_by_regex(elements, rx_selector, method)
158
+ filter_elements_by_regex(elements, rx_selector, filter)
209
159
  end
210
160
 
211
- def filter_elements elements, visible, idx, number
161
+ def filter_elements_by_locator(elements, visible = nil, visible_text = nil, idx = nil, tag_name: nil, filter: :first)
212
162
  elements.select! { |el| visible == el.displayed? } unless visible.nil?
213
- number == :single ? elements[idx || 0] : elements
163
+ elements.select! { |el| visible_text === el.text } unless visible_text.nil?
164
+ elements.select! { |el| element_validator.validate(el, {tag_name: tag_name}) } unless tag_name.nil?
165
+ filter == :first ? elements[idx || 0] : elements
214
166
  end
215
167
 
216
- def filter_elements_by_regex(elements, selector, method)
168
+ def filter_elements_by_regex(elements, selector, filter)
169
+ method = filter == :first ? :find : :select
217
170
  elements.__send__(method) { |el| matches_selector?(el, selector) }
218
171
  end
219
172
 
@@ -262,8 +215,8 @@ module Watir
262
215
  @query_scope.wd
263
216
  end
264
217
 
265
- def locate_element(how, what)
266
- @query_scope.wd.find_element(how, what)
218
+ def locate_element(how, what, scope = @query_scope.wd)
219
+ scope.find_element(how, what)
267
220
  end
268
221
 
269
222
  def locate_elements(how, what, scope = @query_scope.wd)
@@ -271,9 +224,12 @@ module Watir
271
224
  end
272
225
 
273
226
  def wd_supported?(how, what)
274
- return false unless WD_FINDERS.include?(how)
275
- return false unless what.kind_of?(String) || what.kind_of?(Regexp)
276
- return false if [:class, :class_name].include?(how) && what.kind_of?(String) && what.include?(' ')
227
+ return false unless what.kind_of?(String)
228
+ return false if [:class, :class_name].include?(how) && what.include?(' ')
229
+ %i[partial_link_text link_text link].each do |loc|
230
+ next unless how == loc
231
+ Watir.logger.deprecate(":#{loc} locator", ':visible_text')
232
+ end
277
233
  true
278
234
  end
279
235
  end
@@ -2,6 +2,8 @@ module Watir
2
2
  module Locators
3
3
  class Element
4
4
  class SelectorBuilder
5
+ attr_reader :custom_attributes
6
+
5
7
  VALID_WHATS = [Array, String, Regexp, TrueClass, FalseClass, ::Symbol].freeze
6
8
  WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/
7
9
 
@@ -9,6 +11,7 @@ module Watir
9
11
  @query_scope = query_scope # either element or browser
10
12
  @selector = selector
11
13
  @valid_attributes = valid_attributes
14
+ @custom_attributes = []
12
15
  end
13
16
 
14
17
  def normalized_selector
@@ -34,6 +37,10 @@ module Watir
34
37
  unless what.is_a?(TrueClass) || what.is_a?(FalseClass)
35
38
  raise TypeError, "expected TrueClass or FalseClass, got #{what.inspect}:#{what.class}"
36
39
  end
40
+ when :visible_text
41
+ unless what.is_a?(String) || what.is_a?(Regexp)
42
+ raise TypeError, "expected String or Regexp, got #{what.inspect}:#{what.class}"
43
+ end
37
44
  else
38
45
  if what.is_a?(Array) && how != :class && how != :class_name
39
46
  raise TypeError, "Only :class locator can have a value of an Array"
@@ -66,7 +73,7 @@ module Watir
66
73
 
67
74
  def normalize_selector(how, what)
68
75
  case how
69
- when :tag_name, :text, :xpath, :index, :class, :label, :css, :visible, :adjacent
76
+ when :tag_name, :text, :xpath, :index, :class, :label, :css, :visible, :visible_text, :adjacent
70
77
  # include :class since the valid attribute is 'class_name'
71
78
  # include :for since the valid attribute is 'html_for'
72
79
  [how, what]
@@ -75,14 +82,14 @@ module Watir
75
82
  when :caption
76
83
  [:text, what]
77
84
  else
78
- assert_valid_as_attribute how
85
+ check_custom_attribute how
79
86
  [how, what]
80
87
  end
81
88
  end
82
89
 
83
- def assert_valid_as_attribute(attribute)
90
+ def check_custom_attribute(attribute)
84
91
  return if valid_attribute?(attribute) || attribute.to_s =~ WILDCARD_ATTRIBUTE
85
- raise Exception::MissingWayOfFindingObjectException, "invalid attribute: #{attribute.inspect}"
92
+ @custom_attributes << attribute.to_s
86
93
  end
87
94
 
88
95
  def given_xpath_or_css(selector)
@@ -2,15 +2,13 @@ module Watir
2
2
  module Locators
3
3
  class Row
4
4
  class Locator < Element::Locator
5
- def locate_all
6
- find_all_by_multiple
7
- end
8
5
 
9
6
  private
10
7
 
11
- def by_id
12
- nil # avoid this
8
+ def using_selenium(*)
9
+ # force Watir usage
13
10
  end
11
+
14
12
  end
15
13
  end
16
14
  end