capybara 3.35.2 → 3.37.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +59 -4
  3. data/README.md +5 -1
  4. data/lib/capybara/config.rb +16 -4
  5. data/lib/capybara/driver/base.rb +4 -0
  6. data/lib/capybara/driver/node.rb +5 -1
  7. data/lib/capybara/dsl.rb +4 -10
  8. data/lib/capybara/helpers.rb +2 -11
  9. data/lib/capybara/minitest/spec.rb +2 -2
  10. data/lib/capybara/node/actions.rb +10 -5
  11. data/lib/capybara/node/document.rb +2 -2
  12. data/lib/capybara/node/element.rb +13 -2
  13. data/lib/capybara/node/finders.rb +2 -2
  14. data/lib/capybara/node/simple.rb +5 -1
  15. data/lib/capybara/queries/active_element_query.rb +18 -0
  16. data/lib/capybara/queries/ancestor_query.rb +2 -1
  17. data/lib/capybara/queries/current_path_query.rb +1 -1
  18. data/lib/capybara/queries/selector_query.rb +34 -8
  19. data/lib/capybara/queries/sibling_query.rb +2 -1
  20. data/lib/capybara/rack_test/browser.rb +41 -6
  21. data/lib/capybara/rack_test/driver.rb +4 -4
  22. data/lib/capybara/rack_test/node.rb +10 -7
  23. data/lib/capybara/registration_container.rb +0 -3
  24. data/lib/capybara/registrations/drivers.rb +3 -3
  25. data/lib/capybara/rspec/matcher_proxies.rb +3 -3
  26. data/lib/capybara/rspec/matchers/have_selector.rb +5 -5
  27. data/lib/capybara/rspec/matchers.rb +14 -14
  28. data/lib/capybara/selector/builders/css_builder.rb +1 -1
  29. data/lib/capybara/selector/builders/xpath_builder.rb +1 -1
  30. data/lib/capybara/selector/css.rb +1 -1
  31. data/lib/capybara/selector/definition/button.rb +9 -4
  32. data/lib/capybara/selector/definition/checkbox.rb +1 -1
  33. data/lib/capybara/selector/definition/file_field.rb +1 -1
  34. data/lib/capybara/selector/definition/fillable_field.rb +1 -1
  35. data/lib/capybara/selector/definition/radio_button.rb +1 -1
  36. data/lib/capybara/selector/definition.rb +3 -1
  37. data/lib/capybara/selector/filter_set.rb +4 -6
  38. data/lib/capybara/selector.rb +1 -0
  39. data/lib/capybara/selenium/driver.rb +25 -11
  40. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +1 -1
  41. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +1 -1
  42. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  43. data/lib/capybara/selenium/node.rb +22 -8
  44. data/lib/capybara/selenium/nodes/chrome_node.rb +1 -1
  45. data/lib/capybara/selenium/nodes/edge_node.rb +1 -1
  46. data/lib/capybara/selenium/nodes/firefox_node.rb +1 -1
  47. data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
  48. data/lib/capybara/server/animation_disabler.rb +35 -17
  49. data/lib/capybara/session/config.rb +1 -1
  50. data/lib/capybara/session.rb +20 -23
  51. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  52. data/lib/capybara/spec/session/all_spec.rb +9 -13
  53. data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
  54. data/lib/capybara/spec/session/check_spec.rb +9 -0
  55. data/lib/capybara/spec/session/choose_spec.rb +6 -0
  56. data/lib/capybara/spec/session/has_any_selectors_spec.rb +4 -0
  57. data/lib/capybara/spec/session/has_button_spec.rb +24 -0
  58. data/lib/capybara/spec/session/has_current_path_spec.rb +2 -2
  59. data/lib/capybara/spec/session/has_field_spec.rb +25 -1
  60. data/lib/capybara/spec/session/has_link_spec.rb +24 -0
  61. data/lib/capybara/spec/session/has_select_spec.rb +4 -4
  62. data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
  63. data/lib/capybara/spec/session/has_text_spec.rb +2 -6
  64. data/lib/capybara/spec/session/node_spec.rb +43 -1
  65. data/lib/capybara/spec/session/scroll_spec.rb +4 -4
  66. data/lib/capybara/spec/session/visit_spec.rb +14 -0
  67. data/lib/capybara/spec/session/window/window_spec.rb +1 -1
  68. data/lib/capybara/spec/spec_helper.rb +4 -3
  69. data/lib/capybara/spec/test_app.rb +50 -8
  70. data/lib/capybara/spec/views/animated.erb +1 -1
  71. data/lib/capybara/spec/views/form.erb +11 -3
  72. data/lib/capybara/spec/views/frame_child.erb +1 -1
  73. data/lib/capybara/spec/views/frame_one.erb +1 -1
  74. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  75. data/lib/capybara/spec/views/frame_two.erb +1 -1
  76. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  77. data/lib/capybara/spec/views/layout.erb +10 -0
  78. data/lib/capybara/spec/views/obscured.erb +1 -1
  79. data/lib/capybara/spec/views/offset.erb +2 -1
  80. data/lib/capybara/spec/views/path.erb +2 -2
  81. data/lib/capybara/spec/views/popup_one.erb +1 -1
  82. data/lib/capybara/spec/views/popup_two.erb +1 -1
  83. data/lib/capybara/spec/views/react.erb +2 -2
  84. data/lib/capybara/spec/views/scroll.erb +2 -1
  85. data/lib/capybara/spec/views/spatial.erb +1 -1
  86. data/lib/capybara/spec/views/with_animation.erb +2 -3
  87. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  88. data/lib/capybara/spec/views/with_dragula.erb +2 -2
  89. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  90. data/lib/capybara/spec/views/with_hover.erb +2 -2
  91. data/lib/capybara/spec/views/with_html.erb +1 -1
  92. data/lib/capybara/spec/views/with_jquery_animation.erb +1 -1
  93. data/lib/capybara/spec/views/with_js.erb +2 -3
  94. data/lib/capybara/spec/views/with_jstree.erb +1 -1
  95. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  96. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  97. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  98. data/lib/capybara/spec/views/with_sortable_js.erb +2 -2
  99. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  100. data/lib/capybara/spec/views/with_windows.erb +1 -1
  101. data/lib/capybara/spec/views/within_frames.erb +1 -1
  102. data/lib/capybara/version.rb +1 -1
  103. data/lib/capybara/window.rb +1 -1
  104. data/lib/capybara.rb +19 -22
  105. data/spec/basic_node_spec.rb +16 -3
  106. data/spec/dsl_spec.rb +3 -3
  107. data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
  108. data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
  109. data/spec/rack_test_spec.rb +20 -10
  110. data/spec/result_spec.rb +32 -35
  111. data/spec/rspec/features_spec.rb +3 -3
  112. data/spec/rspec/scenarios_spec.rb +1 -1
  113. data/spec/rspec/shared_spec_matchers.rb +2 -2
  114. data/spec/sauce_spec_chrome.rb +3 -3
  115. data/spec/selector_spec.rb +1 -1
  116. data/spec/selenium_spec_chrome.rb +9 -10
  117. data/spec/selenium_spec_chrome_remote.rb +9 -8
  118. data/spec/selenium_spec_firefox.rb +8 -3
  119. data/spec/selenium_spec_firefox_remote.rb +2 -2
  120. data/spec/selenium_spec_ie.rb +3 -6
  121. data/spec/selenium_spec_safari.rb +31 -19
  122. data/spec/server_spec.rb +5 -5
  123. data/spec/shared_selenium_node.rb +0 -4
  124. data/spec/shared_selenium_session.rb +20 -10
  125. data/spec/spec_helper.rb +1 -1
  126. metadata +37 -14
  127. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -16,16 +16,20 @@ module Capybara
16
16
 
17
17
  def initialize(app)
18
18
  @app = app
19
- @disable_markup = format(DISABLE_MARKUP_TEMPLATE, selector: self.class.selector_for(Capybara.disable_animation))
19
+ @disable_css_markup = format(DISABLE_CSS_MARKUP_TEMPLATE,
20
+ selector: self.class.selector_for(Capybara.disable_animation))
21
+ @disable_js_markup = format(DISABLE_JS_MARKUP_TEMPLATE,
22
+ selector: self.class.selector_for(Capybara.disable_animation))
20
23
  end
21
24
 
22
25
  def call(env)
23
26
  @status, @headers, @body = @app.call(env)
24
27
  return [@status, @headers, @body] unless html_content?
25
28
 
29
+ nonces = directive_nonces.transform_values { |nonce| "nonce=\"#{nonce}\"" if nonce && !nonce.empty? }
26
30
  response = Rack::Response.new([], @status, @headers)
27
31
 
28
- @body.each { |html| response.write insert_disable(html) }
32
+ @body.each { |html| response.write insert_disable(html, nonces) }
29
33
  @body.close if @body.respond_to?(:close)
30
34
 
31
35
  response.finish
@@ -33,31 +37,45 @@ module Capybara
33
37
 
34
38
  private
35
39
 
36
- attr_reader :disable_markup
40
+ attr_reader :disable_css_markup, :disable_js_markup
37
41
 
38
42
  def html_content?
39
43
  /html/.match?(@headers['Content-Type'])
40
44
  end
41
45
 
42
- def insert_disable(html)
43
- html.sub(%r{(</body>)}, "#{disable_markup}\\1")
46
+ def insert_disable(html, nonces)
47
+ html.sub(%r{(</head>)}, "<style #{nonces['style-src']}>#{disable_css_markup}</style>\\1")
48
+ .sub(%r{(</body>)}, "<script #{nonces['script-src']}>#{disable_js_markup}</script>\\1")
44
49
  end
45
50
 
46
- DISABLE_MARKUP_TEMPLATE = <<~HTML
47
- <script>
51
+ def directive_nonces
52
+ @headers.fetch('Content-Security-Policy', '')
53
+ .split(';')
54
+ .map(&:split)
55
+ .to_h do |s|
56
+ [
57
+ s[0], s[1..].filter_map do |value|
58
+ /^'nonce-(?<nonce>.+)'/ =~ value
59
+ nonce
60
+ end[0]
61
+ ]
62
+ end
63
+ end
64
+
65
+ DISABLE_CSS_MARKUP_TEMPLATE = <<~CSS
66
+ %<selector>s, %<selector>s::before, %<selector>s::after {
67
+ transition: none !important;
68
+ animation-duration: 0s !important;
69
+ animation-delay: 0s !important;
70
+ scroll-behavior: auto !important;
71
+ }
72
+ CSS
73
+
74
+ DISABLE_JS_MARKUP_TEMPLATE = <<~SCRIPT
48
75
  //<![CDATA[
49
76
  (typeof jQuery !== 'undefined') && (jQuery.fx.off = true);
50
77
  //]]>
51
- </script>
52
- <style>
53
- %<selector>s, %<selector>s::before, %<selector>s::after {
54
- transition: none !important;
55
- animation-duration: 0s !important;
56
- animation-delay: 0s !important;
57
- scroll-behavior: auto !important;
58
- }
59
- </style>
60
- HTML
78
+ SCRIPT
61
79
  end
62
80
  end
63
81
  end
@@ -100,7 +100,7 @@ module Capybara
100
100
  remove_method :test_id=
101
101
  ##
102
102
  #
103
- # Set an attribue to be optionally matched against the locator for builtin selector types.
103
+ # Set an attribute to be optionally matched against the locator for builtin selector types.
104
104
  # This attribute will be checked by builtin selector types whenever id would normally be checked.
105
105
  # If `nil` then it will be ignored.
106
106
  #
@@ -311,6 +311,16 @@ module Capybara
311
311
  driver.send_keys(*args, **kw_args)
312
312
  end
313
313
 
314
+ ##
315
+ #
316
+ # Returns the element with focus.
317
+ #
318
+ # Not supported by Rack Test
319
+ #
320
+ def active_element
321
+ Capybara::Queries::ActiveElementQuery.new.resolve_for(self)[0].tap(&:allow_reload!)
322
+ end
323
+
314
324
  ##
315
325
  #
316
326
  # Executes the given block within the context of a node. {#within} takes the
@@ -408,11 +418,11 @@ module Capybara
408
418
  idx = scopes.index(:frame)
409
419
  top_level_scopes = [:frame, nil]
410
420
  if idx
411
- if scopes.slice(idx..-1).any? { |scope| !top_level_scopes.include?(scope) }
421
+ if scopes.slice(idx..).any? { |scope| !top_level_scopes.include?(scope) }
412
422
  raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
413
423
  '`within` block.'
414
424
  end
415
- scopes.slice!(idx..-1)
425
+ scopes.slice!(idx..)
416
426
  driver.switch_to_frame(:top)
417
427
  end
418
428
  else
@@ -755,33 +765,20 @@ module Capybara
755
765
  end
756
766
 
757
767
  NODE_METHODS.each do |method|
758
- if RUBY_VERSION >= '2.7'
759
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
760
- def #{method}(...)
761
- @touched = true
762
- current_scope.#{method}(...)
763
- end
764
- METHOD
765
- else
766
- define_method method do |*args, &block|
768
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
769
+ def #{method}(...)
767
770
  @touched = true
768
- current_scope.send(method, *args, &block)
771
+ current_scope.#{method}(...)
769
772
  end
770
- end
773
+ METHOD
771
774
  end
772
775
 
773
776
  DOCUMENT_METHODS.each do |method|
774
- if RUBY_VERSION >= '2.7'
775
- class_eval <<~METHOD, __FILE__, __LINE__ + 1
776
- def #{method}(...)
777
- document.#{method}(...)
778
- end
779
- METHOD
780
- else
781
- define_method method do |*args, &block|
782
- document.send(method, *args, &block)
777
+ class_eval <<~METHOD, __FILE__, __LINE__ + 1
778
+ def #{method}(...)
779
+ document.#{method}(...)
783
780
  end
784
- end
781
+ METHOD
785
782
  end
786
783
 
787
784
  def inspect
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ Capybara::SpecHelper.spec '#active_element', requires: [:active_element] do
4
+ it 'should return the active element' do
5
+ @session.visit('/form')
6
+ @session.send_keys(:tab)
7
+
8
+ expect(@session.active_element).to match_selector(:css, '[tabindex="1"]')
9
+
10
+ @session.send_keys(:tab)
11
+
12
+ expect(@session.active_element).to match_selector(:css, '[tabindex="2"]')
13
+ end
14
+
15
+ it 'should support reloading' do
16
+ @session.visit('/form')
17
+ expect(@session.active_element).to match_selector(:css, 'body')
18
+ @session.execute_script <<-JS
19
+ window.setTimeout(() => {
20
+ document.querySelector('#form_title').focus();
21
+ }, 1000)
22
+ JS
23
+ expect(@session.active_element).to match_selector(:css, 'body', wait: false)
24
+ expect(@session.active_element).to match_selector(:css, '#form_title', wait: 2)
25
+ end
26
+
27
+ it 'should return a Capybara::Element' do
28
+ @session.visit('/form')
29
+ expect(@session.active_element).to be_a Capybara::Node::Element
30
+ end
31
+ end
@@ -132,7 +132,7 @@ Capybara::SpecHelper.spec '#all' do
132
132
  it 'should use the sessions ignore_hidden_elements', psc: true do
133
133
  Capybara.ignore_hidden_elements = true
134
134
  @session.config.ignore_hidden_elements = false
135
- expect(Capybara.ignore_hidden_elements).to eq(true)
135
+ expect(Capybara.ignore_hidden_elements).to be(true)
136
136
  expect(@session.all(:css, 'a.simple').size).to eq(2)
137
137
  @session.config.ignore_hidden_elements = true
138
138
  expect(@session.all(:css, 'a.simple').size).to eq(1)
@@ -203,19 +203,15 @@ Capybara::SpecHelper.spec '#all' do
203
203
  expect { @session.all(:css, 'h1, p', between: 0..3) }.to raise_error(Capybara::ExpectationNotMet)
204
204
  end
205
205
 
206
- eval <<~TEST, binding, __FILE__, __LINE__ + 1 if RUBY_VERSION.to_f > 2.5
207
- it'treats an endless range as minimum' do
208
- expect { @session.all(:css, 'h1, p', between: 2..) }.not_to raise_error
209
- expect { @session.all(:css, 'h1, p', between: 5..) }.to raise_error(Capybara::ExpectationNotMet)
210
- end
211
- TEST
206
+ it 'treats an endless range as minimum' do
207
+ expect { @session.all(:css, 'h1, p', between: 2..) }.not_to raise_error
208
+ expect { @session.all(:css, 'h1, p', between: 5..) }.to raise_error(Capybara::ExpectationNotMet)
209
+ end
212
210
 
213
- eval <<~TEST, binding, __FILE__, __LINE__ + 1 if RUBY_VERSION.to_f > 2.6
214
- it'treats a beginless range as maximum' do
215
- expect { @session.all(:css, 'h1, p', between: ..7) }.not_to raise_error
216
- expect { @session.all(:css, 'h1, p', between: ..3) }.to raise_error(Capybara::ExpectationNotMet)
217
- end
218
- TEST
211
+ it 'treats a beginless range as maximum' do
212
+ expect { @session.all(:css, 'h1, p', between: ..7) }.not_to raise_error
213
+ expect { @session.all(:css, 'h1, p', between: ..3) }.to raise_error(Capybara::ExpectationNotMet)
214
+ end
219
215
  end
220
216
 
221
217
  context 'with multiple count filters' do
@@ -3,16 +3,16 @@
3
3
  Capybara::SpecHelper.spec '#assert_text' do
4
4
  it 'should be true if the given text is on the page' do
5
5
  @session.visit('/with_html')
6
- expect(@session.assert_text('est')).to eq(true)
7
- expect(@session.assert_text('Lorem')).to eq(true)
8
- expect(@session.assert_text('Redirect')).to eq(true)
9
- expect(@session.assert_text(:Redirect)).to eq(true)
10
- expect(@session.assert_text('text with whitespace')).to eq(true)
6
+ expect(@session.assert_text('est')).to be(true)
7
+ expect(@session.assert_text('Lorem')).to be(true)
8
+ expect(@session.assert_text('Redirect')).to be(true)
9
+ expect(@session.assert_text(:Redirect)).to be(true)
10
+ expect(@session.assert_text('text with whitespace')).to be(true)
11
11
  end
12
12
 
13
13
  it 'should support collapsing whitespace' do
14
14
  @session.visit('/with_html')
15
- expect(@session.assert_text('text with whitespace', normalize_ws: true)).to eq(true)
15
+ expect(@session.assert_text('text with whitespace', normalize_ws: true)).to be(true)
16
16
  end
17
17
 
18
18
  context 'with enabled default collapsing whitespace' do
@@ -20,19 +20,19 @@ Capybara::SpecHelper.spec '#assert_text' do
20
20
 
21
21
  it 'should be true if the given unnormalized text is on the page' do
22
22
  @session.visit('/with_html')
23
- expect(@session.assert_text('text with whitespace', normalize_ws: false)).to eq(true)
23
+ expect(@session.assert_text('text with whitespace', normalize_ws: false)).to be(true)
24
24
  end
25
25
 
26
26
  it 'should support collapsing whitespace' do
27
27
  @session.visit('/with_html')
28
- expect(@session.assert_text('text with whitespace')).to eq(true)
28
+ expect(@session.assert_text('text with whitespace')).to be(true)
29
29
  end
30
30
  end
31
31
 
32
32
  it 'should take scopes into account' do
33
33
  @session.visit('/with_html')
34
34
  @session.within("//a[@title='awesome title']") do
35
- expect(@session.assert_text('labore')).to eq(true)
35
+ expect(@session.assert_text('labore')).to be(true)
36
36
  end
37
37
  end
38
38
 
@@ -47,13 +47,13 @@ Capybara::SpecHelper.spec '#assert_text' do
47
47
 
48
48
  it 'should be true if :all given and text is invisible.' do
49
49
  @session.visit('/with_html')
50
- expect(@session.assert_text(:all, 'Some of this text is hidden!')).to eq(true)
50
+ expect(@session.assert_text(:all, 'Some of this text is hidden!')).to be(true)
51
51
  end
52
52
 
53
53
  it 'should be true if `Capybara.ignore_hidden_elements = true` and text is invisible.' do
54
54
  Capybara.ignore_hidden_elements = false
55
55
  @session.visit('/with_html')
56
- expect(@session.assert_text('Some of this text is hidden!')).to eq(true)
56
+ expect(@session.assert_text('Some of this text is hidden!')).to be(true)
57
57
  end
58
58
 
59
59
  it 'should raise error with a helpful message if the requested text is present but invisible' do
@@ -88,7 +88,7 @@ Capybara::SpecHelper.spec '#assert_text' do
88
88
 
89
89
  it 'should be true if the text in the page matches given regexp' do
90
90
  @session.visit('/with_html')
91
- expect(@session.assert_text(/Lorem/)).to eq(true)
91
+ expect(@session.assert_text(/Lorem/)).to be(true)
92
92
  end
93
93
 
94
94
  it "should raise error if the text in the page doesn't match given regexp" do
@@ -109,13 +109,13 @@ Capybara::SpecHelper.spec '#assert_text' do
109
109
  Capybara.default_max_wait_time = 2
110
110
  @session.visit('/with_js')
111
111
  @session.click_link('Click me')
112
- expect(@session.assert_text('Has been clicked')).to eq(true)
112
+ expect(@session.assert_text('Has been clicked')).to be(true)
113
113
  end
114
114
 
115
115
  context 'with between' do
116
116
  it 'should be true if the text occurs within the range given' do
117
117
  @session.visit('/with_count')
118
- expect(@session.assert_text('count', between: 1..3)).to eq(true)
118
+ expect(@session.assert_text('count', between: 1..3)).to be(true)
119
119
  end
120
120
 
121
121
  it 'should be false if the text occurs more or fewer times than range' do
@@ -203,13 +203,13 @@ Capybara::SpecHelper.spec '#assert_no_text' do
203
203
  it 'should be true if scoped to an element which does not have the text' do
204
204
  @session.visit('/with_html')
205
205
  @session.within("//a[@title='awesome title']") do
206
- expect(@session.assert_no_text('monkey')).to eq(true)
206
+ expect(@session.assert_no_text('monkey')).to be(true)
207
207
  end
208
208
  end
209
209
 
210
210
  it 'should be true if the given text is on the page but not visible' do
211
211
  @session.visit('/with_html')
212
- expect(@session.assert_no_text('Inside element with hidden ancestor')).to eq(true)
212
+ expect(@session.assert_no_text('Inside element with hidden ancestor')).to be(true)
213
213
  end
214
214
 
215
215
  it 'should raise error if :all given and text is invisible.' do
@@ -236,7 +236,7 @@ Capybara::SpecHelper.spec '#assert_no_text' do
236
236
  context 'with count' do
237
237
  it 'should be true if the text occurs within the range given' do
238
238
  @session.visit('/with_count')
239
- expect(@session.assert_text('count', count: 2)).to eq(true)
239
+ expect(@session.assert_text('count', count: 2)).to be(true)
240
240
  end
241
241
 
242
242
  it 'should be false if the text occurs more or fewer times than range' do
@@ -236,6 +236,15 @@ Capybara::SpecHelper.spec '#check' do
236
236
  expect(@session).to have_field('multi_label_checkbox', checked: true, visible: :hidden)
237
237
  end
238
238
  end
239
+
240
+ context 'with allow_label_click options', requires: [:js] do
241
+ it 'should allow offsets to click location on label' do
242
+ expect(@session.find(:checkbox, 'form_cars_lotus', unchecked: true, visible: :hidden)).to be_truthy
243
+ @session.check('form_cars_lotus', allow_label_click: { x: 90, y: 10 })
244
+ @session.click_button('awesome')
245
+ expect(extract_results(@session)['cars']).to include('lotus')
246
+ end
247
+ end
239
248
  end
240
249
  end
241
250
  end
@@ -11,6 +11,12 @@ Capybara::SpecHelper.spec '#choose' do
11
11
  expect(extract_results(@session)['gender']).to eq('male')
12
12
  end
13
13
 
14
+ it 'ignores readonly attribute on radio buttons' do
15
+ @session.choose('gender_both')
16
+ @session.click_button('awesome')
17
+ expect(extract_results(@session)['gender']).to eq('both')
18
+ end
19
+
14
20
  it 'should choose a radio button by label' do
15
21
  @session.choose('Both')
16
22
  @session.click_button('awesome')
@@ -22,4 +22,8 @@ Capybara::SpecHelper.spec '#have_any_of_selectors' do
22
22
  expect(@session).to have_any_of_selectors('p a#blah', 'h2#h2three')
23
23
  end.to raise_error ::RSpec::Expectations::ExpectationNotMetError
24
24
  end
25
+
26
+ it 'should be negateable' do
27
+ expect(@session).not_to have_any_of_selectors(:css, 'span a#foo', 'h2#h2nope', 'h2#h2one_no')
28
+ end
25
29
  end
@@ -65,6 +65,18 @@ Capybara::SpecHelper.spec '#has_button?' do
65
65
  it 'should not affect other selectors when enable_aria_role: false' do
66
66
  expect(@session).to have_button('Click me!', enable_aria_role: false)
67
67
  end
68
+
69
+ context 'with focused:', requires: [:active_element] do
70
+ it 'should be true if a field has focus when focused: true' do
71
+ @session.send_keys(:tab)
72
+
73
+ expect(@session).to have_button('A Button', focused: true)
74
+ end
75
+
76
+ it 'should be true if a field does not have focus when focused: false' do
77
+ expect(@session).to have_button('A Button', focused: false)
78
+ end
79
+ end
68
80
  end
69
81
 
70
82
  Capybara::SpecHelper.spec '#has_no_button?' do
@@ -117,4 +129,16 @@ Capybara::SpecHelper.spec '#has_no_button?' do
117
129
  it 'should not affect other selectors when enable_aria_role: false' do
118
130
  expect(@session).to have_no_button('Junk button that does not exist', enable_aria_role: false)
119
131
  end
132
+
133
+ context 'with focused:', requires: [:active_element] do
134
+ it 'should be true if a button does not have focus when focused: true' do
135
+ expect(@session).to have_no_button('A Button', focused: true)
136
+ end
137
+
138
+ it 'should be false if a button has focus when focused: false' do
139
+ @session.send_keys(:tab)
140
+
141
+ expect(@session).to have_no_button('A Button', focused: false)
142
+ end
143
+ end
120
144
  end
@@ -16,8 +16,8 @@ Capybara::SpecHelper.spec '#has_current_path?' do
16
16
 
17
17
  it 'should not raise an error when non-http' do
18
18
  @session.reset_session!
19
- expect(@session.has_current_path?(/monkey/)).to eq false
20
- expect(@session.has_current_path?('/with_js')).to eq false
19
+ expect(@session.has_current_path?(/monkey/)).to be false
20
+ expect(@session.has_current_path?('/with_js')).to be false
21
21
  end
22
22
 
23
23
  it 'should handle non-escaped query options' do
@@ -110,6 +110,18 @@ Capybara::SpecHelper.spec '#has_field' do
110
110
  end
111
111
  end
112
112
 
113
+ context 'with focused:', requires: [:active_element] do
114
+ it 'should be true if a field has focus' do
115
+ 2.times { @session.send_keys(:tab) }
116
+
117
+ expect(@session).to have_field('An Input', focused: true)
118
+ end
119
+
120
+ it 'should be false if a field does not have focus' do
121
+ expect(@session).to have_field('An Input', focused: false)
122
+ end
123
+ end
124
+
113
125
  context 'with valid', requires: [:js] do
114
126
  it 'should be true if field is valid' do
115
127
  @session.fill_in 'required', with: 'something'
@@ -184,6 +196,18 @@ Capybara::SpecHelper.spec '#has_no_field' do
184
196
  expect(@session).to have_no_field('Languages', type: 'textarea')
185
197
  end
186
198
  end
199
+
200
+ context 'with focused:', requires: [:active_element] do
201
+ it 'should be true if a field does not have focus when focused: true' do
202
+ expect(@session).to have_no_field('An Input', focused: true)
203
+ end
204
+
205
+ it 'should be false if a field has focus when focused: true' do
206
+ 2.times { @session.send_keys(:tab) }
207
+
208
+ expect(@session).not_to have_no_field('An Input', focused: true)
209
+ end
210
+ end
187
211
  end
188
212
 
189
213
  Capybara::SpecHelper.spec '#has_checked_field?' do
@@ -359,7 +383,7 @@ Capybara::SpecHelper.spec '#has_no_unchecked_field?' do
359
383
  end
360
384
 
361
385
  it 'should support locator-less usage' do
362
- expect(@session.has_no_unchecked_field?(disabled: false, id: 'form_disabled_unchecked_checkbox')).to eq true
386
+ expect(@session.has_no_unchecked_field?(disabled: false, id: 'form_disabled_unchecked_checkbox')).to be true
363
387
  expect(@session).to have_no_unchecked_field(disabled: false, id: 'form_disabled_unchecked_checkbox')
364
388
  end
365
389
  end
@@ -18,6 +18,18 @@ Capybara::SpecHelper.spec '#has_link?' do
18
18
  expect(@session).not_to have_link('A link', href: '/nonexistent-href')
19
19
  expect(@session).not_to have_link('A link', href: /nonexistent/)
20
20
  end
21
+
22
+ context 'with focused:', requires: [:active_element] do
23
+ it 'should be true if the given link is on the page and has focus' do
24
+ @session.send_keys(:tab)
25
+
26
+ expect(@session).to have_link('labore', focused: true)
27
+ end
28
+
29
+ it 'should be false if the given link is on the page and does not have focus' do
30
+ expect(@session).to have_link('labore', focused: false)
31
+ end
32
+ end
21
33
  end
22
34
 
23
35
  Capybara::SpecHelper.spec '#has_no_link?' do
@@ -36,4 +48,16 @@ Capybara::SpecHelper.spec '#has_no_link?' do
36
48
  expect(@session).to have_no_link('A link', href: '/nonexistent-href')
37
49
  expect(@session).to have_no_link('A link', href: %r{/nonexistent-href})
38
50
  end
51
+
52
+ context 'with focused:', requires: [:active_element] do
53
+ it 'should be true if the given link is on the page and has focus' do
54
+ expect(@session).to have_no_link('labore', focused: true)
55
+ end
56
+
57
+ it 'should be false if the given link is on the page and does not have focus' do
58
+ @session.send_keys(:tab)
59
+
60
+ expect(@session).to have_no_link('labore', focused: false)
61
+ end
62
+ end
39
63
  end
@@ -180,9 +180,9 @@ Capybara::SpecHelper.spec '#has_select?' do
180
180
  end
181
181
 
182
182
  it 'should support locator-less usage' do
183
- expect(@session.has_select?(with_options: %w[Norway Sweden])).to eq true
183
+ expect(@session.has_select?(with_options: %w[Norway Sweden])).to be true
184
184
  expect(@session).to have_select(with_options: ['London'])
185
- expect(@session.has_select?(with_selected: %w[Commando Boxerbriefs])).to eq true
185
+ expect(@session.has_select?(with_selected: %w[Commando Boxerbriefs])).to be true
186
186
  expect(@session).to have_select(with_selected: ['Briefs'])
187
187
  end
188
188
  end
@@ -302,9 +302,9 @@ Capybara::SpecHelper.spec '#has_no_select?' do
302
302
  end
303
303
 
304
304
  it 'should support locator-less usage' do
305
- expect(@session.has_no_select?(with_options: %w[Norway Sweden Finland Latvia])).to eq true
305
+ expect(@session.has_no_select?(with_options: %w[Norway Sweden Finland Latvia])).to be true
306
306
  expect(@session).to have_no_select(with_options: ['New London'])
307
- expect(@session.has_no_select?(id: 'form_underwear', with_selected: ['Boxers'])).to eq true
307
+ expect(@session.has_no_select?(id: 'form_underwear', with_selected: ['Boxers'])).to be true
308
308
  expect(@session).to have_no_select(id: 'form_underwear', with_selected: %w[Commando Boxers])
309
309
  end
310
310
  end
@@ -117,6 +117,21 @@ Capybara::SpecHelper.spec '#has_selector?' do
117
117
  expect(@session).to have_selector(:id, 'h2one', text: 'Header Class Test One', exact_text: false)
118
118
  expect(@session).to have_selector(:id, 'h2one', text: 'Header Class Test', exact_text: false)
119
119
  end
120
+
121
+ it 'should warn if text option is a regexp that it is ignoring exact_text' do
122
+ allow(Capybara::Helpers).to receive(:warn)
123
+ expect(@session).to have_selector(:id, 'h2one', text: /Class Test/, exact_text: true)
124
+ expect(Capybara::Helpers).to have_received(:warn).with(/'exact_text' option is not supported/)
125
+ end
126
+ end
127
+
128
+ context 'regexp' do
129
+ it 'should only match when it fully matches' do
130
+ expect(@session).to have_selector(:id, 'h2one', exact_text: /Header Class Test One/)
131
+ expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Header Class Test/)
132
+ expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Class Test One/)
133
+ expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Class Test/)
134
+ end
120
135
  end
121
136
  end
122
137
 
@@ -127,11 +127,7 @@ Capybara::SpecHelper.spec '#has_text?' do
127
127
  def to_hash; { value: 'Other hash' } end
128
128
  end.new
129
129
  @session.visit('/with_html')
130
- if RUBY_VERSION >= '2.7'
131
- expect(@session).to have_text(:visible, with_to_hash, **{})
132
- else
133
- expect(@session).to have_text(:visible, with_to_hash, {})
134
- end
130
+ expect(@session).to have_text(:visible, with_to_hash, **{})
135
131
  end
136
132
  end
137
133
 
@@ -356,7 +352,7 @@ Capybara::SpecHelper.spec '#has_no_text?' do
356
352
  expect(@session).to have_no_text(/xxxxyzzz/)
357
353
  end
358
354
 
359
- it 'should be false if the text in the page matches given regexp' do
355
+ it 'should be false if the text in the page matches given regexp' do
360
356
  @session.visit('/with_html')
361
357
  expect(@session).not_to have_no_text(/Lorem/)
362
358
  end
@@ -1115,7 +1115,7 @@ Capybara::SpecHelper.spec 'node' do
1115
1115
  end
1116
1116
 
1117
1117
  describe '#evaluate_script', requires: %i[js es_args] do
1118
- it 'should evaluate the given script in the context of the element and return whatever it produces' do
1118
+ it 'should evaluate the given script in the context of the element and return whatever it produces' do
1119
1119
  @session.visit('/with_js')
1120
1120
  el = @session.find(:css, '#with_change_event')
1121
1121
  expect(el.evaluate_script('this.value')).to eq('default value')
@@ -1176,6 +1176,48 @@ Capybara::SpecHelper.spec 'node' do
1176
1176
  end
1177
1177
  end
1178
1178
 
1179
+ describe '#shadow_root', requires: %i[js] do
1180
+ it 'should get the shadow root' do
1181
+ @session.visit('/with_shadow')
1182
+ expect do
1183
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1184
+ expect(shadow_root).not_to be_nil
1185
+ end.not_to raise_error
1186
+ end
1187
+
1188
+ it 'should find elements inside the shadow dom using CSS' do
1189
+ @session.visit('/with_shadow')
1190
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1191
+ expect(shadow_root).to have_css('#shadow_content', text: 'some text')
1192
+ end
1193
+
1194
+ it 'should find nested shadow roots' do
1195
+ @session.visit('/with_shadow')
1196
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1197
+ nested_shadow_root = shadow_root.find(:css, '#nested_shadow_host').shadow_root
1198
+ expect(nested_shadow_root).to have_css('#nested_shadow_content', text: 'nested text')
1199
+ end
1200
+
1201
+ it 'should click on elements' do
1202
+ @session.visit('/with_shadow')
1203
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1204
+ checkbox = shadow_root.find(:css, 'input[type="checkbox"]')
1205
+ expect(checkbox).not_to be_checked
1206
+ checkbox.click
1207
+ expect(checkbox).to be_checked
1208
+ end
1209
+
1210
+ it 'should use convenience methods once moved to a descendant of the shadow root' do
1211
+ @session.visit('/with_shadow')
1212
+ shadow_root = @session.find(:css, '#shadow_host').shadow_root
1213
+ descendant = shadow_root.find(:css, '#controls_wrapper')
1214
+ expect do
1215
+ descendant.check('shadow_checkbox')
1216
+ end.not_to raise_error
1217
+ expect(descendant).to have_checked_field('shadow_checkbox')
1218
+ end
1219
+ end
1220
+
1179
1221
  describe '#reload', requires: [:js] do
1180
1222
  it 'should reload elements found via ancestor with CSS' do
1181
1223
  @session.visit('/with_js')