watir-nokogiri 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +65 -0
  3. data/lib/watir-nokogiri.rb +69 -0
  4. data/lib/watir-nokogiri/aliases.rb +6 -0
  5. data/lib/watir-nokogiri/attribute_helper.rb +145 -0
  6. data/lib/watir-nokogiri/cell_container.rb +31 -0
  7. data/lib/watir-nokogiri/container.rb +50 -0
  8. data/lib/watir-nokogiri/document.rb +171 -0
  9. data/lib/watir-nokogiri/element_collection.rb +93 -0
  10. data/lib/watir-nokogiri/elements/button.rb +71 -0
  11. data/lib/watir-nokogiri/elements/checkbox.rb +61 -0
  12. data/lib/watir-nokogiri/elements/dlist.rb +12 -0
  13. data/lib/watir-nokogiri/elements/element.rb +319 -0
  14. data/lib/watir-nokogiri/elements/file_field.rb +45 -0
  15. data/lib/watir-nokogiri/elements/font.rb +11 -0
  16. data/lib/watir-nokogiri/elements/form.rb +17 -0
  17. data/lib/watir-nokogiri/elements/frame.rb +75 -0
  18. data/lib/watir-nokogiri/elements/generated.rb +2662 -0
  19. data/lib/watir-nokogiri/elements/hidden.rb +24 -0
  20. data/lib/watir-nokogiri/elements/image.rb +59 -0
  21. data/lib/watir-nokogiri/elements/input.rb +34 -0
  22. data/lib/watir-nokogiri/elements/link.rb +7 -0
  23. data/lib/watir-nokogiri/elements/option.rb +83 -0
  24. data/lib/watir-nokogiri/elements/radio.rb +44 -0
  25. data/lib/watir-nokogiri/elements/select.rb +126 -0
  26. data/lib/watir-nokogiri/elements/table.rb +44 -0
  27. data/lib/watir-nokogiri/elements/table_cell.rb +36 -0
  28. data/lib/watir-nokogiri/elements/table_row.rb +46 -0
  29. data/lib/watir-nokogiri/elements/table_section.rb +15 -0
  30. data/lib/watir-nokogiri/elements/text_area.rb +22 -0
  31. data/lib/watir-nokogiri/elements/text_field.rb +44 -0
  32. data/lib/watir-nokogiri/exception.rb +20 -0
  33. data/lib/watir-nokogiri/locators/button_locator.rb +54 -0
  34. data/lib/watir-nokogiri/locators/child_cell_locator.rb +24 -0
  35. data/lib/watir-nokogiri/locators/child_row_locator.rb +29 -0
  36. data/lib/watir-nokogiri/locators/element_locator.rb +298 -0
  37. data/lib/watir-nokogiri/locators/text_area_locator.rb +20 -0
  38. data/lib/watir-nokogiri/locators/text_field_locator.rb +71 -0
  39. data/lib/watir-nokogiri/row_container.rb +42 -0
  40. data/lib/watir-nokogiri/user_editable.rb +38 -0
  41. data/lib/watir-nokogiri/version.rb +3 -0
  42. data/lib/watir-nokogiri/xpath_support.rb +22 -0
  43. metadata +102 -0
@@ -0,0 +1,46 @@
1
+ module WatirNokogiri
2
+ class TableRow < HTMLElement
3
+ include CellContainer
4
+
5
+ # @private
6
+ attr_writer :locator_class
7
+
8
+ #
9
+ # Get the n'th cell (<th> or <td>) of this row
10
+ #
11
+ # @return Watir::TableCell
12
+ #
13
+
14
+ def [](idx)
15
+ cell(:index, idx)
16
+ end
17
+
18
+ private
19
+
20
+ def locator_class
21
+ @locator_class || super
22
+ end
23
+ 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
+ # This sorting will not do anything since nokogiri does not know the rowIndex
35
+ # BUG: https://github.com/jkotests/watir-nokogiri/issues/1
36
+ elements = elements.sort_by { |row| row.get_attribute(:rowIndex).to_i }
37
+ end
38
+
39
+ elements
40
+ end
41
+
42
+ def locator_class
43
+ @locator_class || super
44
+ end
45
+ end # TableRowCollection
46
+ end # Watir
@@ -0,0 +1,15 @@
1
+ module WatirNokogiri
2
+ class TableSection < HTMLElement
3
+ include RowContainer
4
+
5
+ #
6
+ # Returns table section row with given index.
7
+ #
8
+ # @param [Fixnum] idx
9
+ #
10
+
11
+ def [](idx)
12
+ row(:index => idx)
13
+ end
14
+ end # TableSection
15
+ end # WatirNokogiri
@@ -0,0 +1,22 @@
1
+ module WatirNokogiri
2
+ class TextArea < HTMLElement
3
+ include UserEditable
4
+
5
+ private
6
+
7
+ def locator_class
8
+ TextAreaLocator
9
+ end
10
+
11
+ end # TextArea
12
+
13
+ class TextAreaCollection < ElementCollection
14
+
15
+ private
16
+
17
+ def locator_class
18
+ TextAreaLocator
19
+ end
20
+
21
+ end # TextAreaCollection
22
+ end # WatirNokogiri
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ module WatirNokogiri
3
+ class TextField < Input
4
+ include UserEditable
5
+
6
+ attributes WatirNokogiri::TextArea.typed_attributes
7
+ remove_method :type # we want Input#type here, which was overriden by TextArea's attributes
8
+
9
+ private
10
+
11
+ def locator_class
12
+ TextFieldLocator
13
+ end
14
+
15
+ def selector_string
16
+ selector = @selector.dup
17
+ selector[:type] = '(any text type)'
18
+ selector[:tag_name] = "input or textarea"
19
+ selector.inspect
20
+ end
21
+ end # TextField
22
+
23
+ module Container
24
+ def text_field(*args)
25
+ TextField.new(self, extract_selector(args).merge(:tag_name => "input"))
26
+ end
27
+
28
+ def text_fields(*args)
29
+ TextFieldCollection.new(self, extract_selector(args).merge(:tag_name => "input"))
30
+ end
31
+ end # Container
32
+
33
+ class TextFieldCollection < InputCollection
34
+ private
35
+
36
+ def locator_class
37
+ TextFieldLocator
38
+ end
39
+
40
+ def element_class
41
+ TextField
42
+ end
43
+ end # TextFieldCollection
44
+ end # WatirNokogiri
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module WatirNokogiri
4
+ module Exception
5
+ class Error < StandardError; end
6
+
7
+ class UnknownObjectException < Error; end
8
+ class ObjectDisabledException < Error; end
9
+ class ObjectReadOnlyException < Error; end
10
+ class NoValueFoundException < Error; end
11
+ class MissingWayOfFindingObjectException < Error; end
12
+ class UnknownCellException < Error; end
13
+ class NoMatchingWindowFoundException < Error; end
14
+ class NoStatusBarException < Error; end
15
+ class NavigationException < Error; end
16
+ class UnknownFrameException < Error; end
17
+ class UnknownRowException < Error; end
18
+
19
+ end # Exception
20
+ end # WatirNokogiri
@@ -0,0 +1,54 @@
1
+ module WatirNokogiri
2
+ class ButtonLocator < ElementLocator
3
+
4
+ def build_nokogiri_selector(selectors)
5
+ return if selectors.values.any? { |e| e.kind_of? Regexp }
6
+
7
+ selectors.delete(:tag_name) || raise("internal error: no tag_name?!")
8
+
9
+ @building = :button
10
+ button_attr_exp = attribute_expression(selectors)
11
+
12
+ @building = :input
13
+ selectors[:type] = Button::VALID_TYPES
14
+ input_attr_exp = attribute_expression(selectors)
15
+
16
+ xpath = ".//button"
17
+ xpath << "[#{button_attr_exp}]" unless button_attr_exp.empty?
18
+ xpath << " | .//input"
19
+ xpath << "[#{input_attr_exp}]"
20
+
21
+ p :build_nokogiri_selector => xpath if $DEBUG
22
+
23
+ [:xpath, xpath]
24
+ end
25
+
26
+ def lhs_for(key)
27
+ if @building == :input && key == :text
28
+ "@value"
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ def equal_pair(key, value)
35
+ if @building == :button && key == :value
36
+ # :value should look for both node text and @value attribute
37
+ text = XpathSupport.escape(value)
38
+ "(text()=#{text} or @value=#{text})"
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def tag_name_matches?(tag_name, _)
45
+ !!(/^(input|button)$/ === tag_name)
46
+ end
47
+
48
+ def validate_element(element)
49
+ return if element.node_name.downcase == "input" && !Button::VALID_TYPES.include?(element.get_attribute(:type).downcase)
50
+ super
51
+ end
52
+
53
+ end # ButtonLocator
54
+ end # WatirNokogiri
@@ -0,0 +1,24 @@
1
+ module WatirNokogiri
2
+ class ChildCellLocator < ElementLocator
3
+
4
+ private
5
+
6
+ def build_nokogiri_selector(selectors)
7
+ return if selectors.values.any? { |e| e.kind_of? Regexp }
8
+
9
+ expressions = %w[./th ./td]
10
+ attr_expr = attribute_expression(selectors)
11
+
12
+ unless attr_expr.empty?
13
+ expressions.map! { |e| "#{e}[#{attr_expr}]" }
14
+ end
15
+
16
+ xpath = expressions.join(" | ")
17
+
18
+ p :build_nokogiri_selector => xpath if $DEBUG
19
+
20
+ [:xpath, xpath]
21
+ end
22
+
23
+ end # ChildCellLocator
24
+ end # WatirNokogiri
@@ -0,0 +1,29 @@
1
+ module WatirNokogiri
2
+ class ChildRowLocator < ElementLocator
3
+
4
+ private
5
+
6
+ def build_nokogiri_selector(selectors)
7
+ return if selectors.values.any? { |e| e.kind_of? Regexp }
8
+ selectors.delete(:tag_name) || raise("internal error: no tag_name?!")
9
+
10
+ expressions = %w[./tr]
11
+ unless %w[tbody tfoot thead].include?(@nokogiri.node_name.downcase)
12
+ expressions += %w[./tbody/tr ./thead/tr ./tfoot/tr]
13
+ end
14
+
15
+ attr_expr = attribute_expression(selectors)
16
+
17
+ unless attr_expr.empty?
18
+ expressions.map! { |e| "#{e}[#{attr_expr}]" }
19
+ end
20
+
21
+ xpath = expressions.join(" | ")
22
+
23
+ p :build_nokogiri_selector => xpath if $DEBUG
24
+
25
+ [:xpath, xpath]
26
+ end
27
+
28
+ end # ChildRowLocator
29
+ end # WatirNokogiri
@@ -0,0 +1,298 @@
1
+ # encoding: utf-8
2
+ module WatirNokogiri
3
+ class ElementLocator
4
+ include WatirNokogiri::Exception
5
+
6
+ def initialize(nokogiri, selector, valid_attributes)
7
+ @nokogiri = nokogiri
8
+ @selector = selector.dup
9
+ @valid_attributes = valid_attributes
10
+ end
11
+
12
+ def locate()
13
+ idx = @selector.delete(:index)
14
+ if idx
15
+ element = locate_all[idx]
16
+ else
17
+ element = locate_all.first
18
+ end
19
+
20
+ # This actually only applies when finding by xpath - browser.text_field(:xpath, "//input[@type='radio']")
21
+ # We don't need to validate the element if we built the xpath ourselves.
22
+ # It is also used to alter behavior of methods locating more than one type of element
23
+ # (e.g. text_field locates both input and textarea)
24
+ validate_element(element) if element
25
+ end
26
+
27
+ def locate_all()
28
+ selector = normalized_selector
29
+
30
+ if selector.has_key? :index
31
+ raise ArgumentError, "can't locate all elements by :index"
32
+ end
33
+
34
+ how, what = given_css(selector) ||
35
+ given_xpath(selector) ||
36
+ build_nokogiri_selector(selector)
37
+
38
+ case how
39
+ when :css
40
+ @nokogiri.css(what)
41
+ when :xpath
42
+ @nokogiri.xpath(what)
43
+ else
44
+ find_by_regexp_selector(selector, :select)
45
+ end
46
+ end
47
+
48
+ def find_by_regexp_selector(selector, method = :find)
49
+ parent = @nokogiri
50
+ rx_selector = delete_regexps_from(selector)
51
+
52
+ if rx_selector.has_key?(:label) && should_use_label_element?
53
+ label = label_from_text(rx_selector.delete(:label)) || (return [])
54
+ if (id = label.get_attribute('for'))
55
+ selector[:id] = id
56
+ else
57
+ parent = label
58
+ end
59
+ end
60
+
61
+ how, what = build_nokogiri_selector(selector)
62
+
63
+ unless how
64
+ raise Error, "internal error: unable to build WebDriver selector from #{selector.inspect}"
65
+ end
66
+
67
+ elements = parent.xpath(what)
68
+ elements.__send__(method) { |el| matches_selector?(el, rx_selector) }
69
+ end
70
+
71
+ def delete_regexps_from(selector)
72
+ rx_selector = {}
73
+
74
+ selector.dup.each do |how, what|
75
+ next unless what.kind_of?(Regexp)
76
+ rx_selector[how] = what
77
+ selector.delete how
78
+ end
79
+
80
+ rx_selector
81
+ end
82
+
83
+ def assert_valid_as_attribute(attribute)
84
+ unless valid_attribute? attribute or attribute.to_s =~ /^data_.+$/
85
+ raise MissingWayOfFindingObjectException, "invalid attribute: #{attribute.inspect}"
86
+ end
87
+ end
88
+
89
+ def valid_attribute?(attribute)
90
+ @valid_attributes && @valid_attributes.include?(attribute)
91
+ end
92
+
93
+ def should_use_label_element?
94
+ @selector[:tag_name] != "option"
95
+ end
96
+
97
+ def label_from_text(label_exp)
98
+ # TODO: this won't work correctly if @nokogiri is a sub-element
99
+ @nokogiri.search('label').find do |el|
100
+ matches_selector?(el, :text => label_exp)
101
+ end
102
+ end
103
+
104
+ def matches_selector?(element, selector)
105
+ selector.all? do |how, what|
106
+ what === fetch_value(element, how)
107
+ end
108
+ end
109
+
110
+ def fetch_value(element, how)
111
+ case how
112
+ when :text
113
+ element.text
114
+ when :tag_name
115
+ element.node_name.downcase
116
+ when :href
117
+ (href = element.get_attribute('href')) && href.strip
118
+ else
119
+ element.get_attribute(how.to_s.gsub("_", "-"))
120
+ end
121
+ end
122
+
123
+ def normalized_selector
124
+ selector = {}
125
+
126
+ @selector.each do |how, what|
127
+ check_type(how, what)
128
+
129
+ how, what = normalize_selector(how, what)
130
+ selector[how] = what
131
+ end
132
+
133
+ selector
134
+ end
135
+
136
+ def normalize_selector(how, what)
137
+ case how
138
+ when :tag_name, :text, :xpath, :index, :class, :label, :css
139
+ # include :class since the valid attribute is 'class_name'
140
+ # include :for since the valid attribute is 'html_for'
141
+ [how, what]
142
+ when :class_name
143
+ [:class, what]
144
+ when :caption
145
+ [:text, what]
146
+ when :for
147
+ assert_valid_as_attribute :html_for
148
+ [how, what]
149
+ else
150
+ assert_valid_as_attribute how
151
+ [how, what]
152
+ end
153
+ end
154
+
155
+
156
+ VALID_WHATS = [String, Regexp]
157
+
158
+ def check_type(how, what)
159
+ case how
160
+ when :index
161
+ unless what.kind_of?(Fixnum)
162
+ raise TypeError, "expected Fixnum, got #{what.inspect}:#{what.class}"
163
+ end
164
+ else
165
+ unless VALID_WHATS.any? { |t| what.kind_of? t }
166
+ raise TypeError, "expected one of #{VALID_WHATS.inspect}, got #{what.inspect}:#{what.class}"
167
+ end
168
+ end
169
+ end
170
+
171
+ def given_xpath(selector)
172
+ return unless xpath = selector.delete(:xpath)
173
+
174
+ unless selector.empty? || can_be_combined_with_xpath?(selector)
175
+ raise ArgumentError, ":xpath cannot be combined with other selectors (#{selector.inspect})"
176
+ end
177
+
178
+ [:xpath, xpath]
179
+ end
180
+
181
+ def can_be_combined_with_xpath?(selector)
182
+ # ouch - is this worth it?
183
+ keys = selector.keys
184
+ return true if keys == [:tag_name]
185
+
186
+ if selector[:tag_name] == "input"
187
+ return keys == [:tag_name, :type] || keys == [:type, :tag_name]
188
+ end
189
+
190
+ false
191
+ end
192
+
193
+ def given_css(selector)
194
+ return unless css = selector.delete(:css)
195
+
196
+ unless selector.empty? || can_be_combined_with_css?(selector)
197
+ raise ArgumentError, ":css cannot be combined with other selectors (#{selector.inspect})"
198
+ end
199
+
200
+ [:css, xpath]
201
+ end
202
+
203
+ def can_be_combined_with_css?(selector)
204
+ # ouch - is this worth it?
205
+ keys = selector.keys
206
+ return true if keys == [:tag_name]
207
+
208
+ if selector[:tag_name] == "input"
209
+ return keys == [:tag_name, :type] || keys == [:type, :tag_name]
210
+ end
211
+
212
+ false
213
+ end
214
+
215
+ def build_nokogiri_selector(selectors)
216
+ unless selectors.values.any? { |e| e.kind_of? Regexp }
217
+ build_xpath(selectors)
218
+ end
219
+ end
220
+
221
+ def build_xpath(selectors)
222
+ xpath = ".//"
223
+ xpath << (selectors.delete(:tag_name) || '*').to_s
224
+
225
+ idx = selectors.delete :index
226
+
227
+ # the remaining entries should be attributes
228
+ unless selectors.empty?
229
+ xpath << "[" << attribute_expression(selectors) << "]"
230
+ end
231
+
232
+ if idx
233
+ xpath << "[#{idx + 1}]"
234
+ end
235
+
236
+ p :xpath => xpath, :selectors => selectors if $DEBUG
237
+
238
+ [:xpath, xpath]
239
+ end
240
+
241
+ def attribute_expression(selectors)
242
+ selectors.map do |key, val|
243
+ if val.kind_of?(Array)
244
+ "(" + val.map { |v| equal_pair(key, v) }.join(" or ") + ")"
245
+ else
246
+ equal_pair(key, val)
247
+ end
248
+ end.join(" and ")
249
+ end
250
+
251
+ def equal_pair(key, value)
252
+ if key == :class
253
+ klass = XpathSupport.escape " #{value} "
254
+ "contains(concat(' ', @class, ' '), #{klass})"
255
+ elsif key == :label && should_use_label_element?
256
+ # we assume :label means a corresponding label element, not the attribute
257
+ text = "normalize-space()=#{XpathSupport.escape value}"
258
+ "(@id=//label[#{text}]/@for or parent::label[#{text}])"
259
+ else
260
+ "#{lhs_for(key)}=#{XpathSupport.escape value}"
261
+ end
262
+ end
263
+
264
+ def lhs_for(key)
265
+ case key
266
+ when :text, 'text'
267
+ 'normalize-space()'
268
+ when :href
269
+ # TODO: change this behaviour?
270
+ 'normalize-space(@href)'
271
+ when :type
272
+ # type attributes can be upper case - downcase them
273
+ # https://github.com/watir/watir-webdriver/issues/72
274
+ XpathSupport.downcase('@type')
275
+ else
276
+ "@#{key.to_s.gsub("_", "-")}"
277
+ end
278
+ end
279
+
280
+ def validate_element(element)
281
+ tn = @selector[:tag_name]
282
+ element_tag_name = element.node_name.downcase
283
+
284
+ return if tn && !tag_name_matches?(element_tag_name, tn)
285
+
286
+ if element_tag_name == 'input'
287
+ return if @selector[:type] && @selector[:type] != element.get_attribute(:type).downcase
288
+ end
289
+
290
+ element
291
+ end
292
+
293
+ def tag_name_matches?(element_tag_name, tag_name)
294
+ tag_name === element_tag_name
295
+ end
296
+
297
+ end # ElementLocator
298
+ end # WatirNokogiri