watir-webdriver 0.9.1 → 0.9.2

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +7 -3
  4. data/CHANGES.md +6 -0
  5. data/Rakefile +17 -5
  6. data/lib/watir-webdriver.rb +16 -6
  7. data/lib/watir-webdriver/after_hooks.rb +2 -2
  8. data/lib/watir-webdriver/browser.rb +5 -5
  9. data/lib/watir-webdriver/cell_container.rb +4 -10
  10. data/lib/watir-webdriver/element_collection.rb +28 -7
  11. data/lib/watir-webdriver/elements/button.rb +0 -18
  12. data/lib/watir-webdriver/elements/cell.rb +17 -0
  13. data/lib/watir-webdriver/elements/checkbox.rb +0 -3
  14. data/lib/watir-webdriver/elements/element.rb +37 -10
  15. data/lib/watir-webdriver/elements/file_field.rb +0 -3
  16. data/lib/watir-webdriver/elements/hidden.rb +0 -3
  17. data/lib/watir-webdriver/elements/html_elements.rb +0 -219
  18. data/lib/watir-webdriver/elements/iframe.rb +29 -19
  19. data/lib/watir-webdriver/elements/row.rb +17 -0
  20. data/lib/watir-webdriver/elements/svg_elements.rb +0 -120
  21. data/lib/watir-webdriver/elements/table.rb +1 -1
  22. data/lib/watir-webdriver/elements/table_cell.rb +0 -28
  23. data/lib/watir-webdriver/elements/table_row.rb +1 -30
  24. data/lib/watir-webdriver/elements/text_area.rb +0 -17
  25. data/lib/watir-webdriver/elements/text_field.rb +2 -8
  26. data/lib/watir-webdriver/generator/base/visitor.rb +1 -10
  27. data/lib/watir-webdriver/locators.rb +22 -0
  28. data/lib/watir-webdriver/locators/button/locator.rb +38 -0
  29. data/lib/watir-webdriver/locators/button/selector_builder.rb +27 -0
  30. data/lib/watir-webdriver/locators/button/selector_builder/xpath.rb +29 -0
  31. data/lib/watir-webdriver/locators/button/validator.rb +14 -0
  32. data/lib/watir-webdriver/locators/cell/locator.rb +17 -0
  33. data/lib/watir-webdriver/locators/cell/selector_builder.rb +24 -0
  34. data/lib/watir-webdriver/locators/element/locator.rb +249 -0
  35. data/lib/watir-webdriver/locators/element/selector_builder.rb +147 -0
  36. data/lib/watir-webdriver/locators/element/selector_builder/css.rb +65 -0
  37. data/lib/watir-webdriver/locators/element/selector_builder/xpath.rb +72 -0
  38. data/lib/watir-webdriver/locators/element/validator.rb +22 -0
  39. data/lib/watir-webdriver/locators/row/locator.rb +17 -0
  40. data/lib/watir-webdriver/locators/row/selector_builder.rb +29 -0
  41. data/lib/watir-webdriver/locators/text_area/locator.rb +13 -0
  42. data/lib/watir-webdriver/locators/text_area/selector_builder.rb +22 -0
  43. data/lib/watir-webdriver/locators/text_field/locator.rb +42 -0
  44. data/lib/watir-webdriver/locators/text_field/selector_builder.rb +34 -0
  45. data/lib/watir-webdriver/locators/text_field/selector_builder/xpath.rb +19 -0
  46. data/lib/watir-webdriver/locators/text_field/validator.rb +20 -0
  47. data/lib/watir-webdriver/row_container.rb +3 -9
  48. data/lib/watir-webdriver/version.rb +1 -1
  49. data/lib/watir-webdriver/wait.rb +6 -3
  50. data/spec/always_locate_spec.rb +2 -1
  51. data/spec/element_locator_spec.rb +1 -1
  52. data/spec/element_spec.rb +8 -40
  53. data/spec/implementation.rb +52 -45
  54. data/spec/locator_spec_helper.rb +8 -2
  55. data/support/travis.sh +17 -6
  56. metadata +25 -10
  57. data/lib/watir-webdriver/locators/button_locator.rb +0 -85
  58. data/lib/watir-webdriver/locators/child_cell_locator.rb +0 -32
  59. data/lib/watir-webdriver/locators/child_row_locator.rb +0 -37
  60. data/lib/watir-webdriver/locators/element_locator.rb +0 -470
  61. data/lib/watir-webdriver/locators/text_area_locator.rb +0 -24
  62. data/lib/watir-webdriver/locators/text_field_locator.rb +0 -93
@@ -31,7 +31,7 @@ module Watir
31
31
  # Returns row of this table with given index.
32
32
  #
33
33
  # @param [Fixnum] idx
34
- # @return Watir::TableRow
34
+ # @return Watir::Row
35
35
  #
36
36
 
37
37
  def [](idx)
@@ -1,34 +1,6 @@
1
1
  module Watir
2
2
  class TableCell < HTMLElement
3
- # @private
4
- attr_writer :locator_class
5
-
6
3
  alias_method :colspan, :col_span
7
4
  alias_method :rowspan, :row_span
8
-
9
- def locator_class
10
- @locator_class || super
11
- end
12
5
  end # TableCell
13
-
14
- class TableCellCollection < ElementCollection
15
- attr_writer :locator_class
16
-
17
- def locator_class
18
- @locator_class || super
19
- end
20
-
21
- def elements
22
- # we do this craziness since the xpath used will find direct child rows
23
- # before any rows inside thead/tbody/tfoot...
24
- elements = super
25
-
26
- if locator_class == ChildCellLocator
27
- elements = elements.sort_by { |row| row.attribute(:cellIndex).to_i }
28
- end
29
-
30
- elements
31
- end
32
-
33
- end # TableCellCollection
34
6
  end # Watir
@@ -2,43 +2,14 @@ module Watir
2
2
  class TableRow < HTMLElement
3
3
  include CellContainer
4
4
 
5
- # @private
6
- attr_writer :locator_class
7
-
8
5
  #
9
6
  # Get the n'th cell (<th> or <td>) of this row
10
7
  #
11
- # @return Watir::TableCell
8
+ # @return Watir::Cell
12
9
  #
13
10
 
14
11
  def [](idx)
15
12
  cell(:index, idx)
16
13
  end
17
-
18
- private
19
-
20
- def locator_class
21
- @locator_class || super
22
- end
23
14
  end # TableRow
24
-
25
- class TableRowCollection < ElementCollection
26
- attr_writer :locator_class
27
-
28
- def elements
29
- # we do this craziness since the xpath used will find direct child rows
30
- # before any rows inside thead/tbody/tfoot...
31
- elements = super
32
-
33
- if locator_class == ChildRowLocator and @parent.kind_of? Table
34
- elements = elements.sort_by { |row| row.attribute(:rowIndex).to_i }
35
- end
36
-
37
- elements
38
- end
39
-
40
- def locator_class
41
- @locator_class || super
42
- end
43
- end # TableRowCollection
44
15
  end # Watir
@@ -1,22 +1,5 @@
1
1
  module Watir
2
2
  class TextArea < HTMLElement
3
3
  include UserEditable
4
-
5
- private
6
-
7
- def locator_class
8
- TextAreaLocator
9
- end
10
-
11
4
  end # TextArea
12
-
13
- class TextAreaCollection < ElementCollection
14
-
15
- private
16
-
17
- def locator_class
18
- TextAreaLocator
19
- end
20
-
21
- end # TextAreaCollection
22
5
  end # Watir
@@ -2,15 +2,13 @@ module Watir
2
2
  class TextField < Input
3
3
  include UserEditable
4
4
 
5
+ NON_TEXT_TYPES = %w[file radio checkbox submit reset image button hidden datetime date month week time datetime-local range color]
6
+
5
7
  inherit_attributes_from Watir::TextArea
6
8
  remove_method :type # we want Input#type here, which was overriden by TextArea's attributes
7
9
 
8
10
  private
9
11
 
10
- def locator_class
11
- TextFieldLocator
12
- end
13
-
14
12
  def selector_string
15
13
  selector = @selector.dup
16
14
  selector[:type] = '(any text type)'
@@ -32,10 +30,6 @@ module Watir
32
30
  class TextFieldCollection < InputCollection
33
31
  private
34
32
 
35
- def locator_class
36
- TextFieldLocator
37
- end
38
-
39
33
  def element_class
40
34
  TextField
41
35
  end
@@ -97,16 +97,7 @@ module Watir
97
97
  @already_defined << name
98
98
  name = Util.classify(classify_regexp, name)
99
99
 
100
- [:class, "#{name}Collection", [:const, :ElementCollection],
101
- [:scope,
102
- [:defn, :element_class,
103
- [:args],
104
- [:scope,
105
- [:block, [:const, name]]
106
- ]
107
- ]
108
- ]
109
- ]
100
+ [:class, "#{name}Collection", [:const, :ElementCollection]]
110
101
  end
111
102
 
112
103
  def attribute_calls(attributes)
@@ -0,0 +1,22 @@
1
+ require 'watir-webdriver/locators/element/locator'
2
+ require 'watir-webdriver/locators/element/selector_builder'
3
+ require 'watir-webdriver/locators/element/validator'
4
+
5
+ require 'watir-webdriver/locators/button/locator'
6
+ require 'watir-webdriver/locators/button/selector_builder'
7
+ require 'watir-webdriver/locators/button/selector_builder/xpath'
8
+ require 'watir-webdriver/locators/button/validator'
9
+
10
+ require 'watir-webdriver/locators/cell/locator'
11
+ require 'watir-webdriver/locators/cell/selector_builder'
12
+
13
+ require 'watir-webdriver/locators/row/locator'
14
+ require 'watir-webdriver/locators/row/selector_builder'
15
+
16
+ require 'watir-webdriver/locators/text_area/locator'
17
+ require 'watir-webdriver/locators/text_area/selector_builder'
18
+
19
+ require 'watir-webdriver/locators/text_field/locator'
20
+ require 'watir-webdriver/locators/text_field/selector_builder'
21
+ require 'watir-webdriver/locators/text_field/selector_builder/xpath'
22
+ require 'watir-webdriver/locators/text_field/validator'
@@ -0,0 +1,38 @@
1
+ module Watir
2
+ module Locators
3
+ class Button
4
+ class Locator < Element::Locator
5
+ def locate_all
6
+ find_all_by_multiple
7
+ end
8
+
9
+ private
10
+
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
16
+
17
+ super
18
+ end
19
+
20
+ def can_convert_regexp_to_contains?
21
+ # regexp conversion won't work with the complex xpath selector
22
+ false
23
+ end
24
+
25
+ def matches_selector?(element, selector)
26
+ if selector.key?(:value)
27
+ copy = selector.dup
28
+ value = copy.delete(:value)
29
+
30
+ super(element, copy) && (value === fetch_value(element, :value) || value === fetch_value(element, :text))
31
+ else
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ module Watir
2
+ module Locators
3
+ class Button
4
+ class SelectorBuilder < Element::SelectorBuilder
5
+ def build_wd_selector(selectors)
6
+ return if selectors.values.any? { |e| e.kind_of? Regexp }
7
+
8
+ selectors.delete(:tag_name) || raise("internal error: no tag_name?!")
9
+
10
+ button_attr_exp = xpath_builder.attribute_expression(:button, selectors)
11
+
12
+ selectors[:type] = Watir::Button::VALID_TYPES
13
+ input_attr_exp = xpath_builder.attribute_expression(:input, selectors)
14
+
15
+ xpath = ".//button"
16
+ xpath << "[#{button_attr_exp}]" unless button_attr_exp.empty?
17
+ xpath << " | .//input"
18
+ xpath << "[#{input_attr_exp}]"
19
+
20
+ p build_wd_selector: xpath if $DEBUG
21
+
22
+ [:xpath, xpath]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ module Watir
2
+ module Locators
3
+ class Button
4
+ class SelectorBuilder
5
+ class XPath < Element::SelectorBuilder::XPath
6
+ def lhs_for(building, key)
7
+ if building == :input && key == :text
8
+ "@value"
9
+ else
10
+ super
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def equal_pair(building, key, value)
17
+ if building == :button && key == :value
18
+ # :value should look for both node text and @value attribute
19
+ text = XpathSupport.escape(value)
20
+ "(text()=#{text} or @value=#{text})"
21
+ else
22
+ super
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ module Watir
2
+ module Locators
3
+ class Button
4
+ class Validator < Element::Validator
5
+ def validate(element, selector)
6
+ return unless %w[input button].include?(element.tag_name.downcase)
7
+ return if element.tag_name.downcase == "input" && !Watir::Button::VALID_TYPES.include?(element.attribute(:type))
8
+
9
+ element
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module Watir
2
+ module Locators
3
+ class Cell
4
+ class Locator < Element::Locator
5
+ def locate_all
6
+ find_all_by_multiple
7
+ end
8
+
9
+ private
10
+
11
+ def by_id
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module Watir
2
+ module Locators
3
+ class Cell
4
+ class SelectorBuilder < Element::SelectorBuilder
5
+ def build_wd_selector(selectors)
6
+ return if selectors.values.any? { |e| e.kind_of? Regexp }
7
+
8
+ expressions = %w[./th ./td]
9
+ attr_expr = xpath_builder.attribute_expression(nil, selectors)
10
+
11
+ unless attr_expr.empty?
12
+ expressions.map! { |e| "#{e}[#{attr_expr}]" }
13
+ end
14
+
15
+ xpath = expressions.join(" | ")
16
+
17
+ p build_wd_selector: xpath if $DEBUG
18
+
19
+ [:xpath, xpath]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,249 @@
1
+ module Watir
2
+ module Locators
3
+ class Element
4
+ class Locator
5
+ attr_reader :selector_builder
6
+ attr_reader :element_validator
7
+
8
+ WD_FINDERS = [
9
+ :class,
10
+ :class_name,
11
+ :css,
12
+ :id,
13
+ :link,
14
+ :link_text,
15
+ :name,
16
+ :partial_link_text,
17
+ :tag_name,
18
+ :xpath
19
+ ]
20
+
21
+ # Regular expressions that can be reliably converted to xpath `contains`
22
+ # expressions in order to optimize the .
23
+ CONVERTABLE_REGEXP = %r{
24
+ \A
25
+ ([^\[\]\\^$.|?*+()]*) # leading literal characters
26
+ [^|]*? # do not try to convert expressions with alternates
27
+ ([^\[\]\\^$.|?*+()]*) # trailing literal characters
28
+ \z
29
+ }x
30
+
31
+ def initialize(parent, selector, selector_builder, element_validator)
32
+ @parent = parent # either element or browser
33
+ @selector = selector.dup
34
+ @selector_builder = selector_builder
35
+ @element_validator = element_validator
36
+ end
37
+
38
+ def locate
39
+ e = by_id and return e # short-circuit if :id is given
40
+
41
+ if @selector.size == 1
42
+ element = find_first_by_one
43
+ else
44
+ element = find_first_by_multiple
45
+ end
46
+
47
+ # This actually only applies when finding by xpath/css - browser.text_field(:xpath, "//input[@type='radio']")
48
+ # We don't need to validate the element if we built the xpath ourselves.
49
+ # It is also used to alter behavior of methods locating more than one type of element
50
+ # (e.g. text_field locates both input and textarea)
51
+ element_validator.validate(element, @selector) if element
52
+ rescue Selenium::WebDriver::Error::NoSuchElementError, Selenium::WebDriver::Error::StaleElementReferenceError
53
+ nil
54
+ end
55
+
56
+ def locate_all
57
+ if @selector.size == 1
58
+ find_all_by_one
59
+ else
60
+ find_all_by_multiple
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def by_id
67
+ return unless id = @selector[:id] and id.is_a? String
68
+
69
+ selector = @selector.dup
70
+ selector.delete(:id)
71
+
72
+ tag_name = selector.delete(:tag_name)
73
+ return unless selector.empty? # multiple attributes
74
+
75
+ element = @parent.wd.find_element(:id, id)
76
+ return if tag_name && !element_validator.validate(element, selector)
77
+
78
+ element
79
+ end
80
+
81
+ def find_first_by_one
82
+ how, what = @selector.to_a.first
83
+ selector_builder.check_type(how, what)
84
+
85
+ if WD_FINDERS.include?(how)
86
+ wd_find_first_by(how, what)
87
+ else
88
+ find_first_by_multiple
89
+ end
90
+ end
91
+
92
+ def find_first_by_multiple
93
+ selector = selector_builder.normalized_selector
94
+
95
+ idx = selector.delete(:index)
96
+ how, what = selector_builder.build(selector)
97
+
98
+ if how
99
+ # could build xpath/css for selector
100
+ if idx
101
+ @parent.wd.find_elements(how, what)[idx]
102
+ else
103
+ @parent.wd.find_element(how, what)
104
+ end
105
+ else
106
+ # can't use xpath, probably a regexp in there
107
+ if idx
108
+ wd_find_by_regexp_selector(selector, :select)[idx]
109
+ else
110
+ wd_find_by_regexp_selector(selector, :find)
111
+ end
112
+ end
113
+ end
114
+
115
+ def find_all_by_one
116
+ how, what = @selector.to_a.first
117
+ selector_builder.check_type how, what
118
+
119
+ if WD_FINDERS.include?(how)
120
+ wd_find_all_by(how, what)
121
+ else
122
+ find_all_by_multiple
123
+ end
124
+ end
125
+
126
+ def find_all_by_multiple
127
+ selector = selector_builder.normalized_selector
128
+
129
+ if selector.key? :index
130
+ raise ArgumentError, "can't locate all elements by :index"
131
+ end
132
+
133
+ how, what = selector_builder.build(selector)
134
+ if how
135
+ @parent.wd.find_elements(how, what)
136
+ else
137
+ wd_find_by_regexp_selector(selector, :select)
138
+ end
139
+ end
140
+
141
+ def wd_find_all_by(how, what)
142
+ if what.is_a? String
143
+ @parent.wd.find_elements(how, what)
144
+ else
145
+ all_elements.select { |element| fetch_value(element, how) =~ what }
146
+ end
147
+ end
148
+
149
+ def fetch_value(element, how)
150
+ case how
151
+ when :text
152
+ element.text
153
+ when :tag_name
154
+ element.tag_name.downcase
155
+ when :href
156
+ (href = element.attribute(:href)) && href.strip
157
+ else
158
+ element.attribute(how.to_s.tr("_", "-").to_sym)
159
+ end
160
+ end
161
+
162
+ def all_elements
163
+ @parent.wd.find_elements(xpath: ".//*")
164
+ end
165
+
166
+ def wd_find_first_by(how, what)
167
+ if what.is_a? String
168
+ @parent.wd.find_element(how, what)
169
+ else
170
+ all_elements.find { |element| fetch_value(element, how) =~ what }
171
+ end
172
+ end
173
+
174
+ def wd_find_by_regexp_selector(selector, method = :find)
175
+ parent = @parent.wd
176
+ rx_selector = delete_regexps_from(selector)
177
+
178
+ if rx_selector.key?(:label) && selector_builder.should_use_label_element?
179
+ label = label_from_text(rx_selector.delete(:label)) || return
180
+ if (id = label.attribute(:for))
181
+ selector[:id] = id
182
+ else
183
+ parent = label
184
+ end
185
+ end
186
+
187
+ how, what = selector_builder.build(selector)
188
+
189
+ unless how
190
+ raise Error, "internal error: unable to build WebDriver selector from #{selector.inspect}"
191
+ end
192
+
193
+ if how == :xpath && can_convert_regexp_to_contains?
194
+ rx_selector.each do |key, value|
195
+ next if key == :tag_name || key == :text
196
+
197
+ predicates = regexp_selector_to_predicates(key, value)
198
+ what = "(#{what})[#{predicates.join(' and ')}]" unless predicates.empty?
199
+ end
200
+ end
201
+
202
+ elements = parent.find_elements(how, what)
203
+ elements.__send__(method) { |el| matches_selector?(el, rx_selector) }
204
+ end
205
+
206
+ def delete_regexps_from(selector)
207
+ rx_selector = {}
208
+
209
+ selector.dup.each do |how, what|
210
+ next unless what.is_a?(Regexp)
211
+ rx_selector[how] = what
212
+ selector.delete how
213
+ end
214
+
215
+ rx_selector
216
+ end
217
+
218
+ def label_from_text(label_exp)
219
+ # TODO: this won't work correctly if @wd is a sub-element
220
+ @parent.wd.find_elements(:tag_name, 'label').find do |el|
221
+ matches_selector?(el, text: label_exp)
222
+ end
223
+ end
224
+
225
+ def matches_selector?(element, selector)
226
+ selector.all? do |how, what|
227
+ what === fetch_value(element, how)
228
+ end
229
+ end
230
+
231
+ def can_convert_regexp_to_contains?
232
+ true
233
+ end
234
+
235
+ def regexp_selector_to_predicates(key, re)
236
+ return [] if re.casefold?
237
+
238
+ match = re.source.match(CONVERTABLE_REGEXP)
239
+ return [] unless match
240
+
241
+ lhs = selector_builder.xpath_builder.lhs_for(nil, key)
242
+ match.captures.reject(&:empty?).map do |literals|
243
+ "contains(#{lhs}, #{XpathSupport.escape(literals)})"
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end