capybara 3.8.2 → 3.9.0
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/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
|
|