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
@@ -0,0 +1,147 @@
1
+ require 'watir-webdriver/locators/element/selector_builder/css'
2
+ require 'watir-webdriver/locators/element/selector_builder/xpath'
3
+
4
+ module Watir
5
+ module Locators
6
+ class Element
7
+ class SelectorBuilder
8
+ VALID_WHATS = [String, Regexp]
9
+ WILDCARD_ATTRIBUTE = /^(aria|data)_(.+)$/
10
+
11
+ def initialize(parent, selector, valid_attributes)
12
+ @parent = parent # either element or browser
13
+ @selector = selector
14
+ @valid_attributes = valid_attributes
15
+ end
16
+
17
+ def normalized_selector
18
+ selector = {}
19
+
20
+ @selector.each do |how, what|
21
+ check_type(how, what)
22
+
23
+ how, what = normalize_selector(how, what)
24
+ selector[how] = what
25
+ end
26
+
27
+ selector
28
+ end
29
+
30
+ def check_type(how, what)
31
+ case how
32
+ when :index
33
+ unless what.is_a?(Fixnum)
34
+ raise TypeError, "expected Fixnum, got #{what.inspect}:#{what.class}"
35
+ end
36
+ else
37
+ unless VALID_WHATS.any? { |t| what.is_a? t }
38
+ raise TypeError, "expected one of #{VALID_WHATS.inspect}, got #{what.inspect}:#{what.class}"
39
+ end
40
+ end
41
+ end
42
+
43
+ def should_use_label_element?
44
+ !valid_attribute?(:label)
45
+ end
46
+
47
+ def build(selector)
48
+ given_xpath_or_css(selector) || build_wd_selector(selector)
49
+ end
50
+
51
+ def xpath_builder
52
+ @xpath_builder ||= xpath_builder_class.new(should_use_label_element?)
53
+ end
54
+
55
+ private
56
+
57
+ def normalize_selector(how, what)
58
+ case how
59
+ when :tag_name, :text, :xpath, :index, :class, :label, :css
60
+ # include :class since the valid attribute is 'class_name'
61
+ # include :for since the valid attribute is 'html_for'
62
+ [how, what]
63
+ when :class_name
64
+ [:class, what]
65
+ when :caption
66
+ [:text, what]
67
+ else
68
+ assert_valid_as_attribute how
69
+ [how, what]
70
+ end
71
+ end
72
+
73
+ def assert_valid_as_attribute(attribute)
74
+ return if valid_attribute?(attribute) || attribute.to_s =~ WILDCARD_ATTRIBUTE
75
+ raise Exception::MissingWayOfFindingObjectException, "invalid attribute: #{attribute.inspect}"
76
+ end
77
+
78
+ def given_xpath_or_css(selector)
79
+ xpath = selector.delete(:xpath)
80
+ css = selector.delete(:css)
81
+ return unless xpath || css
82
+
83
+ if xpath && css
84
+ raise ArgumentError, ":xpath and :css cannot be combined (#{selector.inspect})"
85
+ end
86
+
87
+ how, what = if xpath
88
+ [:xpath, xpath]
89
+ elsif css
90
+ [:css, css]
91
+ end
92
+
93
+ if selector.any? && !can_be_combined_with_xpath_or_css?(selector)
94
+ raise ArgumentError, "#{how} cannot be combined with other selectors (#{selector.inspect})"
95
+ end
96
+
97
+ [how, what]
98
+ end
99
+
100
+ def build_wd_selector(selectors)
101
+ unless selectors.values.any? { |e| e.is_a? Regexp }
102
+ build_css(selectors) || build_xpath(selectors)
103
+ end
104
+ end
105
+
106
+ def valid_attribute?(attribute)
107
+ @valid_attributes && @valid_attributes.include?(attribute)
108
+ end
109
+
110
+ def can_be_combined_with_xpath_or_css?(selector)
111
+ keys = selector.keys
112
+ return true if keys == [:tag_name]
113
+
114
+ if selector[:tag_name] == "input"
115
+ return keys.sort == [:tag_name, :type]
116
+ end
117
+
118
+ false
119
+ end
120
+
121
+ def build_xpath(selectors)
122
+ xpath_builder.build(selectors)
123
+ end
124
+
125
+ def build_css(selectors)
126
+ css_builder.build(selectors)
127
+ end
128
+
129
+ def xpath_builder_class
130
+ Kernel.const_get("#{self.class.name}::XPath")
131
+ rescue
132
+ XPath
133
+ end
134
+
135
+ def css_builder
136
+ @css_builder ||= css_builder_class.new
137
+ end
138
+
139
+ def css_builder_class
140
+ Kernel.const_get("#{self.class.name}::CSS")
141
+ rescue
142
+ CSS
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,65 @@
1
+ module Watir
2
+ module Locators
3
+ class Element
4
+ class SelectorBuilder
5
+ class CSS
6
+ def build(selectors)
7
+ return unless use_css?(selectors)
8
+
9
+ if selectors.empty?
10
+ css = '*'
11
+ else
12
+ css = ''
13
+ css << (selectors.delete(:tag_name) || '')
14
+
15
+ klass = selectors.delete(:class)
16
+ if klass
17
+ if klass.include? ' '
18
+ css << %([class="#{css_escape klass}"])
19
+ else
20
+ css << ".#{klass}"
21
+ end
22
+ end
23
+
24
+ href = selectors.delete(:href)
25
+ if href
26
+ css << %([href~="#{css_escape href}"])
27
+ end
28
+
29
+ selectors.each do |key, value|
30
+ key = key.to_s.tr("_", "-")
31
+ css << %([#{key}="#{css_escape value}"]) # TODO: proper escaping
32
+ end
33
+ end
34
+
35
+ [:css, css]
36
+ end
37
+
38
+ private
39
+
40
+ def use_css?(selectors)
41
+ return false unless Watir.prefer_css?
42
+
43
+ if selectors.key?(:text) || selectors.key?(:label) || selectors.key?(:index)
44
+ return false
45
+ end
46
+
47
+ if selectors[:tag_name] == 'input' && selectors.key?(:type)
48
+ return false
49
+ end
50
+
51
+ if selectors.key?(:class) && selectors[:class] !~ /^[\w-]+$/ui
52
+ return false
53
+ end
54
+
55
+ true
56
+ end
57
+
58
+ def css_escape(str)
59
+ str.gsub('"', '\\"')
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,72 @@
1
+ module Watir
2
+ module Locators
3
+ class Element
4
+ class SelectorBuilder
5
+ class XPath
6
+ def initialize(should_use_label_element)
7
+ @should_use_label_element = should_use_label_element
8
+ end
9
+
10
+ def build(selectors)
11
+ xpath = ".//"
12
+ xpath << (selectors.delete(:tag_name) || '*').to_s
13
+
14
+ selectors.delete :index
15
+
16
+ # the remaining entries should be attributes
17
+ unless selectors.empty?
18
+ xpath << "[" << attribute_expression(nil, selectors) << "]"
19
+ end
20
+
21
+ p xpath: xpath, selectors: selectors if $DEBUG
22
+
23
+ [:xpath, xpath]
24
+ end
25
+
26
+ # @todo Get rid of building
27
+ def attribute_expression(building, selectors)
28
+ f = selectors.map do |key, val|
29
+ if val.is_a?(Array)
30
+ "(" + val.map { |v| equal_pair(building, key, v) }.join(" or ") + ")"
31
+ else
32
+ equal_pair(building, key, val)
33
+ end
34
+ end
35
+ f.join(" and ")
36
+ end
37
+
38
+ # @todo Get rid of building
39
+ def equal_pair(building, key, value)
40
+ if key == :class
41
+ klass = XpathSupport.escape " #{value} "
42
+ "contains(concat(' ', @class, ' '), #{klass})"
43
+ elsif key == :label && @should_use_label_element
44
+ # we assume :label means a corresponding label element, not the attribute
45
+ text = "normalize-space()=#{XpathSupport.escape value}"
46
+ "(@id=//label[#{text}]/@for or parent::label[#{text}])"
47
+ else
48
+ "#{lhs_for(building, key)}=#{XpathSupport.escape value}"
49
+ end
50
+ end
51
+
52
+ # @todo Get rid of building
53
+ def lhs_for(_building, key)
54
+ case key
55
+ when :text, 'text'
56
+ 'normalize-space()'
57
+ when :href
58
+ # TODO: change this behaviour?
59
+ 'normalize-space(@href)'
60
+ when :type
61
+ # type attributes can be upper case - downcase them
62
+ # https://github.com/watir/watir-webdriver/issues/72
63
+ XpathSupport.downcase('@type')
64
+ else
65
+ "@#{key.to_s.tr("_", "-")}"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,22 @@
1
+ module Watir
2
+ module Locators
3
+ class Element
4
+ class Validator
5
+ def validate(element, selector)
6
+ selector_tag_name = selector[:tag_name]
7
+ element_tag_name = element.tag_name.downcase
8
+
9
+ if selector_tag_name
10
+ return unless selector_tag_name === element_tag_name
11
+ end
12
+
13
+ if element_tag_name == 'input'
14
+ return if selector[:type] && selector[:type] != element.attribute(:type)
15
+ end
16
+
17
+ element
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Watir
2
+ module Locators
3
+ class Row
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 # avoid this
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module Watir
2
+ module Locators
3
+ class Row
4
+ class SelectorBuilder < Element::SelectorBuilder
5
+ def build_wd_selector(selectors)
6
+ return if selectors.values.any? { |e| e.kind_of? Regexp }
7
+ selectors.delete(:tag_name) || raise("internal error: no tag_name?!")
8
+
9
+ expressions = %w[./tr]
10
+ unless %w[tbody tfoot thead].include?(@parent.tag_name.downcase)
11
+ expressions += %w[./tbody/tr ./thead/tr ./tfoot/tr]
12
+ end
13
+
14
+ attr_expr = xpath_builder.attribute_expression(nil, selectors)
15
+
16
+ unless attr_expr.empty?
17
+ expressions.map! { |e| "#{e}[#{attr_expr}]" }
18
+ end
19
+
20
+ xpath = expressions.join(" | ")
21
+
22
+ p build_wd_selector: xpath if $DEBUG
23
+
24
+ [:xpath, xpath]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Watir
2
+ module Locators
3
+ class TextArea
4
+ class Locator < Element::Locator
5
+ private
6
+
7
+ def can_convert_regexp_to_contains?
8
+ false
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module Watir
2
+ module Locators
3
+ class TextArea
4
+ class SelectorBuilder < Element::SelectorBuilder
5
+ private
6
+
7
+ def normalize_selector(how, what)
8
+ # We need to iterate through located elements and fetch
9
+ # attribute value using WebDriver because XPath doesn't understand
10
+ # difference between IDL vs content attribute.
11
+ # Current Element design doesn't allow to do that in any
12
+ # obvious way except to use regular expression.
13
+ if how == :value && what.kind_of?(String)
14
+ [how, Regexp.new('^' + Regexp.escape(what) + '$')]
15
+ else
16
+ super
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ module Watir
2
+ module Locators
3
+ class TextField
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
+ how, what = selector_builder.build_wd_selector(how => what) if how == :tag_name
13
+ super
14
+ end
15
+
16
+ def matches_selector?(element, rx_selector)
17
+ rx_selector = rx_selector.dup
18
+
19
+ tag_name = element.tag_name.downcase
20
+
21
+ [:text, :value, :label].each do |key|
22
+ if rx_selector.key?(key)
23
+ correct_key = tag_name == 'input' ? :value : :text
24
+ rx_selector[correct_key] = rx_selector.delete(key)
25
+ end
26
+ end
27
+
28
+ super
29
+ end
30
+
31
+ def by_id
32
+ element = super
33
+
34
+ if element && !Watir::TextField::NON_TEXT_TYPES.include?(element.attribute(:type))
35
+ warn "Locating textareas with '#text_field' is deprecated. Please, use '#textarea' method instead."
36
+ element
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ module Watir
2
+ module Locators
3
+ class TextField
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)
9
+
10
+ textarea_attr_exp = xpath_builder.attribute_expression(:textarea, selectors)
11
+ input_attr_exp = xpath_builder.attribute_expression(:input, selectors)
12
+
13
+ xpath = ".//input[(not(@type) or (#{negative_type_expr}))"
14
+ xpath << " and #{input_attr_exp}" unless input_attr_exp.empty?
15
+ xpath << "] "
16
+ xpath << "| .//textarea"
17
+ xpath << "[#{textarea_attr_exp}]" unless textarea_attr_exp.empty?
18
+
19
+ p build_wd_selector: xpath if $DEBUG
20
+
21
+ [:xpath, xpath]
22
+ end
23
+
24
+ private
25
+
26
+ def negative_type_expr
27
+ Watir::TextField::NON_TEXT_TYPES.map do |type|
28
+ "%s!=%s" % [XpathSupport.downcase('@type'), type.inspect]
29
+ end.join(' and ')
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end