watir-webdriver 0.9.1 → 0.9.2

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