capybara 3.11.1 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +18 -1
  3. data/README.md +4 -4
  4. data/lib/capybara.rb +7 -0
  5. data/lib/capybara/node/element.rb +7 -1
  6. data/lib/capybara/node/matchers.rb +0 -18
  7. data/lib/capybara/queries/base_query.rb +1 -1
  8. data/lib/capybara/queries/selector_query.rb +24 -39
  9. data/lib/capybara/result.rb +4 -4
  10. data/lib/capybara/selector.rb +7 -15
  11. data/lib/capybara/selector/builders/css_builder.rb +59 -27
  12. data/lib/capybara/selector/builders/xpath_builder.rb +50 -35
  13. data/lib/capybara/selector/css.rb +7 -7
  14. data/lib/capybara/selector/filter.rb +1 -0
  15. data/lib/capybara/selector/filter_set.rb +17 -15
  16. data/lib/capybara/selector/filters/locator_filter.rb +19 -0
  17. data/lib/capybara/selector/regexp_disassembler.rb +104 -61
  18. data/lib/capybara/selector/selector.rb +14 -5
  19. data/lib/capybara/selenium/driver.rb +14 -9
  20. data/lib/capybara/selenium/driver_specializations/{marionette_driver.rb → firefox_driver.rb} +3 -3
  21. data/lib/capybara/selenium/nodes/{marionette_node.rb → firefox_node.rb} +1 -1
  22. data/lib/capybara/spec/session/all_spec.rb +8 -1
  23. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  24. data/lib/capybara/spec/session/click_button_spec.rb +5 -3
  25. data/lib/capybara/spec/session/click_link_spec.rb +5 -5
  26. data/lib/capybara/spec/session/find_spec.rb +1 -1
  27. data/lib/capybara/spec/session/first_spec.rb +1 -1
  28. data/lib/capybara/spec/session/has_css_spec.rb +7 -0
  29. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  30. data/lib/capybara/spec/session/node_spec.rb +10 -3
  31. data/lib/capybara/spec/session/window/window_spec.rb +2 -2
  32. data/lib/capybara/spec/spec_helper.rb +1 -2
  33. data/lib/capybara/spec/views/obscured.erb +3 -0
  34. data/lib/capybara/version.rb +1 -1
  35. data/spec/css_builder_spec.rb +99 -0
  36. data/spec/result_spec.rb +6 -0
  37. data/spec/selector_spec.rb +26 -1
  38. data/spec/selenium_spec_chrome.rb +18 -16
  39. data/spec/selenium_spec_chrome_remote.rb +0 -2
  40. data/spec/{selenium_spec_marionette.rb → selenium_spec_firefox.rb} +31 -25
  41. data/spec/selenium_spec_firefox_remote.rb +4 -6
  42. data/spec/shared_selenium_session.rb +2 -2
  43. data/spec/spec_helper.rb +5 -5
  44. data/spec/xpath_builder_spec.rb +91 -0
  45. metadata +10 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54ee49a77f4b1cec2ed59869334766af110a64a50dae8cdc02a95b5ba750fb9b
4
- data.tar.gz: db2319b75d0626c01babc298eccd8ce76d3da65a8a0a994bb25de5c742e1721f
3
+ metadata.gz: 8b3b4d3a612269d9ee165e7ed94e1e62f91c6fd85bcc5c5ae614c25d0c22bd32
4
+ data.tar.gz: a2bdfa0c2398789bd15d791e3b2d0d03d9c3371033e4f4e4d21e302db845c93f
5
5
  SHA512:
6
- metadata.gz: d9c1965b9ba0d38813d3060aaefe797ef8d6610416c954bf4cebdae70de5ca23bc4b07303033ecbf2f5e62bc681319c6b20dc0567f49e89ccc02df38dd10d44d
7
- data.tar.gz: bf971abb444f58438091422e656108d29bb7efa0d2f2dfb1728a818626cea38e7fa700b3701904ca10344a4f272e14fd99482c23b62b9cfe3b6139d868fa5b30
6
+ metadata.gz: f4815632f5bc14c932e9b5181d68584ac3088f11241e8ee3c19f3ee0a27190f8ad1544b88a532f901eaf79f9ae3c2f721dfca1d2d6d20d098a40438042b9f64a
7
+ data.tar.gz: eb2765462cdbdda76a4ca5d100b93688029d1ca727c00579c5e9e5d77f26fc4a0fe64bbe3616bb3f89b34e730c337e242b319b2ea606b5287a780f8219ee5b08
data/History.md CHANGED
@@ -1,7 +1,24 @@
1
+ # Version 3.12.0
2
+ Release date: 2018-11-28
3
+
4
+ ### Added
5
+
6
+ * Support Ruby 2.6 endless range in Result#[] and query `:between` option
7
+ * Pre-registered headless firefox driver :selenium_headless [Andrew Havens]
8
+ * Selenium driver now defaults to clearing `sessionStorage` and `localStorage`. To disable pass `clear_local_storage: false` and/or `clear_session_storage: false` when creating Capybara::Selenium::Driver instance in your driver registration
9
+
10
+ ### Fixed
11
+
12
+ * Raise error if only :x or :y are passed as an offset to click methods
13
+
14
+ ### Removed
15
+
16
+ * Support for RSpec < 3.5
17
+
1
18
  # Version 3.11.1
2
19
  Release date: 2018-11-16
3
20
 
4
- ###Fixed
21
+ ### Fixed
5
22
 
6
23
  * Fixed :link_or_button XPath generation when it has had an expression filter added
7
24
 
data/README.md CHANGED
@@ -6,8 +6,7 @@
6
6
  [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jnicklas/capybara?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
7
  [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=capybara&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=capybara&package-manager=bundler&version-scheme=semver)
8
8
 
9
- **Note** You are viewing the README for the 3.11.0 version of Capybara.
10
-
9
+ **Note** You are viewing the README for the 3.12.x version of Capybara.
11
10
 
12
11
  Capybara helps you test web applications by simulating how a real user would
13
12
  interact with your app. It is agnostic about the driver running your tests and
@@ -139,7 +138,7 @@ There are also explicit tags for each registered driver set up for you (`@seleni
139
138
 
140
139
  ## <a name="using-capybara-with-rspec"></a>Using Capybara with RSpec
141
140
 
142
- Load RSpec 3.x support by adding the following line (typically to your
141
+ Load RSpec 3.5+ support by adding the following line (typically to your
143
142
  `spec_helper.rb` file):
144
143
 
145
144
  ```ruby
@@ -394,6 +393,7 @@ and add it to your Gemfile if you're using bundler.
394
393
  Capybara pre-registers a number of named drives that use Selenium - they are:
395
394
 
396
395
  * :selenium => Selenium driving Firefox
396
+ * :selenium_headless => Selenium driving Firefox in a headless configuration
397
397
  * :selenium_chrome => Selenium driving Chrome
398
398
  * :selenium_chrome_headless => Selenium driving Chrome in a headless configuration
399
399
 
@@ -404,7 +404,7 @@ to the browsers. See the section on adding and configuring drivers.
404
404
 
405
405
  **Note**: drivers which run the server in a different thread may not share the
406
406
  same transaction as your tests, causing data not to be shared between your test
407
- and test server, see "Transactions and database setup" below.
407
+ and test server, see [Transactions and database setup](#transactions-and-database-setup) below.
408
408
 
409
409
  ### <a name="capybara-webkit"></a>Capybara-webkit
410
410
 
@@ -532,6 +532,13 @@ Capybara.register_driver :selenium do |app|
532
532
  Capybara::Selenium::Driver.new(app)
533
533
  end
534
534
 
535
+ Capybara.register_driver :selenium_headless do |app|
536
+ Capybara::Selenium::Driver.load_selenium
537
+ browser_options = ::Selenium::WebDriver::Firefox::Options.new
538
+ browser_options.args << '-headless'
539
+ Capybara::Selenium::Driver.new(app, browser: :firefox, options: browser_options)
540
+ end
541
+
535
542
  Capybara.register_driver :selenium_chrome do |app|
536
543
  Capybara::Selenium::Driver.new(app, browser: :chrome)
537
544
  end
@@ -153,7 +153,9 @@ module Capybara
153
153
  # @option offset [Integer] y Y coordinate to offset the click location from the top left corner of the element
154
154
  # @return [Capybara::Node::Element] The element
155
155
  def click(*keys, wait: nil, **offset)
156
- synchronize(wait) { base.click(keys, offset) }
156
+ raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
157
+
158
+ synchronize(wait) { base.click(Array(keys), offset) }
157
159
  self
158
160
  end
159
161
 
@@ -164,6 +166,8 @@ module Capybara
164
166
  # @macro click_modifiers
165
167
  # @return [Capybara::Node::Element] The element
166
168
  def right_click(*keys, wait: nil, **offset)
169
+ raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
170
+
167
171
  synchronize(wait) { base.right_click(keys, offset) }
168
172
  self
169
173
  end
@@ -175,6 +179,8 @@ module Capybara
175
179
  # @macro click_modifiers
176
180
  # @return [Capybara::Node::Element] The element
177
181
  def double_click(*keys, wait: nil, **offset)
182
+ raise ArgumentError, 'You must specify both x: and y: for a click offset' if nil ^ offset[:x] ^ offset[:y]
183
+
178
184
  synchronize(wait) { base.double_click(keys, offset) }
179
185
  self
180
186
  end
@@ -546,24 +546,6 @@ module Capybara
546
546
  end
547
547
  end
548
548
 
549
- # Deprecated
550
- # TODO: remove
551
- def refute_selector(*args, &optional_filter_block)
552
- warn '`refute_selector` was never meant to be in this scope unless ' \
553
- 'using minitest. Either replace with `assert_no_selector` ' \
554
- "or require 'capybara/minitest'."
555
- assert_no_selector(*args, &optional_filter_block)
556
- end
557
-
558
- # Deprecated
559
- # TODO: remove
560
- def refute_matches_selector(*args, &optional_filter_block)
561
- warn '`refute_matches_selector` was never meant to be in this scope unless ' \
562
- 'using minitest. Either replace with `assert_not_matches_selector` ' \
563
- "or require 'capybara/minitest'."
564
- assert_not_matches_selector(*args, &optional_filter_block)
565
- end
566
-
567
549
  ##
568
550
  #
569
551
  # Checks if the current node matches given selector
@@ -79,7 +79,7 @@ module Capybara
79
79
  if count
80
80
  message << " #{occurrences count}"
81
81
  elsif between
82
- message << " between #{between.first} and #{between.last} times"
82
+ message << " between #{between.first} and #{between.end ? between.last : 'infinite'} times"
83
83
  elsif maximum
84
84
  message << " at most #{occurrences maximum}"
85
85
  elsif minimum
@@ -62,13 +62,11 @@ module Capybara
62
62
 
63
63
  def matches_filters?(node, node_filter_errors = [])
64
64
  return true if (@resolved_node&.== node) && options[:allow_self]
65
- return false unless matches_locator_filter?(node)
66
65
 
67
- applied_filters << :system
68
- return false unless matches_system_filters?(node)
69
-
70
- applied_filters << :node
71
- matches_node_filters?(node, node_filter_errors) && matches_filter_block?(node)
66
+ matches_locator_filter?(node) &&
67
+ matches_system_filters?(node) &&
68
+ matches_node_filters?(node, node_filter_errors) &&
69
+ matches_filter_block?(node)
72
70
  rescue *(node.respond_to?(:session) ? node.session.driver.invalid_element_errors : [])
73
71
  false
74
72
  end
@@ -93,11 +91,11 @@ module Capybara
93
91
  exact = exact? if exact.nil?
94
92
  expr = apply_expression_filters(@expression)
95
93
  expr = exact ? expr.to_xpath(:exact) : expr.to_s if expr.respond_to?(:to_xpath)
96
- filtered_xpath(expr)
94
+ filtered_expression(expr)
97
95
  end
98
96
 
99
97
  def css
100
- filtered_css(apply_expression_filters(@expression))
98
+ filtered_expression(apply_expression_filters(@expression))
101
99
  end
102
100
 
103
101
  # @api private
@@ -136,12 +134,10 @@ module Capybara
136
134
  end
137
135
 
138
136
  def find_selector(locator)
139
- selector = if locator.is_a?(Symbol)
140
- Selector.all.fetch(locator) { |sel_type| raise ArgumentError, "Unknown selector type (:#{sel_type})" }
141
- else
142
- Selector.all.values.find { |sel| sel.match?(locator) }
143
- end
144
- selector || Selector.all[session_options.default_selector]
137
+ case locator
138
+ when Symbol then Selector[locator]
139
+ else Selector.for(locator)
140
+ end || Selector[session_options.default_selector]
145
141
  end
146
142
 
147
143
  def find_nodes_by_selector_format(node, exact)
@@ -165,6 +161,8 @@ module Capybara
165
161
  end
166
162
 
167
163
  def matches_node_filters?(node, errors)
164
+ applied_filters << :node
165
+
168
166
  unapplied_options = options.keys - valid_keys
169
167
  @selector.with_filter_errors(errors) do
170
168
  node_filters.all? do |filter_name, filter|
@@ -196,7 +194,7 @@ module Capybara
196
194
  end
197
195
 
198
196
  def filter_set(name)
199
- ::Capybara::Selector::FilterSet.all[name]
197
+ ::Capybara::Selector::FilterSet[name]
200
198
  end
201
199
 
202
200
  def node_filters
@@ -235,18 +233,11 @@ module Capybara
235
233
  raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
236
234
  end
237
235
 
238
- def filtered_xpath(expr)
239
- expr = "(#{expr})[#{conditions_from_id}]" if use_default_id_filter?
240
- expr = "(#{expr})[#{conditions_from_classes}]" if use_default_class_filter?
241
- expr
242
- end
243
-
244
- def filtered_css(expr)
245
- ::Capybara::Selector::CSS.split(expr).map do |sel|
246
- sel += conditions_from_id if use_default_id_filter?
247
- sel += conditions_from_classes if use_default_class_filter?
248
- sel
249
- end.join(', ')
236
+ def filtered_expression(expr)
237
+ conditions = {}
238
+ conditions[:id] = options[:id] if use_default_id_filter?
239
+ conditions[:class] = options[:class] if use_default_class_filter?
240
+ builder(expr).add_attribute_conditions(conditions)
250
241
  end
251
242
 
252
243
  def use_default_id_filter?
@@ -257,14 +248,6 @@ module Capybara
257
248
  options.key?(:class) && !custom_keys.include?(:class)
258
249
  end
259
250
 
260
- def conditions_from_classes
261
- builder.class_conditions(options[:class])
262
- end
263
-
264
- def conditions_from_id
265
- builder.attribute_conditions(id: options[:id])
266
- end
267
-
268
251
  def apply_expression_filters(expression)
269
252
  unapplied_options = options.keys - valid_keys
270
253
  expression_filters.inject(expression) do |expr, (name, ef)|
@@ -307,12 +290,14 @@ module Capybara
307
290
  end
308
291
 
309
292
  def matches_locator_filter?(node)
310
- return true if @selector.locator_filter.nil?
293
+ return true unless @selector.locator_filter
311
294
 
312
- @selector.locator_filter.call(node, @locator)
295
+ @selector.locator_filter.matches?(node, @locator, @selector)
313
296
  end
314
297
 
315
298
  def matches_system_filters?(node)
299
+ applied_filters << :system
300
+
316
301
  matches_id_filter?(node) &&
317
302
  matches_class_filter?(node) &&
318
303
  matches_text_filter?(node) &&
@@ -374,8 +359,8 @@ module Capybara
374
359
  @selector.default_visibility(session_options.ignore_hidden_elements, options)
375
360
  end
376
361
 
377
- def builder
378
- selector.builder
362
+ def builder(expr)
363
+ selector.builder(expr)
379
364
  end
380
365
  end
381
366
  end
@@ -59,7 +59,7 @@ module Capybara
59
59
  nil
60
60
  end
61
61
  when Range
62
- idx.max
62
+ idx.end && idx.max # endless range will have end == nil
63
63
  end
64
64
 
65
65
  if max_idx.nil?
@@ -92,9 +92,9 @@ module Capybara
92
92
  end
93
93
 
94
94
  if between
95
- min, max = between.minmax
96
- size = load_up_to(max + 1)
97
- return between.include?(size) ? 0 : size <=> min
95
+ min, max = between.min, (between.end && between.max)
96
+ size = load_up_to(max ? max + 1 : min)
97
+ return size <=> min unless between.include?(size)
98
98
  end
99
99
 
100
100
  0
@@ -34,7 +34,7 @@ Capybara.add_selector(:css) do
34
34
  end
35
35
 
36
36
  Capybara.add_selector(:id) do
37
- xpath { |id| XPath.descendant[builder.attribute_conditions(id: id)] }
37
+ xpath { |id| builder(XPath.descendant).add_attribute_conditions(id: id) }
38
38
  locator_filter { |node, id| id.is_a?(Regexp) ? node[:id] =~ id : true }
39
39
  end
40
40
 
@@ -92,8 +92,7 @@ end
92
92
 
93
93
  Capybara.add_selector(:link) do
94
94
  xpath do |locator, href: true, alt: nil, title: nil, **|
95
- xpath = XPath.descendant(:a)
96
- xpath = xpath[@href_conditions = builder.attribute_conditions(href: href)]
95
+ xpath = builder(XPath.descendant(:a)).add_attribute_conditions(href: href)
97
96
 
98
97
  unless locator.nil?
99
98
  locator = locator.to_s
@@ -119,25 +118,18 @@ Capybara.add_selector(:link) do
119
118
  end
120
119
 
121
120
  expression_filter(:download, valid_values: [true, false, String]) do |expr, download|
122
- expr[builder.attribute_conditions(download: download)]
121
+ builder(expr).add_attribute_conditions(download: download)
123
122
  end
124
123
 
125
124
  describe_expression_filters do |**options|
126
125
  desc = +''
127
126
  if (href = options[:href])
128
- if !href.is_a?(Regexp)
129
- desc << " with href #{href.inspect}"
130
- elsif @href_conditions
131
- desc << " with href matching #{href.inspect}"
132
- end
127
+ desc << " with href #{'matching ' if href.is_a? Regexp}#{href.inspect}"
128
+ elsif options.key?(:href) # is nil/false specified?
129
+ desc << ' with no href attribute'
133
130
  end
134
- desc << ' with no href attribute' if options.fetch(:href, true).nil?
135
131
  desc
136
132
  end
137
-
138
- describe_node_filters do |href: nil, **|
139
- " with href matching #{href.inspect}" if href.is_a?(Regexp) && @href_conditions.nil?
140
- end
141
133
  end
142
134
 
143
135
  Capybara.add_selector(:button) do
@@ -489,7 +481,7 @@ Capybara.add_selector(:element) do
489
481
  end
490
482
 
491
483
  expression_filter(:attributes, matcher: /.+/) do |xpath, name, val|
492
- xpath[builder.attribute_conditions(name => val)]
484
+ builder(xpath).add_attribute_conditions(name => val)
493
485
  end
494
486
 
495
487
  node_filter(:attributes, matcher: /.+/) do |node, name, val|
@@ -6,41 +6,73 @@ module Capybara
6
6
  class Selector
7
7
  # @api private
8
8
  class CSSBuilder
9
- class << self
10
- def attribute_conditions(attributes)
11
- attributes.map do |attribute, value|
12
- case value
13
- when XPath::Expression
14
- raise ArgumentError, "XPath expressions are not supported for the :#{attribute} filter with CSS based selectors"
15
- when Regexp
16
- Selector::RegexpDisassembler.new(value).substrings.map do |str|
17
- "[#{attribute}*='#{str}'#{' i' if value.casefold?}]"
9
+ def initialize(expression)
10
+ @expression = expression || ''
11
+ end
12
+
13
+ attr_reader :expression
14
+
15
+ def add_attribute_conditions(**attributes)
16
+ @expression = attributes.inject(expression) do |css, (name, value)|
17
+ conditions = if name == :class
18
+ class_conditions(value)
19
+ elsif value.is_a? Regexp
20
+ Selector::RegexpDisassembler.new(value).alternated_substrings.map do |strs|
21
+ strs.map do |str|
22
+ "[#{name}*='#{str}'#{' i' if value.casefold?}]"
18
23
  end.join
19
- when true
20
- "[#{attribute}]"
21
- when false
22
- ':not([attribute])'
23
- else
24
- if attribute == :id
25
- "##{::Capybara::Selector::CSS.escape(value)}"
26
- else
27
- "[#{attribute}='#{value}']"
28
- end
29
24
  end
30
- end.join
25
+ else
26
+ [attribute_conditions(name => value)]
27
+ end
28
+
29
+ ::Capybara::Selector::CSS.split(css).map do |sel|
30
+ next sel if conditions.empty?
31
+
32
+ conditions.map { |cond| sel + cond }.join(', ')
33
+ end.join(', ')
31
34
  end
35
+ end
36
+
37
+ private
32
38
 
33
- def class_conditions(classes)
34
- case classes
39
+ def attribute_conditions(attributes)
40
+ attributes.map do |attribute, value|
41
+ case value
35
42
  when XPath::Expression
36
- raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
43
+ raise ArgumentError, "XPath expressions are not supported for the :#{attribute} filter with CSS based selectors"
37
44
  when Regexp
38
- attribute_conditions(class: classes)
45
+ Selector::RegexpDisassembler.new(value).substrings.map do |str|
46
+ "[#{attribute}*='#{str}'#{' i' if value.casefold?}]"
47
+ end.join
48
+ when true
49
+ "[#{attribute}]"
50
+ when false
51
+ ':not([attribute])'
39
52
  else
40
- cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
41
- (cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
42
- cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join
53
+ if attribute == :id
54
+ "##{::Capybara::Selector::CSS.escape(value)}"
55
+ else
56
+ "[#{attribute}='#{value}']"
57
+ end
58
+ end
59
+ end.join
60
+ end
61
+
62
+ def class_conditions(classes)
63
+ case classes
64
+ when XPath::Expression
65
+ raise ArgumentError, 'XPath expressions are not supported for the :class filter with CSS based selectors'
66
+ when Regexp
67
+ Selector::RegexpDisassembler.new(classes).alternated_substrings.map do |strs|
68
+ strs.map do |str|
69
+ "[class*='#{str}'#{' i' if classes.casefold?}]"
70
+ end.join
43
71
  end
72
+ else
73
+ cls = Array(classes).group_by { |cl| cl.start_with?('!') && !cl.start_with?('!!!') }
74
+ [(cls[false].to_a.map { |cl| ".#{Capybara::Selector::CSS.escape(cl.sub(/^!!/, ''))}" } +
75
+ cls[true].to_a.map { |cl| ":not(.#{Capybara::Selector::CSS.escape(cl.slice(1..-1))})" }).join]
44
76
  end
45
77
  end
46
78
  end