capybara 3.8.2 → 3.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +10 -0
- data/lib/capybara.rb +32 -10
- data/lib/capybara/config.rb +1 -0
- data/lib/capybara/dsl.rb +2 -2
- data/lib/capybara/helpers.rb +1 -0
- data/lib/capybara/node/actions.rb +4 -0
- data/lib/capybara/node/base.rb +1 -0
- data/lib/capybara/node/element.rb +3 -0
- data/lib/capybara/node/finders.rb +2 -0
- data/lib/capybara/node/simple.rb +1 -0
- data/lib/capybara/queries/base_query.rb +1 -0
- data/lib/capybara/queries/match_query.rb +1 -0
- data/lib/capybara/queries/selector_query.rb +34 -37
- data/lib/capybara/queries/text_query.rb +2 -0
- data/lib/capybara/rack_test/browser.rb +1 -0
- data/lib/capybara/rack_test/driver.rb +5 -0
- data/lib/capybara/rack_test/node.rb +2 -0
- data/lib/capybara/result.rb +2 -0
- data/lib/capybara/rspec/compound.rb +2 -0
- data/lib/capybara/rspec/matchers.rb +1 -0
- data/lib/capybara/selector.rb +14 -27
- data/lib/capybara/selector/builders/css_builder.rb +49 -0
- data/lib/capybara/selector/builders/xpath_builder.rb +56 -0
- data/lib/capybara/selector/filter_set.rb +1 -0
- data/lib/capybara/selector/filters/base.rb +2 -0
- data/lib/capybara/selector/regexp_disassembler.rb +66 -0
- data/lib/capybara/selector/selector.rb +25 -5
- data/lib/capybara/selenium/driver.rb +8 -1
- data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +19 -1
- data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +1 -0
- data/lib/capybara/selenium/node.rb +7 -0
- data/lib/capybara/selenium/nodes/chrome_node.rb +2 -0
- data/lib/capybara/selenium/nodes/marionette_node.rb +37 -20
- data/lib/capybara/server.rb +4 -0
- data/lib/capybara/server/animation_disabler.rb +1 -0
- data/lib/capybara/session.rb +5 -0
- data/lib/capybara/session/config.rb +2 -0
- data/lib/capybara/spec/session/has_css_spec.rb +16 -0
- data/lib/capybara/spec/session/has_field_spec.rb +4 -0
- data/lib/capybara/spec/session/node_spec.rb +6 -0
- data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
- data/lib/capybara/spec/session/reset_session_spec.rb +15 -1
- data/lib/capybara/spec/session/selectors_spec.rb +12 -2
- data/lib/capybara/spec/views/form.erb +15 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/xpath_patches.rb +27 -0
- data/spec/dsl_spec.rb +15 -1
- data/spec/rack_test_spec.rb +6 -1
- data/spec/regexp_dissassembler_spec.rb +154 -0
- data/spec/selector_spec.rb +37 -2
- data/spec/selenium_spec_chrome.rb +2 -2
- data/spec/selenium_spec_firefox_remote.rb +2 -0
- data/spec/selenium_spec_marionette.rb +11 -0
- data/spec/shared_selenium_session.rb +20 -0
- data/spec/spec_helper.rb +4 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42a9311d8f3e7670a90f5851cc9927db169022d550e1c9b2a086900145b7ad18
|
4
|
+
data.tar.gz: 8159deadf9826514634c55f9744d2b8f18a910434d71f66870eb299f6c5980ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d8256a0c4bc0ca9586d65aec40d4814c060226a7902f5122271d4c9e22b63eec1568c81315d327e02873d732062beef6a0c01d0a50df9d49e14fcd0189084c0
|
7
|
+
data.tar.gz: a86ce539c90c9fbaea0ac8165bbf6f1b66ee8fa4589c129c366d88730cb1881616a3260c3a710b546c0e833aa708de8791326cbaa9c5ffe5f8875ce249c779cd
|
data/History.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# Version 3.9.0
|
2
|
+
Release date: 2018-10-03
|
3
|
+
|
4
|
+
### Added
|
5
|
+
|
6
|
+
* Selenium with Chrome removes all cookies at session reset instead of just cookies from current domain if possible
|
7
|
+
* Support for Regexp for system :id and :class filters where possible
|
8
|
+
* `using_session` now accepts a session object as well as the name of the session for users who manually manage sessions
|
9
|
+
* The `:field` selector will now find `type = "hidden"` fields if the `type: "hidden"` filter option is provided
|
10
|
+
|
1
11
|
# Version 3.8.2
|
2
12
|
Release date: 2018-09-26
|
3
13
|
|
data/lib/capybara.rb
CHANGED
@@ -299,7 +299,7 @@ module Capybara
|
|
299
299
|
# @return [Capybara::Session] The currently used session
|
300
300
|
#
|
301
301
|
def current_session
|
302
|
-
session_pool["#{current_driver}:#{session_name}:#{app.object_id}"]
|
302
|
+
specified_session || session_pool["#{current_driver}:#{session_name}:#{app.object_id}"]
|
303
303
|
end
|
304
304
|
|
305
305
|
##
|
@@ -337,22 +337,25 @@ module Capybara
|
|
337
337
|
|
338
338
|
##
|
339
339
|
#
|
340
|
-
# Yield a block using a specific session name.
|
340
|
+
# Yield a block using a specific session name or Capybara::Session instance.
|
341
341
|
#
|
342
|
-
def using_session(
|
342
|
+
def using_session(name_or_session)
|
343
343
|
previous_session_info = {
|
344
|
+
specified_session: specified_session,
|
344
345
|
session_name: session_name,
|
345
346
|
current_driver: current_driver,
|
346
347
|
app: app
|
347
348
|
}
|
348
|
-
self.session_name =
|
349
|
+
self.specified_session = self.session_name = nil
|
350
|
+
if name_or_session.is_a? Capybara::Session
|
351
|
+
self.specified_session = name_or_session
|
352
|
+
else
|
353
|
+
self.session_name = name_or_session
|
354
|
+
end
|
349
355
|
yield
|
350
356
|
ensure
|
351
|
-
self.session_name = previous_session_info
|
352
|
-
if threadsafe
|
353
|
-
self.current_driver = previous_session_info[:current_driver]
|
354
|
-
self.app = previous_session_info[:app]
|
355
|
-
end
|
357
|
+
self.session_name, self.specified_session = previous_session_info.values_at(:session_name, :specified_session)
|
358
|
+
self.current_driver, self.app = previous_session_info.values_at(:current_driver, :app) if threadsafe
|
356
359
|
end
|
357
360
|
|
358
361
|
##
|
@@ -381,7 +384,25 @@ module Capybara
|
|
381
384
|
end
|
382
385
|
|
383
386
|
def session_pool
|
384
|
-
@session_pool ||=
|
387
|
+
@session_pool ||= Hash.new do |hash, name|
|
388
|
+
hash[name] = Capybara::Session.new(current_driver, app)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def specified_session
|
393
|
+
if threadsafe
|
394
|
+
Thread.current['capybara_specified_session']
|
395
|
+
else
|
396
|
+
@specified_session
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def specified_session=(session)
|
401
|
+
if threadsafe
|
402
|
+
Thread.current['capybara_specified_session'] = session
|
403
|
+
else
|
404
|
+
@specified_session = session
|
405
|
+
end
|
385
406
|
end
|
386
407
|
end
|
387
408
|
|
@@ -393,6 +414,7 @@ module Capybara
|
|
393
414
|
module RackTest; end
|
394
415
|
module Selenium; end
|
395
416
|
|
417
|
+
require 'capybara/xpath_patches'
|
396
418
|
require 'capybara/helpers'
|
397
419
|
require 'capybara/session'
|
398
420
|
require 'capybara/window'
|
data/lib/capybara/config.rb
CHANGED
data/lib/capybara/dsl.rb
CHANGED
@@ -18,8 +18,8 @@ module Capybara
|
|
18
18
|
#
|
19
19
|
# Shortcut to working in a different session.
|
20
20
|
#
|
21
|
-
def using_session(
|
22
|
-
Capybara.using_session(
|
21
|
+
def using_session(name_or_session, &block)
|
22
|
+
Capybara.using_session(name_or_session, &block)
|
23
23
|
end
|
24
24
|
|
25
25
|
# Shortcut to using a different wait time.
|
data/lib/capybara/helpers.rb
CHANGED
@@ -274,6 +274,7 @@ module Capybara
|
|
274
274
|
find(:select, from, options)
|
275
275
|
rescue Capybara::ElementNotFound => select_error
|
276
276
|
raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
|
277
|
+
|
277
278
|
begin
|
278
279
|
find(:datalist_input, from, options)
|
279
280
|
rescue Capybara::ElementNotFound => dlinput_error
|
@@ -287,6 +288,7 @@ module Capybara
|
|
287
288
|
datalist_options = input.evaluate_script(DATALIST_OPTIONS_SCRIPT)
|
288
289
|
option = datalist_options.find { |opt| opt.values_at('value', 'label').include?(value) }
|
289
290
|
raise ::Capybara::ElementNotFound, %(Unable to find datalist option "#{value}") unless option
|
291
|
+
|
290
292
|
input.set(option['value'])
|
291
293
|
rescue ::Capybara::NotSupportedByDriverError
|
292
294
|
# Implement for drivers that don't support JS
|
@@ -299,6 +301,7 @@ module Capybara
|
|
299
301
|
visible_css = { opacity: 1, display: 'block', visibility: 'visible' } if visible_css == true
|
300
302
|
_update_style(element, visible_css)
|
301
303
|
raise ExpectationNotMet, 'The style changes in :make_visible did not make the file input visible' unless element.visible?
|
304
|
+
|
302
305
|
begin
|
303
306
|
yield element
|
304
307
|
ensure
|
@@ -326,6 +329,7 @@ module Capybara
|
|
326
329
|
el.set(checked)
|
327
330
|
rescue StandardError => err
|
328
331
|
raise unless allow_label_click && catch_error?(err)
|
332
|
+
|
329
333
|
begin
|
330
334
|
el ||= find(selector, locator, options.merge(visible: :all))
|
331
335
|
el.session.find(:label, for: el, visible: true).click unless el.checked? == checked
|
data/lib/capybara/node/base.rb
CHANGED
@@ -84,6 +84,7 @@ module Capybara
|
|
84
84
|
def style(*styles)
|
85
85
|
styles = styles.flatten.map(&:to_s)
|
86
86
|
raise ArgumentError, 'You must specify at least one CSS style' if styles.empty?
|
87
|
+
|
87
88
|
begin
|
88
89
|
synchronize { base.style(styles) }
|
89
90
|
rescue NotImplementedError => err
|
@@ -113,6 +114,7 @@ module Capybara
|
|
113
114
|
# @return [Capybara::Node::Element] The element
|
114
115
|
def set(value, **options)
|
115
116
|
raise Capybara::ReadOnlyElementError, "Attempt to set readonly element with value: #{value}" if readonly?
|
117
|
+
|
116
118
|
options = session_options.default_set_options.to_h.merge(options)
|
117
119
|
synchronize { base.set(value, options) }
|
118
120
|
self
|
@@ -442,6 +444,7 @@ module Capybara
|
|
442
444
|
%(#<Capybara::Node::Element tag="#{base.tag_name}">)
|
443
445
|
rescue StandardError => err
|
444
446
|
raise unless session.driver.invalid_element_errors.any? { |et| err.is_a?(et) }
|
447
|
+
|
445
448
|
%(Obsolete #<Capybara::Node::Element>)
|
446
449
|
end
|
447
450
|
|
@@ -252,10 +252,12 @@ module Capybara
|
|
252
252
|
synchronize(query.wait) do
|
253
253
|
result = query.resolve_for(self)
|
254
254
|
raise Capybara::ExpectationNotMet, result.failure_message unless result.matches_count?
|
255
|
+
|
255
256
|
result
|
256
257
|
end
|
257
258
|
rescue Capybara::ExpectationNotMet
|
258
259
|
raise if minimum_specified || (result.compare_count == 1)
|
260
|
+
|
259
261
|
Result.new([], nil)
|
260
262
|
end
|
261
263
|
end
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -51,6 +51,7 @@ module Capybara
|
|
51
51
|
return false if options[:maximum] && (Integer(options[:maximum]) < count)
|
52
52
|
return false if options[:minimum] && (Integer(options[:minimum]) > count)
|
53
53
|
return false if options[:between] && !options[:between].include?(count)
|
54
|
+
|
54
55
|
true
|
55
56
|
end
|
56
57
|
|
@@ -59,8 +59,11 @@ module Capybara
|
|
59
59
|
|
60
60
|
def matches_filters?(node)
|
61
61
|
return true if (@resolved_node&.== node) && options[:allow_self]
|
62
|
+
|
62
63
|
@applied_filters ||= :system
|
64
|
+
return false unless matches_id_filter?(node) && matches_class_filter?(node)
|
63
65
|
return false unless matches_text_filter?(node) && matches_exact_text_filter?(node) && matches_visible_filter?(node)
|
66
|
+
|
64
67
|
@applied_filters = :node
|
65
68
|
matches_node_filters?(node) && matches_filter_block?(node)
|
66
69
|
rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
|
@@ -68,7 +71,7 @@ module Capybara
|
|
68
71
|
end
|
69
72
|
|
70
73
|
def visible
|
71
|
-
case (vis = options.fetch(:visible) { @selector.default_visibility(session_options.ignore_hidden_elements) })
|
74
|
+
case (vis = options.fetch(:visible) { @selector.default_visibility(session_options.ignore_hidden_elements, options) })
|
72
75
|
when true then :visible
|
73
76
|
when false then :all
|
74
77
|
else vis
|
@@ -174,6 +177,7 @@ module Capybara
|
|
174
177
|
|
175
178
|
def matches_filter_block?(node)
|
176
179
|
return true unless @filter_block
|
180
|
+
|
177
181
|
if node.respond_to?(:session)
|
178
182
|
node.session.using_wait_time(0) { @filter_block.call(node) }
|
179
183
|
else
|
@@ -203,6 +207,7 @@ module Capybara
|
|
203
207
|
unless VALID_MATCH.include?(match)
|
204
208
|
raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(', ')}"
|
205
209
|
end
|
210
|
+
|
206
211
|
unhandled_options = @options.keys.reject do |option_name|
|
207
212
|
valid_keys.include?(option_name) ||
|
208
213
|
expression_filters.any? { |_name, ef| ef.handles_option? option_name } ||
|
@@ -210,28 +215,22 @@ module Capybara
|
|
210
215
|
end
|
211
216
|
|
212
217
|
return if unhandled_options.empty?
|
218
|
+
|
213
219
|
invalid_names = unhandled_options.map(&:inspect).join(', ')
|
214
220
|
valid_names = (valid_keys - [:allow_self]).map(&:inspect).join(', ')
|
215
221
|
raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
|
216
222
|
end
|
217
223
|
|
218
224
|
def filtered_xpath(expr)
|
219
|
-
if use_default_id_filter?
|
220
|
-
|
221
|
-
XPath.attr(:id)[options[:id]]
|
222
|
-
else
|
223
|
-
XPath.attr(:id) == options[:id]
|
224
|
-
end
|
225
|
-
expr = "(#{expr})[#{id_xpath}]"
|
226
|
-
end
|
227
|
-
expr = "(#{expr})[#{xpath_from_classes}]" if use_default_class_filter?
|
225
|
+
expr = "(#{expr})[#{conditions_from_id}]" if use_default_id_filter?
|
226
|
+
expr = "(#{expr})[#{conditions_from_classes}]" if use_default_class_filter?
|
228
227
|
expr
|
229
228
|
end
|
230
229
|
|
231
230
|
def filtered_css(expr)
|
232
231
|
::Capybara::Selector::CSS.split(expr).map do |sel|
|
233
|
-
sel +=
|
234
|
-
sel +=
|
232
|
+
sel += conditions_from_id if use_default_id_filter?
|
233
|
+
sel += conditions_from_classes if use_default_class_filter?
|
235
234
|
sel
|
236
235
|
end.join(', ')
|
237
236
|
end
|
@@ -244,33 +243,12 @@ module Capybara
|
|
244
243
|
options.key?(:class) && !custom_keys.include?(:class)
|
245
244
|
end
|
246
245
|
|
247
|
-
def
|
248
|
-
|
249
|
-
raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
|
250
|
-
end
|
251
|
-
|
252
|
-
classes = Array(options[:class]).group_by { |cl| cl.start_with? '!' }
|
253
|
-
(classes[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl)}" } +
|
254
|
-
classes[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join
|
246
|
+
def conditions_from_classes
|
247
|
+
builder.class_conditions(options[:class])
|
255
248
|
end
|
256
249
|
|
257
|
-
def
|
258
|
-
|
259
|
-
raise ArgumentError, 'XPath expressions are not supported for the :id filter with CSS based selectors'
|
260
|
-
end
|
261
|
-
"##{::Capybara::Selector::CSS.escape(options[:id])}"
|
262
|
-
end
|
263
|
-
|
264
|
-
def xpath_from_classes
|
265
|
-
return XPath.attr(:class)[options[:class]] if options[:class].is_a?(XPath::Expression)
|
266
|
-
|
267
|
-
Array(options[:class]).map do |klass|
|
268
|
-
if klass.start_with?('!')
|
269
|
-
!XPath.attr(:class).contains_word(klass.slice(1..-1))
|
270
|
-
else
|
271
|
-
XPath.attr(:class).contains_word(klass)
|
272
|
-
end
|
273
|
-
end.reduce(:&)
|
250
|
+
def conditions_from_id
|
251
|
+
builder.attribute_conditions(id: options[:id])
|
274
252
|
end
|
275
253
|
|
276
254
|
def apply_expression_filters(expression)
|
@@ -294,6 +272,7 @@ module Capybara
|
|
294
272
|
|
295
273
|
def warn_exact_usage
|
296
274
|
return unless options.key?(:exact) && !supports_exact?
|
275
|
+
|
297
276
|
warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression}\" has no effect."
|
298
277
|
end
|
299
278
|
|
@@ -313,16 +292,30 @@ module Capybara
|
|
313
292
|
node.is_a?(::Capybara::Node::Simple) && node.path == '/'
|
314
293
|
end
|
315
294
|
|
295
|
+
def matches_id_filter?(node)
|
296
|
+
return true unless use_default_id_filter? && options[:id].is_a?(Regexp)
|
297
|
+
|
298
|
+
node[:id] =~ options[:id]
|
299
|
+
end
|
300
|
+
|
301
|
+
def matches_class_filter?(node)
|
302
|
+
return true unless use_default_class_filter? && options[:class].is_a?(Regexp)
|
303
|
+
|
304
|
+
node[:class] =~ options[:class]
|
305
|
+
end
|
306
|
+
|
316
307
|
def matches_text_filter?(node)
|
317
308
|
value = options[:text]
|
318
309
|
return true unless value
|
319
310
|
return matches_text_exactly?(node, value) if exact_text == true
|
311
|
+
|
320
312
|
regexp = value.is_a?(Regexp) ? value : Regexp.escape(value.to_s)
|
321
313
|
matches_text_regexp?(node, regexp)
|
322
314
|
end
|
323
315
|
|
324
316
|
def matches_exact_text_filter?(node)
|
325
317
|
return true unless exact_text.is_a?(String)
|
318
|
+
|
326
319
|
matches_text_exactly?(node, exact_text)
|
327
320
|
end
|
328
321
|
|
@@ -348,6 +341,10 @@ module Capybara
|
|
348
341
|
text_visible = :all if text_visible == :hidden
|
349
342
|
!!node.text(text_visible, normalize_ws: normalize_ws).match(regexp)
|
350
343
|
end
|
344
|
+
|
345
|
+
def builder
|
346
|
+
selector.builder
|
347
|
+
end
|
351
348
|
end
|
352
349
|
end
|
353
350
|
end
|
@@ -64,6 +64,7 @@ module Capybara
|
|
64
64
|
insensitive_regexp = Capybara::Helpers.to_regexp(@expected_text, options: Regexp::IGNORECASE)
|
65
65
|
insensitive_count = @actual_text.scan(insensitive_regexp).size
|
66
66
|
return if insensitive_count == @count
|
67
|
+
|
67
68
|
"it was found #{insensitive_count} #{Capybara::Helpers.declension('time', 'times', insensitive_count)} using a case insensitive search"
|
68
69
|
end
|
69
70
|
|
@@ -71,6 +72,7 @@ module Capybara
|
|
71
72
|
invisible_text = text(query_type: :all)
|
72
73
|
invisible_count = invisible_text.scan(@search_regexp).size
|
73
74
|
return if invisible_count == @count
|
75
|
+
|
74
76
|
"it was found #{invisible_count} #{Capybara::Helpers.declension('time', 'times', invisible_count)} including non-visible text"
|
75
77
|
rescue StandardError
|
76
78
|
# An error getting the non-visible text (if element goes out of scope) should not affect the response
|
@@ -16,6 +16,7 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
16
16
|
|
17
17
|
def initialize(app, **options)
|
18
18
|
raise ArgumentError, 'rack-test requires a rack application, but none was given' unless app
|
19
|
+
|
19
20
|
@app = app
|
20
21
|
@options = DEFAULT_OPTIONS.merge(options)
|
21
22
|
end
|
@@ -74,6 +75,10 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
|
|
74
75
|
|
75
76
|
def find_css(selector)
|
76
77
|
browser.find(:css, selector)
|
78
|
+
rescue Nokogiri::CSS::SyntaxError
|
79
|
+
raise unless selector.include?(' i]')
|
80
|
+
|
81
|
+
raise ArgumentError, "This driver doesn't support case insensitive attribute matching when using CSS base selectors"
|
77
82
|
end
|
78
83
|
|
79
84
|
def html
|
@@ -50,12 +50,14 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
50
50
|
|
51
51
|
def select_option
|
52
52
|
return if disabled?
|
53
|
+
|
53
54
|
deselect_options unless select_node.multiple?
|
54
55
|
native['selected'] = 'selected'
|
55
56
|
end
|
56
57
|
|
57
58
|
def unselect_option
|
58
59
|
raise Capybara::UnselectNotAllowed, 'Cannot unselect option from single select box.' unless select_node.multiple?
|
60
|
+
|
59
61
|
native.remove_attribute('selected')
|
60
62
|
end
|
61
63
|
|