cuprite 0.12 → 0.15.1
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/LICENSE +1 -1
- data/README.md +14 -12
- data/lib/capybara/cuprite/browser.rb +196 -155
- data/lib/capybara/cuprite/cookie.rb +32 -32
- data/lib/capybara/cuprite/driver.rb +335 -363
- data/lib/capybara/cuprite/errors.rb +9 -8
- data/lib/capybara/cuprite/javascripts/index.js +47 -27
- data/lib/capybara/cuprite/node.rb +218 -224
- data/lib/capybara/cuprite/options.rb +14 -0
- data/lib/capybara/cuprite/page.rb +148 -154
- data/lib/capybara/cuprite/version.rb +1 -1
- data/lib/capybara/cuprite.rb +3 -0
- metadata +19 -145
@@ -2,294 +2,288 @@
|
|
2
2
|
|
3
3
|
require "forwardable"
|
4
4
|
|
5
|
-
module Capybara
|
6
|
-
|
7
|
-
|
5
|
+
module Capybara
|
6
|
+
module Cuprite
|
7
|
+
class Node < Capybara::Driver::Node
|
8
|
+
attr_reader :node
|
8
9
|
|
9
|
-
|
10
|
+
extend Forwardable
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
delegate %i[description] => :node
|
13
|
+
delegate %i[browser] => :driver
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def initialize(driver, node)
|
16
|
+
super(driver, self)
|
17
|
+
@node = node
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
def command(name, *args)
|
21
|
+
browser.send(name, node, *args)
|
22
|
+
rescue Ferrum::NodeNotFoundError => e
|
23
|
+
raise ObsoleteNode.new(self, e.response)
|
24
|
+
rescue Ferrum::BrowserError => e
|
25
|
+
case e.message
|
26
|
+
when "Cuprite.MouseEventFailed"
|
27
|
+
raise MouseEventFailed.new(self, e.response)
|
28
|
+
else
|
29
|
+
raise
|
30
|
+
end
|
29
31
|
end
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def parents
|
34
|
+
command(:parents).map do |parent|
|
35
|
+
self.class.new(driver, parent)
|
36
|
+
end
|
35
37
|
end
|
36
|
-
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
def find_xpath(selector)
|
40
|
+
find(:xpath, selector)
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
def find_css(selector)
|
44
|
+
find(:css, selector)
|
45
|
+
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
def find(method, selector)
|
48
|
+
command(:find_within, method, selector).map do |node|
|
49
|
+
self.class.new(driver, node)
|
50
|
+
end
|
49
51
|
end
|
50
|
-
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
53
|
+
def all_text
|
54
|
+
filter_text(command(:all_text))
|
55
|
+
end
|
55
56
|
|
56
|
-
|
57
|
-
if Capybara::VERSION.to_f < 3.0
|
58
|
-
filter_text(command(:visible_text))
|
59
|
-
else
|
57
|
+
def visible_text
|
60
58
|
command(:visible_text).to_s
|
61
59
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
62
60
|
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
63
61
|
.gsub(/\n+/, "\n")
|
64
62
|
.tr("\u00a0", " ")
|
65
63
|
end
|
66
|
-
end
|
67
64
|
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
def [](name)
|
73
|
-
# Although the attribute matters, the property is consistent. Return that in
|
74
|
-
# preference to the attribute for links and images.
|
75
|
-
if (tag_name == "img" && name == "src") ||
|
76
|
-
(tag_name == "a" && name == "href")
|
77
|
-
# if attribute exists get the property
|
78
|
-
return command(:attribute, name) && command(:property, name)
|
65
|
+
def property(name)
|
66
|
+
command(:property, name)
|
79
67
|
end
|
80
68
|
|
81
|
-
|
82
|
-
|
69
|
+
def [](name)
|
70
|
+
# Although the attribute matters, the property is consistent. Return that in
|
71
|
+
# preference to the attribute for links and images.
|
72
|
+
if (tag_name == "img" && name == "src") ||
|
73
|
+
(tag_name == "a" && name == "href")
|
74
|
+
# if attribute exists get the property
|
75
|
+
return command(:attribute, name) && command(:property, name)
|
76
|
+
end
|
83
77
|
|
84
|
-
|
85
|
-
|
78
|
+
value = property(name)
|
79
|
+
value = command(:attribute, name) if value.nil? || value.is_a?(Hash)
|
86
80
|
|
87
|
-
|
88
|
-
|
89
|
-
end
|
81
|
+
value
|
82
|
+
end
|
90
83
|
|
91
|
-
|
92
|
-
|
93
|
-
|
84
|
+
def attributes
|
85
|
+
command(:attributes)
|
86
|
+
end
|
94
87
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
88
|
+
def value
|
89
|
+
command(:value)
|
90
|
+
end
|
91
|
+
|
92
|
+
def set(value, options = {})
|
93
|
+
warn "Options passed to Node#set but Cuprite doesn't currently support any - ignoring" unless options.empty?
|
94
|
+
|
95
|
+
if tag_name == "input"
|
96
|
+
case self[:type]
|
97
|
+
when "radio"
|
98
|
+
click
|
99
|
+
when "checkbox"
|
100
|
+
click if value != checked?
|
101
|
+
when "file"
|
102
|
+
files = value.respond_to?(:to_ary) ? value.to_ary.map(&:to_s) : value.to_s
|
103
|
+
command(:select_file, files)
|
104
|
+
when "color"
|
105
|
+
node.evaluate("this.setAttribute('value', '#{value}')")
|
106
|
+
else
|
107
|
+
command(:set, value.to_s)
|
108
|
+
end
|
109
|
+
elsif tag_name == "textarea"
|
110
110
|
command(:set, value.to_s)
|
111
|
+
elsif self[:isContentEditable]
|
112
|
+
command(:delete_text)
|
113
|
+
send_keys(value.to_s)
|
111
114
|
end
|
112
|
-
elsif tag_name == "textarea"
|
113
|
-
command(:set, value.to_s)
|
114
|
-
elsif self[:isContentEditable]
|
115
|
-
command(:delete_text)
|
116
|
-
send_keys(value.to_s)
|
117
115
|
end
|
118
|
-
end
|
119
116
|
|
120
|
-
|
121
|
-
|
122
|
-
|
117
|
+
def select_option
|
118
|
+
command(:select, true)
|
119
|
+
end
|
123
120
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
121
|
+
def unselect_option
|
122
|
+
command(:select, false) ||
|
123
|
+
raise(Capybara::UnselectNotAllowed, "Cannot unselect option from single select box.")
|
124
|
+
end
|
128
125
|
|
129
|
-
|
130
|
-
|
131
|
-
|
126
|
+
def tag_name
|
127
|
+
@tag_name ||= description["nodeName"].downcase
|
128
|
+
end
|
132
129
|
|
133
|
-
|
134
|
-
|
135
|
-
|
130
|
+
def visible?
|
131
|
+
command(:visible?)
|
132
|
+
end
|
136
133
|
|
137
|
-
|
138
|
-
|
139
|
-
|
134
|
+
def checked?
|
135
|
+
self[:checked]
|
136
|
+
end
|
140
137
|
|
141
|
-
|
142
|
-
|
143
|
-
|
138
|
+
def selected?
|
139
|
+
!!self[:selected]
|
140
|
+
end
|
144
141
|
|
145
|
-
|
146
|
-
|
147
|
-
|
142
|
+
def disabled?
|
143
|
+
command(:disabled?)
|
144
|
+
end
|
148
145
|
|
149
|
-
|
150
|
-
|
151
|
-
|
146
|
+
def click(keys = [], **options)
|
147
|
+
prepare_and_click(:left, __method__, keys, options)
|
148
|
+
end
|
152
149
|
|
153
|
-
|
154
|
-
|
155
|
-
|
150
|
+
def right_click(keys = [], **options)
|
151
|
+
prepare_and_click(:right, __method__, keys, options)
|
152
|
+
end
|
156
153
|
|
157
|
-
|
158
|
-
|
159
|
-
|
154
|
+
def double_click(keys = [], **options)
|
155
|
+
prepare_and_click(:double, __method__, keys, options)
|
156
|
+
end
|
160
157
|
|
161
|
-
|
162
|
-
|
163
|
-
|
158
|
+
def hover
|
159
|
+
command(:hover)
|
160
|
+
end
|
164
161
|
|
165
|
-
|
166
|
-
|
167
|
-
end
|
162
|
+
def drag_to(other, **options)
|
163
|
+
options[:steps] ||= 1
|
168
164
|
|
169
|
-
|
170
|
-
|
171
|
-
end
|
165
|
+
command(:drag, other.node, options[:steps], options[:delay])
|
166
|
+
end
|
172
167
|
|
173
|
-
|
174
|
-
|
175
|
-
end
|
168
|
+
def drag_by(x, y, **options)
|
169
|
+
options[:steps] ||= 1
|
176
170
|
|
177
|
-
|
178
|
-
if element.is_a?(Node)
|
179
|
-
scroll_element_to_location(element, location)
|
180
|
-
elsif location.is_a?(Symbol)
|
181
|
-
scroll_to_location(location)
|
182
|
-
else
|
183
|
-
scroll_to_coords(*position)
|
171
|
+
command(:drag_by, x, y, options[:steps], options[:delay])
|
184
172
|
end
|
185
|
-
self
|
186
|
-
end
|
187
173
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
if (el.scrollBy){
|
192
|
-
el.scrollBy(arguments[1], arguments[2]);
|
193
|
-
} else {
|
194
|
-
el.scrollTop = el.scrollTop + arguments[2];
|
195
|
-
el.scrollLeft = el.scrollLeft + arguments[1];
|
196
|
-
}
|
197
|
-
JS
|
198
|
-
end
|
174
|
+
def trigger(event)
|
175
|
+
command(:trigger, event)
|
176
|
+
end
|
199
177
|
|
200
|
-
|
201
|
-
|
202
|
-
|
178
|
+
def scroll_to(element, location, position = nil)
|
179
|
+
if element.is_a?(Node)
|
180
|
+
scroll_element_to_location(element, location)
|
181
|
+
elsif location.is_a?(Symbol)
|
182
|
+
scroll_to_location(location)
|
183
|
+
else
|
184
|
+
scroll_to_coords(*position)
|
185
|
+
end
|
186
|
+
self
|
187
|
+
end
|
203
188
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
189
|
+
def scroll_by(x, y)
|
190
|
+
driver.execute_script <<~JS, self, x, y
|
191
|
+
var el = arguments[0];
|
192
|
+
if (el.scrollBy){
|
193
|
+
el.scrollBy(arguments[1], arguments[2]);
|
194
|
+
} else {
|
195
|
+
el.scrollTop = el.scrollTop + arguments[2];
|
196
|
+
el.scrollLeft = el.scrollLeft + arguments[1];
|
197
|
+
}
|
198
|
+
JS
|
199
|
+
end
|
208
200
|
|
209
|
-
|
210
|
-
|
211
|
-
|
201
|
+
def ==(other)
|
202
|
+
node == other.native.node
|
203
|
+
end
|
212
204
|
|
213
|
-
|
214
|
-
|
215
|
-
|
205
|
+
def send_keys(*keys)
|
206
|
+
command(:send_keys, keys)
|
207
|
+
end
|
208
|
+
alias send_key send_keys
|
216
209
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
end
|
210
|
+
def path
|
211
|
+
command(:path)
|
212
|
+
end
|
221
213
|
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
{ ELEMENT: { node: node, id: node.node_id } }
|
226
|
-
end
|
214
|
+
def inspect
|
215
|
+
%(#<#{self.class} @node=#{@node.inspect}>)
|
216
|
+
end
|
227
217
|
|
228
|
-
|
218
|
+
# @api private
|
219
|
+
def to_json(*)
|
220
|
+
JSON.generate(as_json)
|
221
|
+
end
|
229
222
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
223
|
+
# @api private
|
224
|
+
def as_json(*)
|
225
|
+
# FIXME: Where is this method used and why attr is called id?
|
226
|
+
{ ELEMENT: { node: node, id: node.node_id } }
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
237
230
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
231
|
+
def prepare_and_click(mode, name, keys, options)
|
232
|
+
delay = options[:delay].to_i
|
233
|
+
x, y = options.values_at(:x, :y)
|
234
|
+
offset = { x: x, y: y, position: options[:offset] || :top }
|
235
|
+
command(:before_click, name, keys, offset)
|
236
|
+
node.click(mode: mode, keys: keys, offset: offset, delay: delay)
|
237
|
+
end
|
238
|
+
|
239
|
+
def filter_text(text)
|
242
240
|
text.gsub(/[\u200b\u200e\u200f]/, "")
|
243
241
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, " ")
|
244
242
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
245
243
|
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
246
244
|
.tr("\u00a0", " ")
|
247
245
|
end
|
248
|
-
end
|
249
246
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
247
|
+
def scroll_element_to_location(element, location)
|
248
|
+
scroll_opts = case location
|
249
|
+
when :top
|
250
|
+
"true"
|
251
|
+
when :bottom
|
252
|
+
"false"
|
253
|
+
when :center
|
254
|
+
"{behavior: 'instant', block: 'center'}"
|
255
|
+
else
|
256
|
+
raise ArgumentError, "Invalid scroll_to location: #{location}"
|
257
|
+
end
|
258
|
+
driver.execute_script <<~JS, element
|
259
|
+
arguments[0].scrollIntoView(#{scroll_opts})
|
260
|
+
JS
|
261
|
+
end
|
265
262
|
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
arguments[0].scrollTop = #{scroll_y};
|
280
|
-
}
|
281
|
-
JS
|
282
|
-
end
|
263
|
+
def scroll_to_location(location)
|
264
|
+
height = { top: "0",
|
265
|
+
bottom: "arguments[0].scrollHeight",
|
266
|
+
center: "(arguments[0].scrollHeight - arguments[0].clientHeight)/2" }
|
267
|
+
|
268
|
+
driver.execute_script <<~JS, self
|
269
|
+
if (arguments[0].scrollTo){
|
270
|
+
arguments[0].scrollTo(0, #{height[location]});
|
271
|
+
} else {
|
272
|
+
arguments[0].scrollTop = #{height[location]};
|
273
|
+
}
|
274
|
+
JS
|
275
|
+
end
|
283
276
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
277
|
+
def scroll_to_coords(x, y)
|
278
|
+
driver.execute_script <<~JS, self, x, y
|
279
|
+
if (arguments[0].scrollTo){
|
280
|
+
arguments[0].scrollTo(arguments[1], arguments[2]);
|
281
|
+
} else {
|
282
|
+
arguments[0].scrollTop = arguments[2];
|
283
|
+
arguments[0].scrollLeft = arguments[1];
|
284
|
+
}
|
285
|
+
JS
|
286
|
+
end
|
293
287
|
end
|
294
288
|
end
|
295
289
|
end
|