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
@@ -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