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
@@ -1,85 +0,0 @@
1
- module Watir
2
- class ButtonLocator < ElementLocator
3
-
4
- def locate_all
5
- find_all_by_multiple
6
- end
7
-
8
- private
9
-
10
- def wd_find_first_by(how, what)
11
- if how == :tag_name
12
- how = :xpath
13
- what = ".//button | .//input[#{attribute_expression type: Button::VALID_TYPES}]"
14
- end
15
-
16
- super
17
- end
18
-
19
- def build_wd_selector(selectors)
20
- return if selectors.values.any? { |e| e.kind_of? Regexp }
21
-
22
- selectors.delete(:tag_name) || raise("internal error: no tag_name?!")
23
-
24
- @building = :button
25
- button_attr_exp = attribute_expression(selectors)
26
-
27
- @building = :input
28
- selectors[:type] = Button::VALID_TYPES
29
- input_attr_exp = attribute_expression(selectors)
30
-
31
- xpath = ".//button"
32
- xpath << "[#{button_attr_exp}]" unless button_attr_exp.empty?
33
- xpath << " | .//input"
34
- xpath << "[#{input_attr_exp}]"
35
-
36
- p build_wd_selector: xpath if $DEBUG
37
-
38
- [:xpath, xpath]
39
- end
40
-
41
- def lhs_for(key)
42
- if @building == :input && key == :text
43
- "@value"
44
- else
45
- super
46
- end
47
- end
48
-
49
- def equal_pair(key, value)
50
- if @building == :button && key == :value
51
- # :value should look for both node text and @value attribute
52
- text = XpathSupport.escape(value)
53
- "(text()=#{text} or @value=#{text})"
54
- else
55
- super
56
- end
57
- end
58
-
59
- def matches_selector?(element, selector)
60
- if selector.key?(:value)
61
- copy = selector.dup
62
- value = copy.delete(:value)
63
-
64
- super(element, copy) && (value === fetch_value(element, :value) || value === fetch_value(element, :text))
65
- else
66
- super
67
- end
68
- end
69
-
70
- def can_convert_regexp_to_contains?
71
- # regexp conversion won't work with the complex xpath selector
72
- false
73
- end
74
-
75
- def tag_name_matches?(tag_name, _)
76
- !!(/^(input|button)$/ === tag_name)
77
- end
78
-
79
- def validate_element(element)
80
- return if element.tag_name.downcase == "input" && !Button::VALID_TYPES.include?(element.attribute(:type))
81
- super
82
- end
83
-
84
- end # ButtonLocator
85
- end # Watir
@@ -1,32 +0,0 @@
1
- module Watir
2
- class ChildCellLocator < ElementLocator
3
-
4
- def locate_all
5
- find_all_by_multiple
6
- end
7
-
8
- private
9
-
10
- def by_id
11
- nil
12
- end
13
-
14
- def build_wd_selector(selectors)
15
- return if selectors.values.any? { |e| e.kind_of? Regexp }
16
-
17
- expressions = %w[./th ./td]
18
- attr_expr = attribute_expression(selectors)
19
-
20
- unless attr_expr.empty?
21
- expressions.map! { |e| "#{e}[#{attr_expr}]" }
22
- end
23
-
24
- xpath = expressions.join(" | ")
25
-
26
- p build_wd_selector: xpath if $DEBUG
27
-
28
- [:xpath, xpath]
29
- end
30
-
31
- end # ChildCellLocator
32
- end # Watir
@@ -1,37 +0,0 @@
1
- module Watir
2
- class ChildRowLocator < ElementLocator
3
-
4
- def locate_all
5
- find_all_by_multiple
6
- end
7
-
8
- private
9
-
10
- def by_id
11
- nil # avoid this
12
- end
13
-
14
- def build_wd_selector(selectors)
15
- return if selectors.values.any? { |e| e.kind_of? Regexp }
16
- selectors.delete(:tag_name) || raise("internal error: no tag_name?!")
17
-
18
- expressions = %w[./tr]
19
- unless %w[tbody tfoot thead].include?(@wd.tag_name.downcase)
20
- expressions += %w[./tbody/tr ./thead/tr ./tfoot/tr]
21
- end
22
-
23
- attr_expr = attribute_expression(selectors)
24
-
25
- unless attr_expr.empty?
26
- expressions.map! { |e| "#{e}[#{attr_expr}]" }
27
- end
28
-
29
- xpath = expressions.join(" | ")
30
-
31
- p build_wd_selector: xpath if $DEBUG
32
-
33
- [:xpath, xpath]
34
- end
35
-
36
- end # ChildRowLocator
37
- end # Watir
@@ -1,470 +0,0 @@
1
- module Watir
2
- class ElementLocator
3
- include Watir::Exception
4
-
5
- WD_FINDERS = [
6
- :class,
7
- :class_name,
8
- :css,
9
- :id,
10
- :link,
11
- :link_text,
12
- :name,
13
- :partial_link_text,
14
- :tag_name,
15
- :xpath
16
- ]
17
-
18
- WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/
19
-
20
- # Regular expressions that can be reliably converted to xpath `contains`
21
- # expressions in order to optimize the locator.
22
- #
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(wd, selector, valid_attributes)
32
- @wd = wd
33
- @selector = selector.dup
34
- @valid_attributes = valid_attributes
35
- end
36
-
37
- def locate
38
- e = by_id and return e # short-circuit if :id is given
39
-
40
- if @selector.size == 1
41
- element = find_first_by_one
42
- else
43
- element = find_first_by_multiple
44
- end
45
-
46
- # This actually only applies when finding by xpath/css - browser.text_field(:xpath, "//input[@type='radio']")
47
- # We don't need to validate the element if we built the xpath ourselves.
48
- # It is also used to alter behavior of methods locating more than one type of element
49
- # (e.g. text_field locates both input and textarea)
50
- validate_element(element) if element
51
- rescue Selenium::WebDriver::Error::NoSuchElementError, Selenium::WebDriver::Error::StaleElementReferenceError
52
- nil
53
- end
54
-
55
- def locate_all
56
- if @selector.size == 1
57
- find_all_by_one
58
- else
59
- find_all_by_multiple
60
- end
61
- end
62
-
63
- private
64
-
65
- def find_first_by_one
66
- how, what = @selector.to_a.first
67
- check_type how, what
68
-
69
- if WD_FINDERS.include?(how)
70
- wd_find_first_by(how, what)
71
- else
72
- find_first_by_multiple
73
- end
74
- end
75
-
76
- def find_all_by_one
77
- how, what = @selector.to_a.first
78
- check_type how, what
79
-
80
- if WD_FINDERS.include?(how)
81
- wd_find_all_by(how, what)
82
- else
83
- find_all_by_multiple
84
- end
85
- end
86
-
87
- def find_first_by_multiple
88
- selector = normalized_selector
89
-
90
- idx = selector.delete(:index)
91
- how, what = given_xpath_or_css(selector) || build_wd_selector(selector)
92
-
93
- if how
94
- # could build xpath/css for selector
95
- if idx
96
- @wd.find_elements(how, what)[idx]
97
- else
98
- @wd.find_element(how, what)
99
- end
100
- else
101
- # can't use xpath, probably a regexp in there
102
- if idx
103
- wd_find_by_regexp_selector(selector, :select)[idx]
104
- else
105
- wd_find_by_regexp_selector(selector, :find)
106
- end
107
- end
108
- end
109
-
110
- def find_all_by_multiple
111
- selector = normalized_selector
112
-
113
- if selector.key? :index
114
- raise ArgumentError, "can't locate all elements by :index"
115
- end
116
-
117
- how, what = given_xpath_or_css(selector) || build_wd_selector(selector)
118
- if how
119
- @wd.find_elements(how, what)
120
- else
121
- wd_find_by_regexp_selector(selector, :select)
122
- end
123
- end
124
-
125
- def wd_find_first_by(how, what)
126
- if what.kind_of? String
127
- @wd.find_element(how, what)
128
- else
129
- all_elements.find { |element| fetch_value(element, how) =~ what }
130
- end
131
- end
132
-
133
- def wd_find_all_by(how, what)
134
- if what.kind_of? String
135
- @wd.find_elements(how, what)
136
- else
137
- all_elements.select { |element| fetch_value(element, how) =~ what }
138
- end
139
- end
140
-
141
- def wd_find_by_regexp_selector(selector, method = :find)
142
- parent = @wd
143
- rx_selector = delete_regexps_from(selector)
144
-
145
- if rx_selector.key?(:label) && should_use_label_element?
146
- label = label_from_text(rx_selector.delete(:label)) || return
147
- if (id = label.attribute(:for))
148
- selector[:id] = id
149
- else
150
- parent = label
151
- end
152
- end
153
-
154
- how, what = build_wd_selector(selector)
155
-
156
- unless how
157
- raise Error, "internal error: unable to build WebDriver selector from #{selector.inspect}"
158
- end
159
-
160
- if how == :xpath && can_convert_regexp_to_contains?
161
- rx_selector.each do |key, value|
162
- next if key == :tag_name || key == :text
163
-
164
- predicates = regexp_selector_to_predicates(key, value)
165
- what = "(#{what})[#{predicates.join(' and ')}]" unless predicates.empty?
166
- end
167
- end
168
-
169
- elements = parent.find_elements(how, what)
170
- elements.__send__(method) { |el| matches_selector?(el, rx_selector) }
171
- end
172
-
173
- VALID_WHATS = [String, Regexp]
174
-
175
- def check_type(how, what)
176
- case how
177
- when :index
178
- unless what.kind_of?(Fixnum)
179
- raise TypeError, "expected Fixnum, got #{what.inspect}:#{what.class}"
180
- end
181
- else
182
- unless VALID_WHATS.any? { |t| what.kind_of? t }
183
- raise TypeError, "expected one of #{VALID_WHATS.inspect}, got #{what.inspect}:#{what.class}"
184
- end
185
- end
186
- end
187
-
188
- def label_from_text(label_exp)
189
- # TODO: this won't work correctly if @wd is a sub-element
190
- @wd.find_elements(:tag_name, 'label').find do |el|
191
- matches_selector?(el, text: label_exp)
192
- end
193
- end
194
-
195
- def fetch_value(element, how)
196
- case how
197
- when :text
198
- element.text
199
- when :tag_name
200
- element.tag_name.downcase
201
- when :href
202
- (href = element.attribute(:href)) && href.strip
203
- else
204
- element.attribute(how.to_s.gsub("_", "-").to_sym)
205
- end
206
- end
207
-
208
- def matches_selector?(element, selector)
209
- selector.all? do |how, what|
210
- what === fetch_value(element, how)
211
- end
212
- end
213
-
214
- def normalized_selector
215
- selector = {}
216
-
217
- @selector.each do |how, what|
218
- check_type(how, what)
219
-
220
- how, what = normalize_selector(how, what)
221
- selector[how] = what
222
- end
223
-
224
- selector
225
- end
226
-
227
- def normalize_selector(how, what)
228
- case how
229
- when :tag_name, :text, :xpath, :index, :class, :label, :css
230
- # include :class since the valid attribute is 'class_name'
231
- # include :for since the valid attribute is 'html_for'
232
- [how, what]
233
- when :class_name
234
- [:class, what]
235
- when :caption
236
- [:text, what]
237
- else
238
- assert_valid_as_attribute how
239
- [how, what]
240
- end
241
- end
242
-
243
- def delete_regexps_from(selector)
244
- rx_selector = {}
245
-
246
- selector.dup.each do |how, what|
247
- next unless what.kind_of?(Regexp)
248
- rx_selector[how] = what
249
- selector.delete how
250
- end
251
-
252
- rx_selector
253
- end
254
-
255
- def regexp_selector_to_predicates(key, re)
256
- return [] if re.casefold?
257
-
258
- match = re.source.match(CONVERTABLE_REGEXP)
259
- return [] unless match
260
-
261
- lhs = lhs_for(key)
262
- match.captures.reject(&:empty?).map do |literals|
263
- "contains(#{lhs}, #{XpathSupport.escape(literals)})"
264
- end
265
- end
266
-
267
- def can_convert_regexp_to_contains?
268
- true
269
- end
270
-
271
- def assert_valid_as_attribute(attribute)
272
- unless valid_attribute? attribute or attribute.to_s =~ WILDCARD_ATTRIBUTE
273
- raise MissingWayOfFindingObjectException, "invalid attribute: #{attribute.inspect}"
274
- end
275
- end
276
-
277
- def by_id
278
- return unless id = @selector[:id] and id.kind_of? String
279
-
280
- selector = @selector.dup
281
- selector.delete(:id)
282
-
283
- tag_name = selector.delete(:tag_name)
284
- return unless selector.empty? # multiple attributes
285
-
286
- element = @wd.find_element(:id, id)
287
- return if tag_name && !tag_name_matches?(element.tag_name.downcase, tag_name)
288
-
289
- element
290
- end
291
-
292
- def all_elements
293
- @wd.find_elements(xpath: ".//*")
294
- end
295
-
296
- def tag_name_matches?(element_tag_name, tag_name)
297
- tag_name === element_tag_name
298
- end
299
-
300
- def valid_attribute?(attribute)
301
- @valid_attributes && @valid_attributes.include?(attribute)
302
- end
303
-
304
- def should_use_label_element?
305
- !valid_attribute?(:label)
306
- end
307
-
308
- def build_wd_selector(selectors)
309
- unless selectors.values.any? { |e| e.kind_of? Regexp }
310
- build_css(selectors) || build_xpath(selectors)
311
- end
312
- end
313
-
314
- def build_xpath(selectors)
315
- xpath = ".//"
316
- xpath << (selectors.delete(:tag_name) || '*').to_s
317
-
318
- idx = selectors.delete :index
319
-
320
- # the remaining entries should be attributes
321
- unless selectors.empty?
322
- xpath << "[" << attribute_expression(selectors) << "]"
323
- end
324
-
325
- p xpath: xpath, selectors: selectors if $DEBUG
326
-
327
- [:xpath, xpath]
328
- end
329
-
330
- def build_css(selectors)
331
- return unless use_css?(selectors)
332
-
333
- if selectors.empty?
334
- css = '*'
335
- else
336
- css = ''
337
- css << (selectors.delete(:tag_name) || '')
338
-
339
- klass = selectors.delete(:class)
340
- if klass
341
- if klass.include? ' '
342
- css << %([class="#{css_escape klass}"])
343
- else
344
- css << ".#{klass}"
345
- end
346
- end
347
-
348
- href = selectors.delete(:href)
349
- if href
350
- css << %([href~="#{css_escape href}"])
351
- end
352
-
353
- selectors.each do |key, value|
354
- key = key.to_s.gsub("_", "-")
355
- css << %([#{key}="#{css_escape value}"]) # TODO: proper escaping
356
- end
357
- end
358
-
359
- [:css, css]
360
- end
361
-
362
- def use_css?(selectors)
363
- return false unless Watir.prefer_css?
364
-
365
- if selectors.key?(:text) || selectors.key?(:label) || selectors.key?(:index)
366
- return false
367
- end
368
-
369
- if selectors[:tag_name] == 'input' && selectors.key?(:type)
370
- return false
371
- end
372
-
373
- if selectors.key?(:class) && selectors[:class] !~ /^[\w-]+$/ui
374
- return false
375
- end
376
-
377
- true
378
- end
379
-
380
- def attribute_expression(selectors)
381
- selectors.map do |key, val|
382
- if val.kind_of?(Array)
383
- "(" + val.map { |v| equal_pair(key, v) }.join(" or ") + ")"
384
- else
385
- equal_pair(key, val)
386
- end
387
- end.join(" and ")
388
- end
389
-
390
- def css_escape(str)
391
- str.gsub('"', '\\"')
392
- end
393
-
394
- def equal_pair(key, value)
395
- if key == :class
396
- klass = XpathSupport.escape " #{value} "
397
- "contains(concat(' ', @class, ' '), #{klass})"
398
- elsif key == :label && should_use_label_element?
399
- # we assume :label means a corresponding label element, not the attribute
400
- text = "normalize-space()=#{XpathSupport.escape value}"
401
- "(@id=//label[#{text}]/@for or parent::label[#{text}])"
402
- else
403
- "#{lhs_for(key)}=#{XpathSupport.escape value}"
404
- end
405
- end
406
-
407
- def lhs_for(key)
408
- case key
409
- when :text, 'text'
410
- 'normalize-space()'
411
- when :href
412
- # TODO: change this behaviour?
413
- 'normalize-space(@href)'
414
- when :type
415
- # type attributes can be upper case - downcase them
416
- # https://github.com/watir/watir-webdriver/issues/72
417
- XpathSupport.downcase('@type')
418
- else
419
- "@#{key.to_s.gsub("_", "-")}"
420
- end
421
- end
422
-
423
- def validate_element(element)
424
- tn = @selector[:tag_name]
425
- element_tag_name = element.tag_name.downcase
426
-
427
- return if tn && !tag_name_matches?(element_tag_name, tn)
428
-
429
- if element_tag_name == 'input'
430
- return if @selector[:type] && @selector[:type] != element.attribute(:type)
431
- end
432
-
433
- element
434
- end
435
-
436
- def given_xpath_or_css(selector)
437
- xpath = selector.delete(:xpath)
438
- css = selector.delete(:css)
439
- return unless xpath || css
440
-
441
- if xpath && css
442
- raise ArgumentError, ":xpath and :css cannot be combined (#{selector.inspect})"
443
- end
444
-
445
- how, what = if xpath
446
- [:xpath, xpath]
447
- elsif css
448
- [:css, css]
449
- end
450
-
451
- if selector.any? && !can_be_combined_with_xpath_or_css?(selector)
452
- raise ArgumentError, "#{how} cannot be combined with other selectors (#{selector.inspect})"
453
- end
454
-
455
- [how, what]
456
- end
457
-
458
- def can_be_combined_with_xpath_or_css?(selector)
459
- keys = selector.keys
460
- return true if keys == [:tag_name]
461
-
462
- if selector[:tag_name] == "input"
463
- return keys == [:tag_name, :type] || keys == [:type, :tag_name]
464
- end
465
-
466
- false
467
- end
468
-
469
- end # ElementLocator
470
- end # Watir