capybara 3.23.0 → 3.35.3

Sign up to get free protection for your applications and to get access to all the features.
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