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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +7 -3
- data/CHANGES.md +6 -0
- data/Rakefile +17 -5
- data/lib/watir-webdriver.rb +16 -6
- data/lib/watir-webdriver/after_hooks.rb +2 -2
- data/lib/watir-webdriver/browser.rb +5 -5
- data/lib/watir-webdriver/cell_container.rb +4 -10
- data/lib/watir-webdriver/element_collection.rb +28 -7
- data/lib/watir-webdriver/elements/button.rb +0 -18
- data/lib/watir-webdriver/elements/cell.rb +17 -0
- data/lib/watir-webdriver/elements/checkbox.rb +0 -3
- data/lib/watir-webdriver/elements/element.rb +37 -10
- data/lib/watir-webdriver/elements/file_field.rb +0 -3
- data/lib/watir-webdriver/elements/hidden.rb +0 -3
- data/lib/watir-webdriver/elements/html_elements.rb +0 -219
- data/lib/watir-webdriver/elements/iframe.rb +29 -19
- data/lib/watir-webdriver/elements/row.rb +17 -0
- data/lib/watir-webdriver/elements/svg_elements.rb +0 -120
- data/lib/watir-webdriver/elements/table.rb +1 -1
- data/lib/watir-webdriver/elements/table_cell.rb +0 -28
- data/lib/watir-webdriver/elements/table_row.rb +1 -30
- data/lib/watir-webdriver/elements/text_area.rb +0 -17
- data/lib/watir-webdriver/elements/text_field.rb +2 -8
- data/lib/watir-webdriver/generator/base/visitor.rb +1 -10
- data/lib/watir-webdriver/locators.rb +22 -0
- data/lib/watir-webdriver/locators/button/locator.rb +38 -0
- data/lib/watir-webdriver/locators/button/selector_builder.rb +27 -0
- data/lib/watir-webdriver/locators/button/selector_builder/xpath.rb +29 -0
- data/lib/watir-webdriver/locators/button/validator.rb +14 -0
- data/lib/watir-webdriver/locators/cell/locator.rb +17 -0
- data/lib/watir-webdriver/locators/cell/selector_builder.rb +24 -0
- data/lib/watir-webdriver/locators/element/locator.rb +249 -0
- data/lib/watir-webdriver/locators/element/selector_builder.rb +147 -0
- data/lib/watir-webdriver/locators/element/selector_builder/css.rb +65 -0
- data/lib/watir-webdriver/locators/element/selector_builder/xpath.rb +72 -0
- data/lib/watir-webdriver/locators/element/validator.rb +22 -0
- data/lib/watir-webdriver/locators/row/locator.rb +17 -0
- data/lib/watir-webdriver/locators/row/selector_builder.rb +29 -0
- data/lib/watir-webdriver/locators/text_area/locator.rb +13 -0
- data/lib/watir-webdriver/locators/text_area/selector_builder.rb +22 -0
- data/lib/watir-webdriver/locators/text_field/locator.rb +42 -0
- data/lib/watir-webdriver/locators/text_field/selector_builder.rb +34 -0
- data/lib/watir-webdriver/locators/text_field/selector_builder/xpath.rb +19 -0
- data/lib/watir-webdriver/locators/text_field/validator.rb +20 -0
- data/lib/watir-webdriver/row_container.rb +3 -9
- data/lib/watir-webdriver/version.rb +1 -1
- data/lib/watir-webdriver/wait.rb +6 -3
- data/spec/always_locate_spec.rb +2 -1
- data/spec/element_locator_spec.rb +1 -1
- data/spec/element_spec.rb +8 -40
- data/spec/implementation.rb +52 -45
- data/spec/locator_spec_helper.rb +8 -2
- data/support/travis.sh +17 -6
- metadata +25 -10
- data/lib/watir-webdriver/locators/button_locator.rb +0 -85
- data/lib/watir-webdriver/locators/child_cell_locator.rb +0 -32
- data/lib/watir-webdriver/locators/child_row_locator.rb +0 -37
- data/lib/watir-webdriver/locators/element_locator.rb +0 -470
- data/lib/watir-webdriver/locators/text_area_locator.rb +0 -24
- 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,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,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
|