watir 6.15.1 → 6.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -2
- data/.travis.yml +2 -0
- data/CHANGES.md +13 -0
- data/Rakefile +6 -0
- data/lib/watir.rb +1 -0
- data/lib/watir/browser.rb +4 -1
- data/lib/watir/element_collection.rb +27 -17
- data/lib/watir/elements/element.rb +41 -14
- data/lib/watir/elements/iframe.rb +3 -1
- data/lib/watir/elements/radio.rb +7 -2
- data/lib/watir/elements/select.rb +1 -0
- data/lib/watir/locators.rb +21 -21
- data/lib/watir/locators/button/matcher.rb +40 -0
- data/lib/watir/locators/cell/selector_builder.rb +3 -0
- data/lib/watir/locators/element/locator.rb +29 -172
- data/lib/watir/locators/element/matcher.rb +127 -0
- data/lib/watir/locators/element/selector_builder.rb +69 -23
- data/lib/watir/locators/element/selector_builder/xpath.rb +3 -10
- data/lib/watir/locators/row/selector_builder.rb +5 -5
- data/lib/watir/locators/text_area/selector_builder.rb +0 -14
- data/lib/watir/locators/text_area/selector_builder/xpath.rb +2 -2
- data/lib/watir/locators/text_field/matcher.rb +38 -0
- data/lib/watir/radio_set.rb +28 -31
- data/lib/watir/scroll.rb +69 -0
- data/lib/watir/version.rb +1 -1
- data/spec/locator_spec_helper.rb +58 -14
- data/spec/unit/element_locator_spec.rb +46 -591
- data/spec/unit/match_elements/button_spec.rb +80 -0
- data/spec/unit/match_elements/element_spec.rb +368 -0
- data/spec/unit/match_elements/text_field_spec.rb +79 -0
- data/spec/unit/selector_builder/anchor_spec.rb +51 -0
- data/spec/unit/selector_builder/button_spec.rb +206 -0
- data/spec/unit/selector_builder/cell_spec.rb +63 -0
- data/spec/unit/selector_builder/element_spec.rb +744 -0
- data/spec/unit/selector_builder/row_spec.rb +111 -0
- data/spec/unit/selector_builder/text_field_spec.rb +189 -0
- data/spec/unit/selector_builder/textarea_spec.rb +25 -0
- data/spec/watirspec/browser_spec.rb +7 -8
- data/spec/watirspec/element_hidden_spec.rb +1 -2
- data/spec/watirspec/elements/element_spec.rb +52 -16
- data/spec/watirspec/elements/iframe_spec.rb +1 -1
- data/spec/watirspec/elements/select_list_spec.rb +1 -1
- data/spec/watirspec/html/obscured.html +3 -1
- data/spec/watirspec/html/scroll.html +32 -0
- data/spec/watirspec/relaxed_locate_spec.rb +6 -1
- data/spec/watirspec/scroll_spec.rb +106 -0
- data/spec/watirspec/support/rspec_matchers.rb +2 -0
- data/spec/watirspec/wait_spec.rb +1 -1
- data/watir.gemspec +2 -4
- metadata +36 -33
- data/lib/watir/locators/button/locator.rb +0 -32
- data/lib/watir/locators/button/validator.rb +0 -17
- data/lib/watir/locators/cell/locator.rb +0 -13
- data/lib/watir/locators/element/validator.rb +0 -11
- data/lib/watir/locators/row/locator.rb +0 -13
- data/lib/watir/locators/text_field/locator.rb +0 -31
- data/lib/watir/locators/text_field/validator.rb +0 -13
- data/spec/unit/anchor_locator_spec.rb +0 -68
- data/spec/watirspec/selector_builder/button_spec.rb +0 -250
- data/spec/watirspec/selector_builder/cell_spec.rb +0 -92
- data/spec/watirspec/selector_builder/element_spec.rb +0 -628
- data/spec/watirspec/selector_builder/row_spec.rb +0 -148
- data/spec/watirspec/selector_builder/text_spec.rb +0 -199
@@ -17,6 +17,7 @@ module Watir
|
|
17
17
|
|
18
18
|
index = @selector.delete(:index)
|
19
19
|
@adjacent = @selector.delete(:adjacent)
|
20
|
+
@scope = @selector.delete(:scope)
|
20
21
|
|
21
22
|
xpath = start_string
|
22
23
|
xpath << adjacent_string
|
@@ -75,7 +76,8 @@ module Watir
|
|
75
76
|
end
|
76
77
|
|
77
78
|
def start_string
|
78
|
-
@adjacent ? './' : './/*'
|
79
|
+
start = @adjacent ? './' : './/*'
|
80
|
+
@scope ? "(#{@scope[:xpath]})[1]#{start.tr('.', '')}" : start
|
79
81
|
end
|
80
82
|
|
81
83
|
def adjacent_string
|
@@ -104,8 +106,6 @@ module Watir
|
|
104
106
|
class_name = @selector.delete(:class)
|
105
107
|
return '' if class_name.nil?
|
106
108
|
|
107
|
-
deprecate_class_array(class_name) if class_name.is_a?(String) && class_name.strip.include?(' ')
|
108
|
-
|
109
109
|
@built[:class] = []
|
110
110
|
|
111
111
|
predicates = [class_name].flatten.map { |value| process_attribute(:class, value) }.compact
|
@@ -181,13 +181,6 @@ module Watir
|
|
181
181
|
end
|
182
182
|
end
|
183
183
|
|
184
|
-
def deprecate_class_array(class_name)
|
185
|
-
dep = "Using the :class locator to locate multiple classes with a String value (i.e. \"#{class_name}\")"
|
186
|
-
Watir.logger.deprecate dep,
|
187
|
-
"Array (e.g. #{class_name.split})",
|
188
|
-
ids: [:class_array]
|
189
|
-
end
|
190
|
-
|
191
184
|
def visible?
|
192
185
|
!(@built.keys & CAN_NOT_BUILD).empty?
|
193
186
|
end
|
@@ -2,13 +2,13 @@ module Watir
|
|
2
2
|
module Locators
|
3
3
|
class Row
|
4
4
|
class SelectorBuilder < Element::SelectorBuilder
|
5
|
-
def
|
6
|
-
|
7
|
-
|
5
|
+
def build_wd_selector(selector)
|
6
|
+
scope_tag_name = @query_scope.selector[:tag_name] || @query_scope.tag_name
|
7
|
+
Kernel.const_get("#{self.class.name}::XPath").new.build(selector, scope_tag_name)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
10
|
+
def use_scope?
|
11
|
+
false
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -2,20 +2,6 @@ module Watir
|
|
2
2
|
module Locators
|
3
3
|
class TextArea
|
4
4
|
class SelectorBuilder < Element::SelectorBuilder
|
5
|
-
private
|
6
|
-
|
7
|
-
def normalize_locator(how, what)
|
8
|
-
# We need to iterate through located elements and fetch
|
9
|
-
# attribute value using Selenium 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.is_a?(String)
|
14
|
-
[how, Regexp.new('^' + Regexp.escape(what) + '$')]
|
15
|
-
else
|
16
|
-
super
|
17
|
-
end
|
18
|
-
end
|
19
5
|
end
|
20
6
|
end
|
21
7
|
end
|
@@ -6,10 +6,10 @@ module Watir
|
|
6
6
|
private
|
7
7
|
|
8
8
|
# value always requires a wire call since we want the property not the attribute
|
9
|
-
def
|
9
|
+
def process_attribute(key, value)
|
10
10
|
return super unless key == :value
|
11
11
|
|
12
|
-
@built[:value] =
|
12
|
+
@built[:value] = value
|
13
13
|
nil
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Watir
|
2
|
+
module Locators
|
3
|
+
class TextField
|
4
|
+
class Matcher < Element::Matcher
|
5
|
+
private
|
6
|
+
|
7
|
+
def elements_match?(element, values_to_match)
|
8
|
+
case element.tag_name.downcase
|
9
|
+
when 'input'
|
10
|
+
%i[text label visible_text].each do |key|
|
11
|
+
next unless values_to_match.key?(key)
|
12
|
+
|
13
|
+
values_to_match[:value] = values_to_match.delete(key)
|
14
|
+
end
|
15
|
+
when 'label'
|
16
|
+
%i[value label].each do |key|
|
17
|
+
next unless values_to_match.key?(key)
|
18
|
+
|
19
|
+
values_to_match[:text] = values_to_match.delete(key)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def text_regexp_deprecation(*)
|
29
|
+
# does not apply to text_field
|
30
|
+
end
|
31
|
+
|
32
|
+
def validate_tag(element, _tag_name)
|
33
|
+
matches_values?(element.tag_name.downcase, 'input')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/watir/radio_set.rb
CHANGED
@@ -46,13 +46,12 @@ module Watir
|
|
46
46
|
#
|
47
47
|
|
48
48
|
def radio(opt = {})
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
return source
|
49
|
+
if !name.empty? && (!opt[:name] || opt[:name] == name)
|
50
|
+
frame.radio(opt.merge(name: name))
|
51
|
+
elsif name.empty?
|
52
|
+
source
|
54
53
|
else
|
55
|
-
raise UnknownObjectException, "#{opt[:name]} does not match name of RadioSet: #{
|
54
|
+
raise UnknownObjectException, "#{opt[:name]} does not match name of RadioSet: #{name}"
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
@@ -61,13 +60,12 @@ module Watir
|
|
61
60
|
#
|
62
61
|
|
63
62
|
def radios(opt = {})
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
RadioCollection.new(frame, element: source.wd)
|
63
|
+
if !name.empty? && (!opt[:name] || opt[:name] == name)
|
64
|
+
element_call(:wait_for_present) { frame.radios(opt.merge(name: name)) }
|
65
|
+
elsif name.empty?
|
66
|
+
single_radio_collection
|
69
67
|
else
|
70
|
-
raise UnknownObjectException, "#{opt[:name]} does not match name of RadioSet: #{
|
68
|
+
raise UnknownObjectException, "#{opt[:name]} does not match name of RadioSet: #{name}"
|
71
69
|
end
|
72
70
|
end
|
73
71
|
|
@@ -132,18 +130,14 @@ module Watir
|
|
132
130
|
#
|
133
131
|
|
134
132
|
def select(str_or_rx)
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
return
|
141
|
-
elsif found_by_text.exist?
|
142
|
-
found_by_text.click unless found_by_text.selected?
|
143
|
-
return found_by_text.text
|
144
|
-
else
|
145
|
-
raise UnknownObjectException, "Unable to locate radio matching #{str_or_rx.inspect}"
|
133
|
+
%i[value label].each do |key|
|
134
|
+
radio = radio(key => str_or_rx)
|
135
|
+
next unless radio.exist?
|
136
|
+
|
137
|
+
radio.click unless radio.selected?
|
138
|
+
return key == :value ? radio.value : radio.text
|
146
139
|
end
|
140
|
+
raise UnknownObjectException, "Unable to locate radio matching #{str_or_rx.inspect}"
|
147
141
|
end
|
148
142
|
|
149
143
|
#
|
@@ -156,7 +150,6 @@ module Watir
|
|
156
150
|
|
157
151
|
def selected?(str_or_rx)
|
158
152
|
found = frame.radio(label: str_or_rx)
|
159
|
-
|
160
153
|
return found.selected? if found.exist?
|
161
154
|
|
162
155
|
raise UnknownObjectException, "Unable to locate radio matching #{str_or_rx.inspect}"
|
@@ -170,8 +163,7 @@ module Watir
|
|
170
163
|
#
|
171
164
|
|
172
165
|
def value
|
173
|
-
|
174
|
-
sel&.value
|
166
|
+
selected&.value
|
175
167
|
end
|
176
168
|
|
177
169
|
#
|
@@ -182,8 +174,7 @@ module Watir
|
|
182
174
|
#
|
183
175
|
|
184
176
|
def text
|
185
|
-
|
186
|
-
sel&.text
|
177
|
+
selected&.text
|
187
178
|
end
|
188
179
|
|
189
180
|
#
|
@@ -206,9 +197,7 @@ module Watir
|
|
206
197
|
#
|
207
198
|
|
208
199
|
def ==(other)
|
209
|
-
|
210
|
-
|
211
|
-
radios == other.radios
|
200
|
+
other.is_a?(self.class) && radios == other.radios
|
212
201
|
end
|
213
202
|
alias eql? ==
|
214
203
|
|
@@ -218,6 +207,14 @@ module Watir
|
|
218
207
|
source.send(method, *args, &blk)
|
219
208
|
end
|
220
209
|
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
def single_radio_collection
|
214
|
+
collection = RadioCollection.new(frame, source.selector)
|
215
|
+
collection.first.cache = source.wd
|
216
|
+
collection
|
217
|
+
end
|
221
218
|
end # RadioSet
|
222
219
|
|
223
220
|
module Container
|
data/lib/watir/scroll.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module Watir
|
2
|
+
module Scrolling
|
3
|
+
def scroll
|
4
|
+
Scroll.new(self)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Scroll
|
9
|
+
def initialize(object)
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
|
13
|
+
# Scrolls by offset.
|
14
|
+
# @param [Fixnum] left Horizontal offset
|
15
|
+
# @param [Fixnum] top Vertical offset
|
16
|
+
#
|
17
|
+
def by(left, top)
|
18
|
+
@object.browser.execute_script('window.scrollBy(arguments[0], arguments[1]);', Integer(left), Integer(top))
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Scrolls to specified location.
|
24
|
+
# @param [Symbol] param
|
25
|
+
#
|
26
|
+
def to(param = :top)
|
27
|
+
args = @object.is_a?(Watir::Element) ? element_scroll(param) : browser_scroll(param)
|
28
|
+
raise ArgumentError, "Don't know how to scroll #{@object} to: #{param}!" if args.nil?
|
29
|
+
|
30
|
+
@object.browser.execute_script(*args)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def element_scroll(param)
|
37
|
+
script = case param
|
38
|
+
when :top, :start
|
39
|
+
'arguments[0].scrollIntoView();'
|
40
|
+
when :center
|
41
|
+
<<-JS
|
42
|
+
var bodyRect = document.body.getBoundingClientRect();
|
43
|
+
var elementRect = arguments[0].getBoundingClientRect();
|
44
|
+
var left = (elementRect.left - bodyRect.left) - (window.innerWidth / 2);
|
45
|
+
var top = (elementRect.top - bodyRect.top) - (window.innerHeight / 2);
|
46
|
+
window.scrollTo(left, top);
|
47
|
+
JS
|
48
|
+
when :bottom, :end
|
49
|
+
'arguments[0].scrollIntoView(false);'
|
50
|
+
else
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
[script, @object]
|
54
|
+
end
|
55
|
+
|
56
|
+
def browser_scroll(param)
|
57
|
+
case param
|
58
|
+
when :top, :start
|
59
|
+
'window.scrollTo(0, 0);'
|
60
|
+
when :center
|
61
|
+
'window.scrollTo(window.outerWidth / 2, window.outerHeight / 2);'
|
62
|
+
when :bottom, :end
|
63
|
+
'window.scrollTo(0, document.body.scrollHeight);'
|
64
|
+
when Array
|
65
|
+
['window.scrollTo(arguments[0], arguments[1]);', Integer(param[0]), Integer(param[1])]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/watir/version.rb
CHANGED
data/spec/locator_spec_helper.rb
CHANGED
@@ -1,17 +1,40 @@
|
|
1
1
|
module LocatorSpecHelper
|
2
2
|
def browser
|
3
|
-
@browser ||=
|
3
|
+
@browser ||= instance_double(Watir::Browser, wd: driver)
|
4
|
+
allow(@browser).to receive(:browser).and_return(@browser)
|
5
|
+
allow(@browser).to receive(:is_a?).with(Watir::Browser).and_return(true)
|
6
|
+
allow(@browser).to receive(:locator_namespace).and_return(@locator_namespace || Watir::Locators)
|
7
|
+
@browser
|
4
8
|
end
|
5
9
|
|
6
10
|
def driver
|
7
|
-
@driver ||=
|
11
|
+
@driver ||= instance_double(Selenium::WebDriver::Driver)
|
8
12
|
end
|
9
13
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
selector_builder
|
14
|
-
|
14
|
+
def selector_builder
|
15
|
+
@built ||= {xpath: ''}
|
16
|
+
@selector_builder = instance_double(Watir::Locators::Element::SelectorBuilder)
|
17
|
+
allow(@selector_builder).to receive(:build).and_return(@built)
|
18
|
+
@selector_builder
|
19
|
+
end
|
20
|
+
|
21
|
+
def attributes
|
22
|
+
@attributes ||= Watir::HTMLElement.attribute_list
|
23
|
+
end
|
24
|
+
|
25
|
+
def query_scope
|
26
|
+
@query_scope ||= browser
|
27
|
+
end
|
28
|
+
|
29
|
+
def element_matcher
|
30
|
+
@element_matcher ||= instance_double(Watir::Locators::Element::Matcher)
|
31
|
+
allow(@element_matcher).to receive(:query_scope).and_return(browser)
|
32
|
+
allow(@element_matcher).to receive(:selector).and_return(@locator || {})
|
33
|
+
@element_matcher
|
34
|
+
end
|
35
|
+
|
36
|
+
def locator
|
37
|
+
Watir::Locators::Element::Locator.new(element_matcher)
|
15
38
|
end
|
16
39
|
|
17
40
|
def expect_one(*args)
|
@@ -22,25 +45,46 @@ module LocatorSpecHelper
|
|
22
45
|
expect(driver).to receive(:find_elements).with(*args)
|
23
46
|
end
|
24
47
|
|
25
|
-
def locate_one(selector
|
26
|
-
|
48
|
+
def locate_one(selector = nil)
|
49
|
+
selector ||= @locator || {}
|
50
|
+
locator.locate ordered_hash(selector)
|
51
|
+
end
|
52
|
+
|
53
|
+
def locate_all(selector = nil)
|
54
|
+
selector ||= @locator || {}
|
55
|
+
locator.locate_all ordered_hash(selector)
|
27
56
|
end
|
28
57
|
|
29
|
-
def
|
30
|
-
|
58
|
+
def selector_build(selector)
|
59
|
+
selector_builder.build(selector)
|
31
60
|
end
|
32
61
|
|
33
62
|
def element(opts = {})
|
34
|
-
|
35
|
-
|
63
|
+
raise unless opts.delete(:attributes).nil?
|
64
|
+
|
65
|
+
klass = opts.delete(:watir_element) || Watir::HTMLElement
|
66
|
+
el = instance_double(klass, opts)
|
67
|
+
|
68
|
+
allow(el).to receive(:enabled?).and_return true
|
69
|
+
allow(el).to receive(:selector_builder).and_return(selector_builder)
|
70
|
+
allow(el).to receive(:wd).and_return wd_element unless opts.key?(:wd)
|
71
|
+
allow(el).to receive(:selector).and_return(@selector || {})
|
72
|
+
el
|
73
|
+
end
|
36
74
|
|
75
|
+
def wd_element(opts = {})
|
76
|
+
attrs = opts.delete(:attributes)
|
77
|
+
el = instance_double(Selenium::WebDriver::Element, opts)
|
37
78
|
attrs&.each do |key, value|
|
38
79
|
allow(el).to receive(:attribute).with(key.to_s).and_return(value)
|
39
80
|
end
|
40
|
-
allow(el).to receive(:enabled?).and_return true
|
41
81
|
el
|
42
82
|
end
|
43
83
|
|
84
|
+
def el
|
85
|
+
@el ||= wd_element
|
86
|
+
end
|
87
|
+
|
44
88
|
def ordered_hash(selector)
|
45
89
|
case selector
|
46
90
|
when Hash
|
@@ -3,627 +3,82 @@ require_relative 'unit_helper'
|
|
3
3
|
describe Watir::Locators::Element::Locator do
|
4
4
|
include LocatorSpecHelper
|
5
5
|
|
6
|
-
describe '
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
expect_one(loc, 'bar').and_return(element(tag_name: 'div'))
|
11
|
-
match = %i[link link_text partial_link_text].include?(loc) ? :to : :to_not
|
12
|
-
msg = /:#{loc} locator is deprecated\. Use :visible_text instead/
|
13
|
-
expect { locate_one loc => 'bar' }.send(match, output(msg).to_stdout_from_any_process)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'raises exception if locating a non-link element by link locator' do
|
18
|
-
selector = {tag_name: 'div', link_text: 'foo'}
|
19
|
-
msg = 'Can not use link_text locator to find a foo element'
|
20
|
-
expect {
|
21
|
-
expect { locate_one(selector) }.to raise_exception(StandardError, msg)
|
22
|
-
}.to have_deprecated_link_text
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe 'with selectors not supported by Selenium' do
|
27
|
-
it 'handles selector with tag name and a single attribute' do
|
28
|
-
expect_one :xpath, ".//*[local-name()='div'][@title='foo']"
|
29
|
-
|
30
|
-
locate_one tag_name: 'div',
|
31
|
-
title: 'foo'
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'handles selector with no tag name and and a single attribute' do
|
35
|
-
expect_one :xpath, ".//*[@title='foo']"
|
36
|
-
|
37
|
-
locate_one title: 'foo'
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'handles single quotes in the attribute string' do
|
41
|
-
expect_one :xpath, %{.//*[@title=concat('foo and ',"'",'bar',"'",'')]}
|
42
|
-
|
43
|
-
locate_one title: "foo and 'bar'"
|
44
|
-
end
|
45
|
-
|
46
|
-
it 'handles selector with tag name and multiple attributes' do
|
47
|
-
expect_one :xpath, ".//*[local-name()='div'][@title='foo' and @dir='bar']"
|
48
|
-
|
49
|
-
locate_one [:tag_name, 'div',
|
50
|
-
:title, 'foo',
|
51
|
-
:dir, 'bar']
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'handles selector with no tag name and multiple attributes' do
|
55
|
-
expect_one :xpath, ".//*[@dir='foo' and @title='bar']"
|
56
|
-
|
57
|
-
locate_one [:dir, 'foo',
|
58
|
-
:title, 'bar']
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'handles selector with attribute presence' do
|
62
|
-
expect_one :xpath, './/*[@data-view]'
|
63
|
-
|
64
|
-
locate_one [:data_view, true]
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'handles selector with attribute absence' do
|
68
|
-
expect_one :xpath, './/*[not(@data-view)]'
|
69
|
-
|
70
|
-
locate_one [:data_view, false]
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'handles selector with class attribute presence' do
|
74
|
-
expect_one :xpath, './/*[@class]'
|
75
|
-
|
76
|
-
locate_one class: true
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'handles selector with multiple classes in array' do
|
80
|
-
xpath = ".//*[contains(concat(' ', @class, ' '), ' a ') and contains(concat(' ', @class, ' '), ' b ')]"
|
81
|
-
expect_one :xpath, xpath
|
82
|
-
|
83
|
-
locate_one class: %w[a b]
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'handles selector with multiple classes in string' do
|
87
|
-
expect_one :xpath, ".//*[contains(concat(' ', @class, ' '), ' a b ')]"
|
88
|
-
|
89
|
-
expect { locate_one class: 'a b' }.to have_deprecated_class_array
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'handles selector with xpath and tag_name String' do
|
93
|
-
elements = [
|
94
|
-
element(tag_name: 'div', attributes: {class: 'foo'}),
|
95
|
-
element(tag_name: 'span', attributes: {class: 'foo'}),
|
96
|
-
element(tag_name: 'div', attributes: {class: 'foo'})
|
97
|
-
]
|
98
|
-
|
99
|
-
expect_all(:xpath, './/*[@class="foo"]').and_return(elements)
|
100
|
-
|
101
|
-
selector = {
|
102
|
-
xpath: './/*[@class="foo"]',
|
103
|
-
tag_name: 'span'
|
104
|
-
}
|
105
|
-
|
106
|
-
expect(locate_one(selector).tag_name).to eq 'span'
|
107
|
-
end
|
108
|
-
|
109
|
-
it 'handles selector with xpath and tag_name Symbol' do
|
110
|
-
elements = [
|
111
|
-
element(tag_name: 'div', attributes: {class: 'foo'}),
|
112
|
-
element(tag_name: 'span', attributes: {class: 'foo'}),
|
113
|
-
element(tag_name: 'div', attributes: {class: 'foo'})
|
114
|
-
]
|
115
|
-
|
116
|
-
expect_all(:xpath, './/*[@class="foo"]').and_return(elements)
|
117
|
-
|
118
|
-
selector = {
|
119
|
-
xpath: './/*[@class="foo"]',
|
120
|
-
tag_name: 'span'
|
121
|
-
}
|
122
|
-
|
123
|
-
expect(locate_one(selector).tag_name).to eq 'span'
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'handles custom attributes' do
|
127
|
-
elements = [
|
128
|
-
element(tag_name: 'div', attributes: {custom_attribute: 'foo'}),
|
129
|
-
element(tag_name: 'span', attributes: {custom_attribute: 'foo'}),
|
130
|
-
element(tag_name: 'div', attributes: {custom_attribute: 'foo'})
|
131
|
-
]
|
132
|
-
|
133
|
-
expect_one(:xpath, ".//*[local-name()='span'][@custom-attribute='foo']").and_return(elements[1])
|
134
|
-
|
135
|
-
selector = {
|
136
|
-
custom_attribute: 'foo',
|
137
|
-
tag_name: 'span'
|
138
|
-
}
|
139
|
-
|
140
|
-
expect(locate_one(selector).tag_name).to eq 'span'
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
describe 'with special cased selectors' do
|
145
|
-
it 'normalizes space for :text' do
|
146
|
-
expect_one :xpath, ".//*[local-name()='div'][normalize-space()='foo']"
|
147
|
-
locate_one tag_name: 'div',
|
148
|
-
text: 'foo'
|
149
|
-
end
|
150
|
-
|
151
|
-
# TODO: This is deprecated by 'text_string'
|
152
|
-
it "handles 'text' key when it's a string" do
|
153
|
-
expect_one :xpath, ".//*[local-name()='div'][normalize-space()='foo']"
|
154
|
-
locate_one tag_name: 'div',
|
155
|
-
'text' => 'foo'
|
156
|
-
end
|
157
|
-
|
158
|
-
it 'translates :caption to :text' do
|
159
|
-
expect_one :xpath, ".//*[local-name()='div'][normalize-space()='foo']"
|
160
|
-
|
161
|
-
locate_one tag_name: 'div',
|
162
|
-
caption: 'foo'
|
163
|
-
end
|
164
|
-
|
165
|
-
it 'handles data-* attributes' do
|
166
|
-
expect_one :xpath, ".//*[local-name()='div'][@data-name='foo']"
|
167
|
-
|
168
|
-
locate_one tag_name: 'div',
|
169
|
-
data_name: 'foo'
|
170
|
-
end
|
171
|
-
|
172
|
-
it 'handles aria-* attributes' do
|
173
|
-
expect_one :xpath, ".//*[local-name()='div'][@aria-label='foo']"
|
174
|
-
|
175
|
-
locate_one tag_name: 'div',
|
176
|
-
aria_label: 'foo'
|
177
|
-
end
|
178
|
-
|
179
|
-
it "doesn't modify attribute name when the attribute key is a string" do
|
180
|
-
expect_one :xpath, ".//*[local-name()='div'][@_ngcontent-c24]"
|
181
|
-
|
182
|
-
locate_one tag_name: 'div',
|
183
|
-
'_ngcontent-c24' => true
|
184
|
-
end
|
185
|
-
|
186
|
-
it 'normalizes space for the :href attribute' do
|
187
|
-
expect_one :xpath, ".//*[local-name()='a'][normalize-space(@href)='foo']"
|
188
|
-
|
189
|
-
selector = {
|
190
|
-
tag_name: 'a',
|
191
|
-
href: 'foo'
|
192
|
-
}
|
193
|
-
|
194
|
-
locate_one selector, Watir::Anchor.attributes
|
195
|
-
end
|
196
|
-
|
197
|
-
it 'wraps :type attribute with translate() for upper case values' do
|
198
|
-
translated_type = "translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
|
199
|
-
"'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ')"
|
200
|
-
expect_one :xpath, ".//*[local-name()='input'][#{translated_type}='file']"
|
201
|
-
|
202
|
-
selector = [
|
203
|
-
:tag_name, 'input',
|
204
|
-
:type, 'file'
|
205
|
-
]
|
206
|
-
|
207
|
-
locate_one selector, Watir::Input.attributes
|
208
|
-
end
|
209
|
-
|
210
|
-
it "uses the corresponding <label>'s @for attribute or parent::label when locating by label" do
|
211
|
-
translated_type = "translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
|
212
|
-
"'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ')"
|
213
|
-
xpath = ".//*[local-name()='input'][@id=//label[normalize-space()='foo']/@for " \
|
214
|
-
"or parent::label[normalize-space()='foo']][#{translated_type}='text']"
|
215
|
-
expect_one :xpath, xpath
|
216
|
-
|
217
|
-
selector = [
|
218
|
-
:tag_name, 'input',
|
219
|
-
:type, 'text',
|
220
|
-
:label, 'foo'
|
221
|
-
]
|
222
|
-
|
223
|
-
locate_one selector, Watir::Input.attributes
|
224
|
-
end
|
225
|
-
|
226
|
-
it 'uses label attribute if it is valid for element' do
|
227
|
-
expect_one :xpath, ".//*[local-name()='option'][@label='foo']"
|
228
|
-
|
229
|
-
selector = {tag_name: 'option', label: 'foo'}
|
230
|
-
locate_one selector, Watir::Option.attributes
|
231
|
-
end
|
232
|
-
|
233
|
-
it 'translates ruby attribute names to content attribute names' do
|
234
|
-
expect_one :xpath, ".//*[local-name()='meta'][@http-equiv='foo']"
|
235
|
-
|
236
|
-
selector = {
|
237
|
-
tag_name: 'meta',
|
238
|
-
http_equiv: 'foo'
|
239
|
-
}
|
240
|
-
|
241
|
-
locate_one selector, Watir::Meta.attributes
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
describe 'with simple regexp selectors' do
|
246
|
-
it 'handles selector with tag name and a simple regexp attribute' do
|
247
|
-
element = element(tag_name: 'div', attributes: {class: 'foob'})
|
248
|
-
|
249
|
-
expect_one(:xpath, ".//*[local-name()='div'][contains(@class, 'oob')]").and_return(element)
|
250
|
-
|
251
|
-
expect(locate_one(tag_name: 'div', class: /oob/)).to eq element
|
252
|
-
end
|
253
|
-
|
254
|
-
it 'handles :tag_name, :index and a simple regexp attribute' do
|
255
|
-
element = element(tag_name: 'div', attributes: {class: 'foo'})
|
256
|
-
|
257
|
-
expect_one(:xpath, "(.//*[local-name()='div'][contains(@class, 'foo')])[2]").and_return(element)
|
258
|
-
|
259
|
-
selector = {
|
260
|
-
tag_name: 'div',
|
261
|
-
class: /foo/,
|
262
|
-
index: 1
|
263
|
-
}
|
264
|
-
|
265
|
-
expect(locate_one(selector)).to eq element
|
266
|
-
end
|
267
|
-
|
268
|
-
it 'handles :xpath and :index selectors' do
|
269
|
-
elements = [
|
270
|
-
element(tag_name: 'div', attributes: {class: 'foo'}),
|
271
|
-
element(tag_name: 'div', attributes: {class: 'foo'})
|
272
|
-
]
|
273
|
-
|
274
|
-
expect_all(:xpath, './/div[@class="foo"]').and_return(elements)
|
275
|
-
|
276
|
-
selector = {
|
277
|
-
xpath: './/div[@class="foo"]',
|
278
|
-
index: 1
|
279
|
-
}
|
280
|
-
|
281
|
-
expect(locate_one(selector)).to eq elements[1]
|
282
|
-
end
|
283
|
-
|
284
|
-
it 'handles :css and :index selectors' do
|
285
|
-
elements = [
|
286
|
-
element(tag_name: 'div', attributes: {class: 'foo'}),
|
287
|
-
element(tag_name: 'div', attributes: {class: 'foo'})
|
288
|
-
]
|
289
|
-
|
290
|
-
expect_all(:css, 'div[class="foo"]').and_return(elements)
|
291
|
-
|
292
|
-
selector = {
|
293
|
-
css: 'div[class="foo"]',
|
294
|
-
index: 1
|
295
|
-
}
|
296
|
-
|
297
|
-
expect(locate_one(selector)).to eq elements[1]
|
298
|
-
end
|
299
|
-
|
300
|
-
it 'handles mix of string and regexp attributes' do
|
301
|
-
element = element(tag_name: 'div', attributes: {dir: 'foo', title: 'baz'})
|
6
|
+
describe '#locate' do
|
7
|
+
context 'when XPath can be built to represent entire selector' do
|
8
|
+
it 'locates without using match' do
|
9
|
+
@locator = {xpath: './/div'}
|
302
10
|
|
303
|
-
expect_one(
|
11
|
+
expect_one(*@locator.to_a.flatten).and_return(el)
|
12
|
+
expect(element_matcher).not_to receive(:match)
|
304
13
|
|
305
|
-
|
306
|
-
tag_name: 'div',
|
307
|
-
dir: 'foo',
|
308
|
-
title: /baz/
|
309
|
-
}
|
310
|
-
|
311
|
-
expect(locate_one(selector)).to eq element
|
312
|
-
end
|
313
|
-
|
314
|
-
it 'handles data-* attributes with regexp' do
|
315
|
-
element = element(tag_name: 'div', attributes: {'data-automation-id': 'bar'})
|
316
|
-
|
317
|
-
expect_one(:xpath, ".//*[local-name()='div'][contains(@data-automation-id, 'bar')]").and_return(element)
|
318
|
-
|
319
|
-
selector = {
|
320
|
-
tag_name: 'div',
|
321
|
-
data_automation_id: /bar/
|
322
|
-
}
|
323
|
-
|
324
|
-
expect(locate_one(selector)).to eq element
|
14
|
+
expect(locate_one).to eq el
|
325
15
|
end
|
326
16
|
|
327
|
-
|
328
|
-
|
329
|
-
label_elements = [
|
330
|
-
element(tag_name: 'label', text: 'foo', attributes: {'for' => 'bar'}),
|
331
|
-
element(tag_name: 'label', text: 'foob', attributes: {'for' => 'baz'})
|
332
|
-
]
|
333
|
-
div_elements = [element(tag_name: 'div')]
|
17
|
+
it 'returns nil if not found' do
|
18
|
+
@locator = {xpath: './/div'}
|
334
19
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
allow(browser).to receive(:ensure_context).and_return(nil)
|
339
|
-
allow(browser).to receive(:execute_script).and_return('foo', 'foob')
|
340
|
-
|
341
|
-
expect(locate_one(tag_name: 'div', label: /oob/)).to eq div_elements.first
|
342
|
-
end
|
343
|
-
|
344
|
-
# TODO: I can not figure out how to mock this out properly with the new implementation
|
345
|
-
xit 'returns nil when no label matching the regexp is found' do
|
346
|
-
expect_all(:tag_name, 'label').and_return([])
|
347
|
-
expect(locate_one(tag_name: 'div', label: /foo/)).to be_nil
|
348
|
-
end
|
349
|
-
|
350
|
-
it 'relocates an element that goes stale during filtering' do
|
351
|
-
element1 = element(tag_name: 'div', attributes: {class: 'foo'})
|
352
|
-
element2 = element(tag_name: 'div', attributes: {class: 'foob'})
|
353
|
-
|
354
|
-
elements1 = [element1.clone, element2.clone]
|
355
|
-
elements2 = [element1.clone, element2.clone]
|
356
|
-
|
357
|
-
allow(elements1.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
|
358
|
-
|
359
|
-
expect_all(:xpath, ".//*[contains(@class, 'foo')]").and_return(elements1, elements2)
|
360
|
-
|
361
|
-
expect(locate_one(class: /foo$/)).to eq elements2[0]
|
362
|
-
end
|
363
|
-
|
364
|
-
it 'raises error if too many attempts to relocate a stale element during filtering' do
|
365
|
-
element1 = element(tag_name: 'div', attributes: {class: 'foo'})
|
366
|
-
element2 = element(tag_name: 'div', attributes: {class: 'foob'})
|
367
|
-
|
368
|
-
elements1 = [element1.clone, element2.clone]
|
369
|
-
elements2 = [element1.clone, element2.clone]
|
370
|
-
elements3 = [element1.clone, element2.clone]
|
371
|
-
|
372
|
-
allow(elements1.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
|
373
|
-
allow(elements2.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
|
374
|
-
allow(elements3.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
|
375
|
-
|
376
|
-
expect_all(:xpath, ".//*[contains(@class, 'foo')]").and_return(elements1, elements2, elements3)
|
377
|
-
|
378
|
-
msg = 'Unable to locate element from {:class=>/foo$/} due to changing page'
|
379
|
-
expect { locate_one(class: /foo$/) }.to raise_exception(Watir::Exception::LocatorException, msg)
|
20
|
+
expect_one(*@locator.to_a.flatten).and_raise(Selenium::WebDriver::Error::NoSuchElementError)
|
21
|
+
expect(locate_one).to eq nil
|
380
22
|
end
|
381
23
|
end
|
382
24
|
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
selector = {
|
387
|
-
tag_name: 'input',
|
388
|
-
xpath: '//div'
|
389
|
-
}
|
25
|
+
context 'when SelectorBuilder result has additional locators to match' do
|
26
|
+
it 'locates using match' do
|
27
|
+
@locator = {xpath: './/div', id: 'foo'}
|
390
28
|
|
391
|
-
|
392
|
-
|
29
|
+
expect_all(*@locator.to_a.first.flatten).and_return([el])
|
30
|
+
expect(element_matcher).to receive(:match).and_return(el)
|
393
31
|
|
394
|
-
|
395
|
-
expect_all(:xpath, '//div').and_return([element(tag_name: 'div')])
|
396
|
-
|
397
|
-
selector = {
|
398
|
-
tag_name: :input,
|
399
|
-
xpath: '//div'
|
400
|
-
}
|
401
|
-
|
402
|
-
expect(locate_one(selector, Watir::Input.attributes)).to be_nil
|
403
|
-
end
|
404
|
-
|
405
|
-
describe 'errors' do
|
406
|
-
it 'raises a TypeError if :index is not a Integer' do
|
407
|
-
msg = /expected one of \[(Integer|Fixnum)\], got "bar":String/
|
408
|
-
expect { locate_one(tag_name: 'div', index: 'bar') }.to raise_error TypeError, msg
|
32
|
+
expect(locate_one).to eq el
|
409
33
|
end
|
410
34
|
|
411
|
-
it '
|
412
|
-
|
413
|
-
expect { locate_one(foo: 123) }.to raise_error TypeError, msg
|
414
|
-
end
|
35
|
+
it 'relocates if element goes stale' do
|
36
|
+
@locator = {xpath: './/div', id: 'foo'}
|
415
37
|
|
416
|
-
|
417
|
-
|
418
|
-
expect
|
419
|
-
|
38
|
+
expect_all(*@locator.to_a.first.flatten).exactly(2).times.and_return([el])
|
39
|
+
stale_exception = Selenium::WebDriver::Error::StaleElementReferenceError
|
40
|
+
expect(element_matcher).to receive(:match).and_raise(stale_exception)
|
41
|
+
expect(element_matcher).to receive(:match).and_return(el)
|
420
42
|
|
421
|
-
|
422
|
-
msg = /Unable to build XPath using 7:(Integer|Fixnum)/
|
423
|
-
expect { locate_one(7 => 'bad') }.to raise_exception(Watir::Exception::Error, msg)
|
43
|
+
expect(locate_one).to eq el
|
424
44
|
end
|
425
45
|
|
426
|
-
it '
|
427
|
-
|
428
|
-
class SelectorBuilder < Watir::Locators::Element::SelectorBuilder
|
429
|
-
def build(*_args)
|
430
|
-
nil
|
431
|
-
end
|
432
|
-
end
|
433
|
-
end
|
46
|
+
it 'Raises Exception if element continues to go stale' do
|
47
|
+
@locator = {xpath: './/div', id: 'foo'}
|
434
48
|
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
locator = Watir::Locators::Element::Locator.new(browser, selector, selector_builder, element_validator)
|
49
|
+
expect_all(*@locator.to_a.first.flatten).exactly(3).times.and_return([el])
|
50
|
+
stale_exception = Selenium::WebDriver::Error::StaleElementReferenceError
|
51
|
+
expect(element_matcher).to receive(:match).and_raise(stale_exception).exactly(3).times
|
439
52
|
|
440
|
-
msg = '
|
441
|
-
expect {
|
442
|
-
end
|
443
|
-
|
444
|
-
it 'raises an Error if unable to build values to match' do
|
445
|
-
module Foo
|
446
|
-
class SelectorBuilder < Watir::Locators::Element::SelectorBuilder
|
447
|
-
def build(*_args)
|
448
|
-
{}
|
449
|
-
end
|
450
|
-
end
|
451
|
-
end
|
452
|
-
|
453
|
-
selector = {name: 'foo'}
|
454
|
-
element_validator = Watir::Locators::Element::Validator.new
|
455
|
-
selector_builder = Foo::SelectorBuilder.new(Watir::HTMLElement.attributes)
|
456
|
-
locator = Watir::Locators::Element::Locator.new(browser, selector, selector_builder, element_validator)
|
457
|
-
|
458
|
-
msg = 'Foo::SelectorBuilder was unable to build selector from {:name=>"foo"}'
|
459
|
-
expect { locator.locate }.to raise_exception(Watir::Exception::LocatorException, msg)
|
53
|
+
msg = 'Unable to locate element from {:xpath=>".//div", :id=>"foo"} due to changing page'
|
54
|
+
expect { locate_one }.to raise_exception Watir::Exception::LocatorException, msg
|
460
55
|
end
|
461
56
|
end
|
462
57
|
end
|
463
58
|
|
464
|
-
describe '
|
465
|
-
|
466
|
-
|
467
|
-
it "delegates to Selenium's #{loc} locator" do
|
468
|
-
expect_all(loc, 'bar').and_return([element(tag_name: 'div')])
|
469
|
-
match = %i[link link_text partial_link_text].include?(loc) ? :to : :to_not
|
470
|
-
msg = /:#{loc} locator is deprecated\. Use :visible_text instead/
|
471
|
-
expect { locate_all loc => 'bar' }.send(match, output(msg).to_stdout_from_any_process)
|
472
|
-
end
|
473
|
-
end
|
474
|
-
end
|
475
|
-
|
476
|
-
describe 'with an empty selector' do
|
477
|
-
it 'finds all when an empty selctor is given' do
|
478
|
-
expect_all :xpath, './/*'
|
479
|
-
locate_all({})
|
480
|
-
end
|
481
|
-
end
|
482
|
-
|
483
|
-
describe 'with selectors not supported by Selenium' do
|
484
|
-
it 'handles selector with tag name and a single attribute' do
|
485
|
-
expect_all :xpath, ".//*[local-name()='div'][@dir='foo']"
|
486
|
-
locate_all tag_name: 'div',
|
487
|
-
dir: 'foo'
|
488
|
-
end
|
489
|
-
|
490
|
-
it 'handles selector with tag name and multiple attributes' do
|
491
|
-
expect_all :xpath, ".//*[local-name()='div'][@dir='foo' and @title='bar']"
|
492
|
-
locate_all [:tag_name, 'div',
|
493
|
-
:dir, 'foo',
|
494
|
-
:title, 'bar']
|
495
|
-
end
|
496
|
-
|
497
|
-
it 'handles selector with class attribute presence' do
|
498
|
-
expect_all :xpath, './/*[@class]'
|
499
|
-
|
500
|
-
locate_all class: true
|
501
|
-
end
|
502
|
-
|
503
|
-
it 'handles selector with multiple classes in array' do
|
504
|
-
xpath = ".//*[contains(concat(' ', @class, ' '), ' a ') and contains(concat(' ', @class, ' '), ' b ')]"
|
505
|
-
expect_all :xpath, xpath
|
59
|
+
describe '#locate_all' do
|
60
|
+
it 'locates using match' do
|
61
|
+
@locator = {xpath: './/div', id: 'foo'}
|
506
62
|
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
it 'handles selector with multiple classes in string' do
|
511
|
-
expect_all :xpath, ".//*[contains(concat(' ', @class, ' '), ' a b ')]"
|
63
|
+
expect_all(*@locator.to_a.first.flatten).and_return([el])
|
64
|
+
expect(element_matcher).to receive(:match).and_return([el])
|
512
65
|
|
513
|
-
|
514
|
-
end
|
66
|
+
expect(locate_all).to eq [el]
|
515
67
|
end
|
516
68
|
|
517
|
-
|
518
|
-
|
519
|
-
elements = [
|
520
|
-
element(tag_name: 'div', attributes: {class: 'foob'}),
|
521
|
-
element(tag_name: 'div', attributes: {class: 'doob'}),
|
522
|
-
element(tag_name: 'div', attributes: {class: 'noob'})
|
523
|
-
]
|
524
|
-
|
525
|
-
expect_all(:xpath, ".//*[local-name()='div'][contains(@class, 'oob')]").and_return(elements)
|
526
|
-
expect(locate_all(tag_name: 'div', class: /oob/)).to eq elements.last(3)
|
527
|
-
end
|
528
|
-
|
529
|
-
it 'handles mix of string and regexp attributes' do
|
530
|
-
elements = [
|
531
|
-
element(tag_name: 'div', attributes: {dir: 'foo', title: 'baz'}),
|
532
|
-
element(tag_name: 'div', attributes: {dir: 'foo', title: 'bazt'})
|
533
|
-
]
|
69
|
+
it 'raises LocatorException if element continues to go stale' do
|
70
|
+
@locator = {xpath: './/div', id: 'foo'}
|
534
71
|
|
535
|
-
|
72
|
+
expect_all(*@locator.to_a.first.flatten).exactly(3).times.and_return([el])
|
73
|
+
stale_exception = Selenium::WebDriver::Error::StaleElementReferenceError
|
74
|
+
expect(element_matcher).to receive(:match).and_raise(stale_exception).exactly(3).times
|
536
75
|
|
537
|
-
|
538
|
-
|
539
|
-
dir: 'foo',
|
540
|
-
title: /baz/
|
541
|
-
}
|
542
|
-
|
543
|
-
expect(locate_all(selector)).to eq elements.last(2)
|
544
|
-
end
|
76
|
+
msg = 'Unable to locate element collection from {:xpath=>".//div", :id=>"foo"} due to changing page'
|
77
|
+
expect { locate_all }.to raise_exception Watir::Exception::LocatorException, msg
|
545
78
|
end
|
546
79
|
|
547
|
-
it '
|
548
|
-
|
549
|
-
|
550
|
-
expect_one(:xpath, "(.//*[local-name()='div'][@dir='foo'])[2]").and_return(element)
|
551
|
-
|
552
|
-
selector = {
|
553
|
-
tag_name: 'div',
|
554
|
-
dir: 'foo',
|
555
|
-
index: 1
|
556
|
-
}
|
557
|
-
|
558
|
-
expect(locate_one(selector)).to eq element
|
559
|
-
end
|
560
|
-
|
561
|
-
context 'and xpath' do
|
562
|
-
it 'converts a leading run of regexp literals to a contains() expression' do
|
563
|
-
elements = [
|
564
|
-
element(tag_name: 'div', attributes: {foo: 'foo'}),
|
565
|
-
element(tag_name: 'div', attributes: {foo: 'foob'}),
|
566
|
-
element(tag_name: 'div', attributes: {foo: 'bar'})
|
567
|
-
]
|
568
|
-
|
569
|
-
expect_all(:xpath, ".//*[local-name()='div'][contains(@foo, 'fo') and contains(@foo, 'b')]")
|
570
|
-
.and_return(elements.first(2))
|
571
|
-
|
572
|
-
expect(locate_one(tag_name: 'div', foo: /fo.b$/)).to eq elements[1]
|
573
|
-
end
|
574
|
-
|
575
|
-
it 'converts a trailing run of regexp literals to a contains() expression' do
|
576
|
-
elements = [
|
577
|
-
element(tag_name: 'div', attributes: {foo: 'foo'}),
|
578
|
-
element(tag_name: 'div', attributes: {foo: 'foob'})
|
579
|
-
]
|
580
|
-
|
581
|
-
expect_all(:xpath, ".//*[local-name()='div'][contains(@foo, 'fo') and contains(@foo, 'b')]")
|
582
|
-
.and_return(elements.last(1))
|
583
|
-
|
584
|
-
expect(locate_one(tag_name: 'div', foo: /^fo.b/)).to eq elements[1]
|
585
|
-
end
|
586
|
-
|
587
|
-
it 'converts a leading and a trailing run of regexp literals to a contains() expression' do
|
588
|
-
elements = [
|
589
|
-
element(tag_name: 'div', attributes: {foo: 'foo'}),
|
590
|
-
element(tag_name: 'div', attributes: {foo: 'foob'})
|
591
|
-
]
|
592
|
-
|
593
|
-
expect_all(:xpath, ".//*[local-name()='div'][contains(@foo, 'fo') and contains(@foo, 'b')]")
|
594
|
-
.and_return(elements.last(1))
|
595
|
-
|
596
|
-
expect(locate_one(tag_name: 'div', foo: /fo.b/)).to eq elements[1]
|
597
|
-
end
|
598
|
-
|
599
|
-
it 'does not try to convert case insensitive expressions' do
|
600
|
-
element = element(tag_name: 'div', attributes: {foo: 'foo'})
|
601
|
-
|
602
|
-
xpath = ".//*[local-name()='div'][contains(translate" \
|
603
|
-
"(@foo,'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
|
604
|
-
"'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'), 'foob')]"
|
605
|
-
expect_one(:xpath, xpath).and_return(element)
|
606
|
-
|
607
|
-
expect(locate_one(tag_name: 'div', foo: /FOOB/i)).to eq element
|
608
|
-
end
|
609
|
-
|
610
|
-
it "does not try to convert expressions containing '|'" do
|
611
|
-
elements = [
|
612
|
-
element(tag_name: 'div', attributes: {foo: 'foo'}),
|
613
|
-
element(tag_name: 'div', attributes: {foo: 'foob'})
|
614
|
-
]
|
615
|
-
|
616
|
-
expect_all(:xpath, ".//*[local-name()='div'][@foo]").and_return(elements.last(1))
|
617
|
-
|
618
|
-
expect(locate_one(tag_name: 'div', foo: /x|b/)).to eq elements[1]
|
619
|
-
end
|
620
|
-
end
|
621
|
-
|
622
|
-
describe 'errors' do
|
623
|
-
it 'raises ArgumentError if :index is given' do
|
624
|
-
expect { locate_all(tag_name: 'div', index: 1) }.to \
|
625
|
-
raise_error(ArgumentError, "can't locate all elements by :index")
|
626
|
-
end
|
80
|
+
it 'raises Argument error if using index key' do
|
81
|
+
expect { locate_all(index: 2) }.to raise_exception(ArgumentError, "can't locate all elements by :index")
|
627
82
|
end
|
628
83
|
end
|
629
84
|
end
|