capybara 3.23.0 → 3.35.3

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.
Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +264 -11
  3. data/README.md +10 -6
  4. data/lib/capybara.rb +20 -8
  5. data/lib/capybara/config.rb +10 -8
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +4 -0
  8. data/lib/capybara/driver/node.rb +4 -0
  9. data/lib/capybara/dsl.rb +10 -2
  10. data/lib/capybara/helpers.rb +28 -2
  11. data/lib/capybara/minitest.rb +232 -144
  12. data/lib/capybara/minitest/spec.rb +156 -97
  13. data/lib/capybara/node/actions.rb +36 -36
  14. data/lib/capybara/node/base.rb +6 -6
  15. data/lib/capybara/node/document.rb +2 -2
  16. data/lib/capybara/node/document_matchers.rb +3 -3
  17. data/lib/capybara/node/element.rb +77 -33
  18. data/lib/capybara/node/finders.rb +24 -17
  19. data/lib/capybara/node/matchers.rb +79 -64
  20. data/lib/capybara/node/simple.rb +11 -4
  21. data/lib/capybara/queries/ancestor_query.rb +6 -10
  22. data/lib/capybara/queries/base_query.rb +2 -1
  23. data/lib/capybara/queries/current_path_query.rb +14 -4
  24. data/lib/capybara/queries/selector_query.rb +259 -23
  25. data/lib/capybara/queries/sibling_query.rb +5 -11
  26. data/lib/capybara/queries/style_query.rb +1 -1
  27. data/lib/capybara/queries/text_query.rb +13 -1
  28. data/lib/capybara/rack_test/browser.rb +13 -4
  29. data/lib/capybara/rack_test/driver.rb +2 -1
  30. data/lib/capybara/rack_test/form.rb +2 -2
  31. data/lib/capybara/rack_test/node.rb +42 -6
  32. data/lib/capybara/registration_container.rb +44 -0
  33. data/lib/capybara/registrations/drivers.rb +18 -12
  34. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  35. data/lib/capybara/registrations/servers.rb +9 -2
  36. data/lib/capybara/result.rb +39 -19
  37. data/lib/capybara/rspec.rb +2 -0
  38. data/lib/capybara/rspec/matcher_proxies.rb +5 -5
  39. data/lib/capybara/rspec/matchers.rb +97 -74
  40. data/lib/capybara/rspec/matchers/base.rb +19 -6
  41. data/lib/capybara/rspec/matchers/count_sugar.rb +2 -1
  42. data/lib/capybara/rspec/matchers/have_ancestor.rb +5 -7
  43. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  44. data/lib/capybara/rspec/matchers/have_selector.rb +15 -10
  45. data/lib/capybara/rspec/matchers/have_sibling.rb +4 -7
  46. data/lib/capybara/rspec/matchers/have_text.rb +4 -7
  47. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  48. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  49. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  50. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  51. data/lib/capybara/selector.rb +46 -19
  52. data/lib/capybara/selector/builders/css_builder.rb +10 -6
  53. data/lib/capybara/selector/builders/xpath_builder.rb +4 -2
  54. data/lib/capybara/selector/css.rb +1 -1
  55. data/lib/capybara/selector/definition.rb +13 -11
  56. data/lib/capybara/selector/definition/button.rb +32 -15
  57. data/lib/capybara/selector/definition/checkbox.rb +2 -2
  58. data/lib/capybara/selector/definition/css.rb +3 -1
  59. data/lib/capybara/selector/definition/datalist_input.rb +2 -2
  60. data/lib/capybara/selector/definition/datalist_option.rb +1 -1
  61. data/lib/capybara/selector/definition/element.rb +3 -2
  62. data/lib/capybara/selector/definition/field.rb +1 -1
  63. data/lib/capybara/selector/definition/file_field.rb +1 -1
  64. data/lib/capybara/selector/definition/fillable_field.rb +2 -2
  65. data/lib/capybara/selector/definition/label.rb +5 -3
  66. data/lib/capybara/selector/definition/link.rb +8 -0
  67. data/lib/capybara/selector/definition/option.rb +1 -1
  68. data/lib/capybara/selector/definition/radio_button.rb +2 -2
  69. data/lib/capybara/selector/definition/select.rb +33 -14
  70. data/lib/capybara/selector/definition/table.rb +6 -3
  71. data/lib/capybara/selector/definition/table_row.rb +2 -2
  72. data/lib/capybara/selector/filter_set.rb +13 -11
  73. data/lib/capybara/selector/filters/base.rb +6 -1
  74. data/lib/capybara/selector/filters/locator_filter.rb +1 -1
  75. data/lib/capybara/selector/regexp_disassembler.rb +7 -0
  76. data/lib/capybara/selector/selector.rb +13 -3
  77. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -1
  78. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -1
  79. data/lib/capybara/selenium/atoms/src/getAttribute.js +1 -1
  80. data/lib/capybara/selenium/atoms/src/isDisplayed.js +10 -10
  81. data/lib/capybara/selenium/driver.rb +86 -24
  82. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +24 -21
  83. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +21 -19
  84. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +17 -1
  85. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +0 -4
  86. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  87. data/lib/capybara/selenium/extensions/find.rb +37 -26
  88. data/lib/capybara/selenium/extensions/html5_drag.rb +55 -11
  89. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  90. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  91. data/lib/capybara/selenium/logger_suppressor.rb +8 -2
  92. data/lib/capybara/selenium/node.rb +160 -40
  93. data/lib/capybara/selenium/nodes/chrome_node.rb +72 -12
  94. data/lib/capybara/selenium/nodes/edge_node.rb +32 -14
  95. data/lib/capybara/selenium/nodes/firefox_node.rb +28 -32
  96. data/lib/capybara/selenium/nodes/safari_node.rb +5 -29
  97. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  98. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  99. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  100. data/lib/capybara/selenium/patches/logs.rb +32 -7
  101. data/lib/capybara/server.rb +19 -3
  102. data/lib/capybara/server/animation_disabler.rb +8 -3
  103. data/lib/capybara/server/checker.rb +1 -1
  104. data/lib/capybara/server/middleware.rb +22 -10
  105. data/lib/capybara/session.rb +66 -40
  106. data/lib/capybara/session/config.rb +11 -3
  107. data/lib/capybara/session/matchers.rb +11 -11
  108. data/lib/capybara/spec/public/offset.js +6 -0
  109. data/lib/capybara/spec/public/test.js +75 -7
  110. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  111. data/lib/capybara/spec/session/all_spec.rb +60 -5
  112. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  113. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  114. data/lib/capybara/spec/session/check_spec.rb +6 -0
  115. data/lib/capybara/spec/session/click_button_spec.rb +16 -0
  116. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  117. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  118. data/lib/capybara/spec/session/fill_in_spec.rb +29 -0
  119. data/lib/capybara/spec/session/find_spec.rb +55 -0
  120. data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -0
  121. data/lib/capybara/spec/session/has_button_spec.rb +51 -0
  122. data/lib/capybara/spec/session/has_css_spec.rb +26 -4
  123. data/lib/capybara/spec/session/has_current_path_spec.rb +15 -2
  124. data/lib/capybara/spec/session/has_field_spec.rb +34 -0
  125. data/lib/capybara/spec/session/has_select_spec.rb +32 -4
  126. data/lib/capybara/spec/session/has_selector_spec.rb +4 -4
  127. data/lib/capybara/spec/session/has_table_spec.rb +51 -5
  128. data/lib/capybara/spec/session/has_text_spec.rb +30 -0
  129. data/lib/capybara/spec/session/html_spec.rb +1 -1
  130. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  131. data/lib/capybara/spec/session/node_spec.rb +394 -9
  132. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  133. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  134. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  135. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -15
  136. data/lib/capybara/spec/session/selectors_spec.rb +16 -3
  137. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  138. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  139. data/lib/capybara/spec/session/window/window_spec.rb +8 -8
  140. data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
  141. data/lib/capybara/spec/spec_helper.rb +14 -14
  142. data/lib/capybara/spec/test_app.rb +27 -21
  143. data/lib/capybara/spec/views/form.erb +47 -4
  144. data/lib/capybara/spec/views/offset.erb +32 -0
  145. data/lib/capybara/spec/views/spatial.erb +31 -0
  146. data/lib/capybara/spec/views/with_animation.erb +37 -1
  147. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  148. data/lib/capybara/spec/views/with_html.erb +24 -2
  149. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  150. data/lib/capybara/spec/views/with_js.erb +4 -1
  151. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  152. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  153. data/lib/capybara/version.rb +1 -1
  154. data/lib/capybara/window.rb +3 -7
  155. data/spec/basic_node_spec.rb +15 -14
  156. data/spec/capybara_spec.rb +28 -28
  157. data/spec/dsl_spec.rb +16 -3
  158. data/spec/filter_set_spec.rb +5 -5
  159. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  160. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  161. data/spec/minitest_spec.rb +3 -2
  162. data/spec/minitest_spec_spec.rb +46 -46
  163. data/spec/rack_test_spec.rb +38 -15
  164. data/spec/regexp_dissassembler_spec.rb +52 -38
  165. data/spec/result_spec.rb +43 -32
  166. data/spec/rspec/features_spec.rb +4 -1
  167. data/spec/rspec/scenarios_spec.rb +4 -0
  168. data/spec/rspec/shared_spec_matchers.rb +68 -56
  169. data/spec/rspec_spec.rb +9 -5
  170. data/spec/selector_spec.rb +32 -17
  171. data/spec/selenium_spec_chrome.rb +78 -11
  172. data/spec/selenium_spec_chrome_remote.rb +23 -6
  173. data/spec/selenium_spec_edge.rb +15 -12
  174. data/spec/selenium_spec_firefox.rb +24 -19
  175. data/spec/selenium_spec_firefox_remote.rb +0 -8
  176. data/spec/selenium_spec_ie.rb +1 -6
  177. data/spec/server_spec.rb +106 -44
  178. data/spec/session_spec.rb +5 -5
  179. data/spec/shared_selenium_node.rb +56 -2
  180. data/spec/shared_selenium_session.rb +122 -15
  181. data/spec/spec_helper.rb +2 -2
  182. metadata +63 -17
  183. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -50,12 +50,6 @@ module TestSessions
50
50
  RemoteFirefox = Capybara::Session.new(FIREFOX_REMOTE_DRIVER, TestApp)
51
51
  end
52
52
 
53
- TestSessions::RemoteFirefox.driver.browser.file_detector = lambda do |args|
54
- # args => ["/path/to/file"]
55
- str = args.first.to_s
56
- str if File.exist?(str)
57
- end
58
-
59
53
  skipped_tests = %i[response_headers status_code trigger download]
60
54
 
61
55
  Capybara::SpecHelper.run_specs TestSessions::RemoteFirefox, FIREFOX_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example|
@@ -71,8 +65,6 @@ Capybara::SpecHelper.run_specs TestSessions::RemoteFirefox, FIREFOX_REMOTE_DRIVE
71
65
  when /#accept_confirm should work with nested modals$/
72
66
  # skip because this is timing based and hence flaky when set to pending
73
67
  skip 'Broken in FF 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1487358' if firefox_gte?(63, @session)
74
- when 'Capybara::Session selenium_firefox_remote #attach_file with a block can upload by clicking the file input'
75
- pending "Geckodriver doesn't allow clicking on file inputs"
76
68
  end
77
69
  end
78
70
 
@@ -39,12 +39,7 @@ Capybara.register_driver :selenium_ie do |app|
39
39
  Capybara::Selenium::Driver.new(app,
40
40
  browser: :remote,
41
41
  options: options,
42
- url: url).tap do |driver|
43
- driver.browser.file_detector = lambda do |args|
44
- str = args.first.to_s
45
- str if File.exist?(str)
46
- end
47
- end
42
+ url: url)
48
43
  else
49
44
  Capybara::Selenium::Driver.new(
50
45
  app,
data/spec/server_spec.rb CHANGED
@@ -5,7 +5,7 @@ require 'spec_helper'
5
5
  RSpec.describe Capybara::Server do
6
6
  it 'should spool up a rack server' do
7
7
  app = proc { |_env| [200, {}, ['Hello Server!']] }
8
- server = Capybara::Server.new(app).boot
8
+ server = described_class.new(app).boot
9
9
 
10
10
  res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
11
11
 
@@ -14,7 +14,7 @@ RSpec.describe Capybara::Server do
14
14
 
15
15
  it 'should do nothing when no server given' do
16
16
  expect do
17
- Capybara::Server.new(nil).boot
17
+ described_class.new(nil).boot
18
18
  end.not_to raise_error
19
19
  end
20
20
 
@@ -26,12 +26,12 @@ RSpec.describe Capybara::Server do
26
26
  app = proc { |_env| [200, {}, ['Hello Server!']] }
27
27
 
28
28
  Capybara.server_host = '127.0.0.1'
29
- server = Capybara::Server.new(app).boot
29
+ server = described_class.new(app).boot
30
30
  res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
31
31
  expect(res).to eq('Hello Server!')
32
32
 
33
33
  Capybara.server_host = '0.0.0.0'
34
- server = Capybara::Server.new(app).boot
34
+ server = described_class.new(app).boot
35
35
  res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}"))
36
36
  expect(res).to eq('Hello Server!')
37
37
  ensure
@@ -43,7 +43,7 @@ RSpec.describe Capybara::Server do
43
43
  Capybara.server_port = 22789
44
44
 
45
45
  app = proc { |_env| [200, {}, ['Hello Server!']] }
46
- server = Capybara::Server.new(app).boot
46
+ server = described_class.new(app).boot
47
47
 
48
48
  res = Net::HTTP.start(server.host, 22789) { |http| http.get('/') }
49
49
  expect(res.body).to include('Hello Server')
@@ -53,7 +53,7 @@ RSpec.describe Capybara::Server do
53
53
 
54
54
  it 'should use given port' do
55
55
  app = proc { |_env| [200, {}, ['Hello Server!']] }
56
- server = Capybara::Server.new(app, port: 22790).boot
56
+ server = described_class.new(app, port: 22790).boot
57
57
 
58
58
  res = Net::HTTP.start(server.host, 22790) { |http| http.get('/') }
59
59
  expect(res.body).to include('Hello Server')
@@ -66,7 +66,7 @@ RSpec.describe Capybara::Server do
66
66
  apps = responses.map do |response|
67
67
  proc { |_env| [200, {}, [response]] }
68
68
  end
69
- servers = apps.map { |app| Capybara::Server.new(app).boot }
69
+ servers = apps.map { |app| described_class.new(app).boot }
70
70
 
71
71
  servers.each_with_index do |server, idx|
72
72
  result = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
@@ -74,28 +74,72 @@ RSpec.describe Capybara::Server do
74
74
  end
75
75
  end
76
76
 
77
- it 'should support SSL' do
78
- begin
79
- key = File.join(Dir.pwd, 'spec', 'fixtures', 'key.pem')
80
- cert = File.join(Dir.pwd, 'spec', 'fixtures', 'certificate.pem')
81
- Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}?key=#{key}&cert=#{cert}" }
82
- app = proc { |_env| [200, {}, ['Hello SSL Server!']] }
83
- server = Capybara::Server.new(app).boot
77
+ it 'should handle that getting available ports fails randomly' do
78
+ # Use a port to force a EADDRINUSE error to be generated
79
+ server = TCPServer.new('0.0.0.0', 0)
80
+ server_port = server.addr[1]
81
+ d_server = instance_double('TCPServer', addr: [nil, server_port, nil, nil], close: nil)
82
+ call_count = 0
83
+ allow(TCPServer).to receive(:new).and_wrap_original do |m, *args|
84
+ call_count.zero? ? d_server : m.call(*args)
85
+ ensure
86
+ call_count += 1
87
+ end
84
88
 
85
- expect do
86
- Net::HTTP.start(server.host, server.port, max_retries: 0) { |http| http.get('/__identify__') }
87
- end.to(raise_error do |e|
88
- expect(e.is_a?(EOFError) || e.is_a?(Net::ReadTimeout)).to be true
89
- end)
89
+ port = described_class.new(Object.new, host: '0.0.0.0').port
90
+ expect(port).not_to eq(server_port)
91
+ ensure
92
+ server&.close
93
+ end
90
94
 
91
- res = Net::HTTP.start(server.host, server.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https|
92
- https.get('/')
93
- end
95
+ it 'should return its #base_url' do
96
+ app = proc { |_env| [200, {}, ['Hello Server!']] }
97
+ server = described_class.new(app).boot
98
+ uri = ::Addressable::URI.parse(server.base_url)
99
+ expect(uri.to_hash).to include(scheme: 'http', host: server.host, port: server.port)
100
+ end
94
101
 
95
- expect(res.body).to include('Hello SSL Server!')
96
- ensure
97
- Capybara.server = :default
102
+ it 'should call #clamp on the puma configuration to ensure that environment is a string' do
103
+ Capybara.server = :puma
104
+ app_proc = proc { |_env| [200, {}, ['Hello Puma!']] }
105
+ require 'puma'
106
+ allow(Puma::Server).to receive(:new).and_wrap_original do |method, app, events, options|
107
+ # If #clamp is not called on the puma config then this will be a Proc
108
+ expect(options.fetch(:environment)).to be_a(String)
109
+ method.call(app, events, options)
110
+ end
111
+ server = described_class.new(app_proc).boot
112
+ expect(Puma::Server).to have_received(:new).with(
113
+ anything,
114
+ anything,
115
+ satisfy { |opts| opts.final_options[:Port] == server.port }
116
+ )
117
+ ensure
118
+ Capybara.server = :default
119
+ end
120
+
121
+ it 'should support SSL' do
122
+ key = File.join(Dir.pwd, 'spec', 'fixtures', 'key.pem')
123
+ cert = File.join(Dir.pwd, 'spec', 'fixtures', 'certificate.pem')
124
+ Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}?key=#{key}&cert=#{cert}" }
125
+ app = proc { |_env| [200, {}, ['Hello SSL Server!']] }
126
+ server = described_class.new(app).boot
127
+
128
+ expect do
129
+ Net::HTTP.start(server.host, server.port, max_retries: 0) { |http| http.get('/__identify__') }
130
+ end.to(raise_error do |e|
131
+ expect(e.is_a?(EOFError) || e.is_a?(Net::ReadTimeout)).to be true
132
+ end)
133
+
134
+ res = Net::HTTP.start(server.host, server.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https|
135
+ https.get('/')
98
136
  end
137
+
138
+ expect(res.body).to include('Hello SSL Server!')
139
+ uri = ::Addressable::URI.parse(server.base_url)
140
+ expect(uri.to_hash).to include(scheme: 'https', host: server.host, port: server.port)
141
+ ensure
142
+ Capybara.server = :default
99
143
  end
100
144
 
101
145
  context 'When Capybara.reuse_server is true' do
@@ -112,7 +156,7 @@ RSpec.describe Capybara::Server do
112
156
  it 'should use the existing server if it already running' do
113
157
  app = proc { |_env| [200, {}, ['Hello Server!']] }
114
158
 
115
- servers = Array.new(2) { Capybara::Server.new(app).boot }
159
+ servers = Array.new(2) { described_class.new(app).boot }
116
160
 
117
161
  servers.each do |server|
118
162
  res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
@@ -132,8 +176,8 @@ RSpec.describe Capybara::Server do
132
176
  [200, {}, ['Hello Server!']]
133
177
  end
134
178
 
135
- server1 = Capybara::Server.new(app).boot
136
- server2 = Capybara::Server.new(app).boot
179
+ server1 = described_class.new(app).boot
180
+ server2 = described_class.new(app).boot
137
181
 
138
182
  expect do
139
183
  start_request(server1, 1.0)
@@ -157,7 +201,7 @@ RSpec.describe Capybara::Server do
157
201
  it 'should not reuse an already running server' do
158
202
  app = proc { |_env| [200, {}, ['Hello Server!']] }
159
203
 
160
- servers = Array.new(2) { Capybara::Server.new(app).boot }
204
+ servers = Array.new(2) { described_class.new(app).boot }
161
205
 
162
206
  servers.each do |server|
163
207
  res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') }
@@ -177,8 +221,8 @@ RSpec.describe Capybara::Server do
177
221
  [200, {}, ['Hello Server!']]
178
222
  end
179
223
 
180
- server1 = Capybara::Server.new(app).boot
181
- server2 = Capybara::Server.new(app).boot
224
+ server1 = described_class.new(app).boot
225
+ server2 = described_class.new(app).boot
182
226
 
183
227
  expect do
184
228
  start_request(server1, 1.0)
@@ -193,24 +237,42 @@ RSpec.describe Capybara::Server do
193
237
  end
194
238
 
195
239
  it 'should raise server errors when the server errors before the timeout' do
196
- begin
197
- Capybara.register_server :kaboom do
198
- sleep 0.1
199
- raise 'kaboom'
200
- end
201
- Capybara.server = :kaboom
240
+ Capybara.register_server :kaboom do
241
+ sleep 0.1
242
+ raise 'kaboom'
243
+ end
244
+ Capybara.server = :kaboom
202
245
 
203
- expect do
204
- Capybara::Server.new(proc { |e| }).boot
205
- end.to raise_error(RuntimeError, 'kaboom')
206
- ensure
207
- Capybara.server = :default
246
+ expect do
247
+ described_class.new(proc { |e| }).boot
248
+ end.to raise_error(RuntimeError, 'kaboom')
249
+ ensure
250
+ Capybara.server = :default
251
+ end
252
+
253
+ it 'should raise an error when there are pending requests' do
254
+ app = proc do |env|
255
+ request = Rack::Request.new(env)
256
+ sleep request.params['wait_time'].to_f
257
+ [200, {}, ['Hello Server!']]
208
258
  end
259
+
260
+ server = described_class.new(app).boot
261
+
262
+ expect do
263
+ start_request(server, 59.0)
264
+ server.wait_for_pending_requests
265
+ end.not_to raise_error
266
+
267
+ expect do
268
+ start_request(server, 61.0)
269
+ server.wait_for_pending_requests
270
+ end.to raise_error('Requests did not finish in 60 seconds: ["/?wait_time=61.0"]')
209
271
  end
210
272
 
211
273
  it 'is not #responsive? when Net::HTTP raises a SystemCallError' do
212
274
  app = -> { [200, {}, ['Hello, world']] }
213
- server = Capybara::Server.new(app)
275
+ server = described_class.new(app)
214
276
  allow(Net::HTTP).to receive(:start).and_raise(SystemCallError.allocate)
215
277
  expect(server.responsive?).to eq false
216
278
  end
@@ -225,7 +287,7 @@ RSpec.describe Capybara::Server do
225
287
  response = Net::HTTPSuccess.allocate
226
288
  allow(response).to receive(:body).and_return app.object_id.to_s
227
289
  allow(Net::HTTP).to receive(:start).with(anything, anything, hash_including(use_ssl: true)).and_return(response).once
228
- Capybara::Server.new(app).boot
290
+ described_class.new(app).boot
229
291
  expect(Net::HTTP).to have_received(:start).exactly(3).times
230
292
  end
231
293
  end
data/spec/session_spec.rb CHANGED
@@ -3,16 +3,16 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe Capybara::Session do
6
- context '#new' do
6
+ describe '#new' do
7
7
  it 'should raise an error if passed non-existent driver' do
8
8
  expect do
9
- Capybara::Session.new(:quox, TestApp).driver
9
+ described_class.new(:quox, TestApp).driver
10
10
  end.to raise_error(Capybara::DriverNotFoundError)
11
11
  end
12
12
 
13
13
  it 'verifies a passed app is a rack app' do
14
14
  expect do
15
- Capybara::Session.new(:unknown, random: 'hash')
15
+ described_class.new(:unknown, random: 'hash')
16
16
  end.to raise_error TypeError, 'The second parameter to Session::new should be a rack app if passed.'
17
17
  end
18
18
  end
@@ -75,14 +75,14 @@ RSpec.describe Capybara::Session do
75
75
 
76
76
  context 'quit' do
77
77
  it 'will reset the driver' do
78
- session = Capybara::Session.new(:rack_test, TestApp)
78
+ session = described_class.new(:rack_test, TestApp)
79
79
  driver = session.driver
80
80
  session.quit
81
81
  expect(session.driver).not_to eql driver
82
82
  end
83
83
 
84
84
  it 'resets the document' do
85
- session = Capybara::Session.new(:rack_test, TestApp)
85
+ session = described_class.new(:rack_test, TestApp)
86
86
  document = session.document
87
87
  session.quit
88
88
  expect(session.document.base).not_to eql document.base
@@ -6,7 +6,7 @@ require 'selenium-webdriver'
6
6
  RSpec.shared_examples 'Capybara::Node' do |session, _mode|
7
7
  let(:session) { session }
8
8
 
9
- context '#content_editable?' do
9
+ describe '#content_editable?' do
10
10
  it 'returns true when the element is content editable' do
11
11
  session.visit('/with_js')
12
12
  expect(session.find(:css, '#existing_content_editable').base.content_editable?).to be true
@@ -19,11 +19,65 @@ RSpec.shared_examples 'Capybara::Node' do |session, _mode|
19
19
  end
20
20
  end
21
21
 
22
- context '#send_keys' do
22
+ describe '#send_keys' do
23
23
  it 'should process space' do
24
24
  session.visit('/form')
25
25
  session.find(:css, '#address1_city').send_keys('ocean', [:shift, :space, 'side'])
26
26
  expect(session.find(:css, '#address1_city').value).to eq 'ocean SIDE'
27
27
  end
28
28
  end
29
+
30
+ describe '#[]' do
31
+ it 'should work for spellcheck' do
32
+ session.visit('/with_html')
33
+ expect(session.find('//input[@spellcheck="TRUE"]')[:spellcheck]).to eq('true')
34
+ expect(session.find('//input[@spellcheck="FALSE"]')[:spellcheck]).to eq('false')
35
+ end
36
+ end
37
+
38
+ describe '#set' do
39
+ it 'respects maxlength when using rapid set' do
40
+ session.visit('/form')
41
+ inp = session.find(:css, '#long_length')
42
+ value = (0...50).map { |i| ((i % 26) + 65).chr }.join
43
+ inp.set(value, rapid: true)
44
+ expect(inp.value).to eq value[0...35]
45
+ end
46
+ end
47
+
48
+ describe '#visible?' do
49
+ let(:bridge) do
50
+ session.driver.browser.send(:bridge)
51
+ end
52
+
53
+ around do |example|
54
+ native_displayed = session.driver.options[:native_displayed]
55
+ example.run
56
+ session.driver.options[:native_displayed] = native_displayed
57
+ end
58
+
59
+ before do
60
+ allow(bridge).to receive(:execute_atom).and_call_original
61
+ end
62
+
63
+ it 'will use native displayed if told to' do
64
+ pending "Chromedriver < 76.0.3809.25 doesn't support native displayed in W3C mode" if chrome_lt?(76, session) && (ENV['W3C'] != 'false')
65
+
66
+ session.driver.options[:native_displayed] = true
67
+ session.visit('/form')
68
+ session.find(:css, '#address1_city', visible: true)
69
+
70
+ expect(bridge).not_to have_received(:execute_atom)
71
+ end
72
+
73
+ it "won't use native displayed if told not to" do
74
+ skip 'Non-W3C uses native' if chrome?(session) && (ENV['W3C'] == 'false')
75
+
76
+ session.driver.options[:native_displayed] = false
77
+ session.visit('/form')
78
+ session.find(:css, '#address1_city', visible: true)
79
+
80
+ expect(bridge).to have_received(:execute_atom).with(:isDisplayed, any_args)
81
+ end
82
+ end
29
83
  end
@@ -72,7 +72,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
72
72
  end
73
73
  end
74
74
 
75
- context '#fill_in_with empty string and no options' do
75
+ describe '#fill_in_with empty string and no options' do
76
76
  it 'should trigger change when clearing a field' do
77
77
  pending "safaridriver doesn't trigger change for clear" if safari?(session)
78
78
  session.visit('/with_js')
@@ -83,7 +83,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
83
83
  end
84
84
  end
85
85
 
86
- context '#fill_in with { :clear => :backspace } fill_option', requires: [:js] do
86
+ describe '#fill_in with { :clear => :backspace } fill_option', requires: [:js] do
87
87
  before do
88
88
  # Firefox has an issue with change events if the main window doesn't think it's focused
89
89
  session.execute_script('window.focus()')
@@ -150,7 +150,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
150
150
  end
151
151
  end
152
152
 
153
- context '#fill_in with { clear: :none } fill_options' do
153
+ describe '#fill_in with { clear: :none } fill_options' do
154
154
  it 'should append to content in a field' do
155
155
  pending 'Safari overwrites by default - need to figure out a workaround' if safari?(session)
156
156
 
@@ -160,9 +160,18 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
160
160
  fill_options: { clear: :none })
161
161
  expect(session.find(:fillable_field, 'form_first_name').value).to eq('JohnHarry')
162
162
  end
163
+
164
+ it 'works with rapid fill' do
165
+ pending 'Safari overwrites by default - need to figure out a workaround' if safari?(session)
166
+
167
+ long_string = (0...60).map { |i| ((i % 26) + 65).chr }.join
168
+ session.visit('/form')
169
+ session.fill_in('form_first_name', with: long_string, fill_options: { clear: :none })
170
+ expect(session.find(:fillable_field, 'form_first_name').value).to eq("John#{long_string}")
171
+ end
163
172
  end
164
173
 
165
- context '#fill_in with Date' do
174
+ describe '#fill_in with Date' do
166
175
  before do
167
176
  session.visit('/form')
168
177
  session.find(:css, '#form_date').execute_script <<-JS
@@ -194,7 +203,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
194
203
  end
195
204
  end
196
205
 
197
- context '#fill_in with { clear: Array } fill_options' do
206
+ describe '#fill_in with { clear: Array } fill_options' do
198
207
  it 'should pass the array through to the element' do
199
208
  # this is mainly for use with [[:control, 'a'], :backspace] - however since that is platform dependant I'm testing with something less useful
200
209
  session.visit('/form')
@@ -205,6 +214,26 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
205
214
  end
206
215
  end
207
216
 
217
+ describe '#fill_in with Emoji' do
218
+ it 'sends emojis' do
219
+ session.visit('/form')
220
+ session.fill_in('form_first_name', with: 'a😀cd😴 🛌🏽🇵🇹 e🤾🏽‍♀️f')
221
+ expect(session.find(:fillable_field, 'form_first_name').value).to eq('a😀cd😴 🛌🏽🇵🇹 e🤾🏽‍♀️f')
222
+ end
223
+ end
224
+
225
+ describe '#send_keys' do
226
+ it 'defaults to sending keys to the document.activeElement' do
227
+ session.visit('/form')
228
+
229
+ expect(session.evaluate_script('document.activeElement')).to eq(session.find('//body'))
230
+
231
+ session.send_keys(:tab)
232
+
233
+ expect(session.evaluate_script('document.activeElement')).to eq(session.first(:field))
234
+ end
235
+ end
236
+
208
237
  describe '#path' do
209
238
  it 'returns xpath' do
210
239
  # this is here because it is testing for an XPath that is specific to the algorithm used in the selenium driver
@@ -299,8 +328,9 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
299
328
  describe 'Element#click' do
300
329
  it 'should handle fixed headers/footers' do
301
330
  session.visit('/with_fixed_header_footer')
302
- # session.click_link('Go to root')
303
- session.find(:link, 'Go to root').click
331
+ session.using_wait_time(2) do
332
+ session.find(:link, 'Go to root').click
333
+ end
304
334
  expect(session).to have_current_path('/')
305
335
  end
306
336
  end
@@ -321,6 +351,15 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
321
351
  session.click_button('Upload Multiple')
322
352
  expect(session.body).to include('5 | ') # number of files
323
353
  end
354
+
355
+ it 'can attach a relative file' do
356
+ pending 'Geckdoriver on windows requires alternate file separator which path expansion replaces' if Gem.win_platform? && firefox?(session)
357
+
358
+ session.visit('/form')
359
+ session.attach_file('Single Document', 'spec/fixtures/capybara.csv')
360
+ session.click_button('Upload Single')
361
+ expect(session.body).to include('Content-type: text/csv')
362
+ end
324
363
  end
325
364
 
326
365
  context 'Windows' do
@@ -348,11 +387,66 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
348
387
  expect(@animation_session).to have_no_link('transition me away', wait: 0.5)
349
388
  end
350
389
 
351
- it 'should disable CSS animations' do
390
+ it 'should disable CSS animations (set to 0s)' do
352
391
  @animation_session.visit('with_animation')
392
+ sleep 1
353
393
  @animation_session.click_link('animate me away')
354
394
  expect(@animation_session).to have_no_link('animate me away', wait: 0.5)
355
395
  end
396
+
397
+ it 'should disable CSS animations on pseudo elements (set to 0s)' do
398
+ @animation_session.visit('with_animation')
399
+ sleep 1
400
+ @animation_session.find_link('animate me away').right_click
401
+ expect(@animation_session).to have_content('Animation Ended', wait: 0.1)
402
+ end
403
+
404
+ it 'should scroll the page instantly', requires: [:js] do
405
+ @animation_session.visit('with_animation')
406
+ scroll_y = @animation_session.evaluate_script(<<~JS)
407
+ (function(){
408
+ window.scrollTo(0,500);
409
+ return window.scrollY;
410
+ })()
411
+ JS
412
+ expect(scroll_y).to eq 500
413
+ end
414
+
415
+ it 'should scroll the page instantly without jquery animation', requires: [:js] do
416
+ @animation_session.visit('with_jquery_animation')
417
+ @animation_session.click_link('scroll top 500')
418
+ scroll_y = @animation_session.evaluate_script('window.scrollY')
419
+ expect(scroll_y).to eq 500
420
+ end
421
+ end
422
+
423
+ context 'when set to `false`' do
424
+ before(:context) do # rubocop:disable RSpec/BeforeAfterAll
425
+ skip "Safari doesn't support multiple sessions" if safari?(session)
426
+ # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
427
+ # it doesn't affect any of these tests because the settings are applied per-session
428
+ Capybara.disable_animation = false
429
+ @animation_session = Capybara::Session.new(session.mode, TestApp.new)
430
+ end
431
+
432
+ it 'should scroll the page with a smooth animation', requires: [:js] do
433
+ @animation_session.visit('with_animation')
434
+ scroll_y = @animation_session.evaluate_script(<<~JS)
435
+ (function(){
436
+ window.scrollTo(0,500);
437
+ return window.scrollY;
438
+ })()
439
+ JS
440
+ # measured over 0.5 seconds: 0, 75, 282, 478, 500
441
+ expect(scroll_y).to be < 500
442
+ end
443
+
444
+ it 'should scroll the page with jquery animation', requires: [:js] do
445
+ @animation_session.visit('with_jquery_animation')
446
+ @animation_session.click_link('scroll top 500')
447
+ scroll_y = @animation_session.evaluate_script('window.scrollY')
448
+ expect(scroll_y).to be < 500
449
+ end
356
450
  end
357
451
 
358
452
  context 'if we pass in css that matches elements' do
@@ -366,12 +460,14 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
366
460
 
367
461
  it 'should disable CSS transitions' do
368
462
  @animation_session_with_matching_css.visit('with_animation')
463
+ sleep 1
369
464
  @animation_session_with_matching_css.click_link('transition me away')
370
465
  expect(@animation_session_with_matching_css).to have_no_link('transition me away', wait: 0.5)
371
466
  end
372
467
 
373
468
  it 'should disable CSS animations' do
374
469
  @animation_session_with_matching_css.visit('with_animation')
470
+ sleep 1
375
471
  @animation_session_with_matching_css.click_link('animate me away')
376
472
  expect(@animation_session_with_matching_css).to have_no_link('animate me away', wait: 0.5)
377
473
  end
@@ -388,6 +484,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
388
484
 
389
485
  it 'should not disable CSS transitions' do
390
486
  @animation_session_without_matching_css.visit('with_animation')
487
+ sleep 1
391
488
  @animation_session_without_matching_css.click_link('transition me away')
392
489
  sleep 0.5 # Wait long enough for click to have been processed
393
490
  expect(@animation_session_without_matching_css).to have_link('transition me away', wait: false)
@@ -396,6 +493,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
396
493
 
397
494
  it 'should not disable CSS animations' do
398
495
  @animation_session_without_matching_css.visit('with_animation')
496
+ sleep 1
399
497
  @animation_session_without_matching_css.click_link('animate me away')
400
498
  sleep 0.5 # Wait long enough for click to have been processed
401
499
  expect(@animation_session_without_matching_css).to have_link('animate me away', wait: false)
@@ -409,7 +507,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
409
507
  it 'can find html5 svg elements' do
410
508
  session.visit('with_html5_svg')
411
509
  expect(session).to have_selector(:element, :svg)
412
- expect(session).to have_selector(:element, :rect, visible: true)
510
+ expect(session).to have_selector(:element, :rect, visible: :visible)
413
511
  expect(session).to have_selector(:element, :circle)
414
512
  expect(session).to have_selector(:element, :linearGradient, visible: :all)
415
513
  end
@@ -425,21 +523,30 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
425
523
  context 'controlled components' do
426
524
  it 'can set and clear a text field' do
427
525
  skip "This test doesn't support older browsers" if ie?(session)
428
- # session.visit 'https://reactjs.org/docs/forms.html'
429
- # session.all(:css, 'h2#controlled-components ~ p a', text: 'Try it on CodePen')[0].click
430
- # copied into local view
526
+
431
527
  session.visit 'react'
432
- # Not necessary when accessed locally
433
- # session.within_frame(:css, 'iframe.result-iframe:not([src=""])', wait: 10) do
528
+
434
529
  session.fill_in('Name:', with: 'abc')
435
530
  session.accept_prompt 'A name was submitted: abc' do
436
531
  session.click_button('Submit')
437
532
  end
533
+
438
534
  session.fill_in('Name:', with: '')
439
535
  session.accept_prompt(/A name was submitted: $/) do
440
536
  session.click_button('Submit')
441
537
  end
442
- # end
538
+ end
539
+
540
+ it 'works with rapid fill' do
541
+ skip "This test doesn't support older browsers" if ie?(session)
542
+
543
+ session.visit 'react'
544
+ long_string = (0...60).map { |i| ((i % 26) + 65).chr }.join
545
+
546
+ session.fill_in('Name:', with: long_string)
547
+ session.accept_prompt "A name was submitted: #{long_string}" do
548
+ session.click_button('Submit')
549
+ end
443
550
  end
444
551
  end
445
552
  end