capybara 3.13.2 → 3.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +587 -16
  4. data/README.md +240 -90
  5. data/lib/capybara/config.rb +24 -11
  6. data/lib/capybara/cucumber.rb +1 -1
  7. data/lib/capybara/driver/base.rb +8 -0
  8. data/lib/capybara/driver/node.rb +20 -4
  9. data/lib/capybara/dsl.rb +5 -3
  10. data/lib/capybara/helpers.rb +25 -4
  11. data/lib/capybara/minitest/spec.rb +174 -90
  12. data/lib/capybara/minitest.rb +256 -142
  13. data/lib/capybara/node/actions.rb +123 -77
  14. data/lib/capybara/node/base.rb +20 -12
  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 +223 -117
  18. data/lib/capybara/node/finders.rb +81 -71
  19. data/lib/capybara/node/matchers.rb +271 -134
  20. data/lib/capybara/node/simple.rb +18 -5
  21. data/lib/capybara/node/whitespace_normalizer.rb +81 -0
  22. data/lib/capybara/queries/active_element_query.rb +18 -0
  23. data/lib/capybara/queries/ancestor_query.rb +8 -9
  24. data/lib/capybara/queries/base_query.rb +3 -2
  25. data/lib/capybara/queries/current_path_query.rb +15 -5
  26. data/lib/capybara/queries/selector_query.rb +364 -54
  27. data/lib/capybara/queries/sibling_query.rb +8 -6
  28. data/lib/capybara/queries/style_query.rb +2 -2
  29. data/lib/capybara/queries/text_query.rb +13 -1
  30. data/lib/capybara/queries/title_query.rb +1 -1
  31. data/lib/capybara/rack_test/browser.rb +76 -11
  32. data/lib/capybara/rack_test/driver.rb +10 -5
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +31 -9
  35. data/lib/capybara/rack_test/node.rb +74 -23
  36. data/lib/capybara/registration_container.rb +41 -0
  37. data/lib/capybara/registrations/drivers.rb +42 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +29 -0
  39. data/lib/capybara/registrations/servers.rb +66 -0
  40. data/lib/capybara/result.rb +44 -20
  41. data/lib/capybara/rspec/matcher_proxies.rb +13 -11
  42. data/lib/capybara/rspec/matchers/base.rb +31 -16
  43. data/lib/capybara/rspec/matchers/compound.rb +1 -1
  44. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  45. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  46. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  47. data/lib/capybara/rspec/matchers/have_selector.rb +21 -21
  48. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  49. data/lib/capybara/rspec/matchers/have_text.rb +4 -4
  50. data/lib/capybara/rspec/matchers/have_title.rb +2 -2
  51. data/lib/capybara/rspec/matchers/match_selector.rb +3 -3
  52. data/lib/capybara/rspec/matchers/match_style.rb +7 -2
  53. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  54. data/lib/capybara/rspec/matchers.rb +111 -68
  55. data/lib/capybara/rspec.rb +2 -0
  56. data/lib/capybara/selector/builders/css_builder.rb +11 -7
  57. data/lib/capybara/selector/builders/xpath_builder.rb +5 -3
  58. data/lib/capybara/selector/css.rb +11 -9
  59. data/lib/capybara/selector/definition/button.rb +68 -0
  60. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  61. data/lib/capybara/selector/definition/css.rb +10 -0
  62. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  63. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  64. data/lib/capybara/selector/definition/element.rb +28 -0
  65. data/lib/capybara/selector/definition/field.rb +40 -0
  66. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  67. data/lib/capybara/selector/definition/file_field.rb +13 -0
  68. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  69. data/lib/capybara/selector/definition/frame.rb +17 -0
  70. data/lib/capybara/selector/definition/id.rb +6 -0
  71. data/lib/capybara/selector/definition/label.rb +62 -0
  72. data/lib/capybara/selector/definition/link.rb +55 -0
  73. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  74. data/lib/capybara/selector/definition/option.rb +27 -0
  75. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  76. data/lib/capybara/selector/definition/select.rb +81 -0
  77. data/lib/capybara/selector/definition/table.rb +109 -0
  78. data/lib/capybara/selector/definition/table_row.rb +21 -0
  79. data/lib/capybara/selector/definition/xpath.rb +5 -0
  80. data/lib/capybara/selector/definition.rb +280 -0
  81. data/lib/capybara/selector/filter_set.rb +19 -18
  82. data/lib/capybara/selector/filters/base.rb +11 -2
  83. data/lib/capybara/selector/filters/locator_filter.rb +13 -3
  84. data/lib/capybara/selector/regexp_disassembler.rb +11 -7
  85. data/lib/capybara/selector/selector.rb +50 -440
  86. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  87. data/lib/capybara/selector.rb +473 -482
  88. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  89. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  90. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  91. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  92. data/lib/capybara/selenium/driver.rb +174 -62
  93. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +74 -18
  94. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +128 -0
  95. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +37 -3
  96. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +14 -1
  97. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  98. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  99. data/lib/capybara/selenium/extensions/find.rb +68 -45
  100. data/lib/capybara/selenium/extensions/html5_drag.rb +192 -22
  101. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  102. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  103. data/lib/capybara/selenium/node.rb +268 -72
  104. data/lib/capybara/selenium/nodes/chrome_node.rb +105 -9
  105. data/lib/capybara/selenium/nodes/edge_node.rb +110 -0
  106. data/lib/capybara/selenium/nodes/firefox_node.rb +51 -61
  107. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  108. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  109. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  110. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  111. data/lib/capybara/selenium/patches/logs.rb +45 -0
  112. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
  113. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  114. data/lib/capybara/server/animation_disabler.rb +43 -21
  115. data/lib/capybara/server/checker.rb +6 -2
  116. data/lib/capybara/server/middleware.rb +25 -13
  117. data/lib/capybara/server.rb +20 -4
  118. data/lib/capybara/session/config.rb +15 -11
  119. data/lib/capybara/session/matchers.rb +11 -11
  120. data/lib/capybara/session.rb +162 -131
  121. data/lib/capybara/spec/public/offset.js +6 -0
  122. data/lib/capybara/spec/public/test.js +105 -6
  123. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  124. data/lib/capybara/spec/session/active_element_spec.rb +31 -0
  125. data/lib/capybara/spec/session/all_spec.rb +89 -15
  126. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  127. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  128. data/lib/capybara/spec/session/assert_text_spec.rb +26 -22
  129. data/lib/capybara/spec/session/attach_file_spec.rb +64 -31
  130. data/lib/capybara/spec/session/check_spec.rb +26 -4
  131. data/lib/capybara/spec/session/choose_spec.rb +14 -2
  132. data/lib/capybara/spec/session/click_button_spec.rb +109 -61
  133. data/lib/capybara/spec/session/click_link_or_button_spec.rb +9 -0
  134. data/lib/capybara/spec/session/click_link_spec.rb +23 -1
  135. data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
  136. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  137. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  138. data/lib/capybara/spec/session/evaluate_script_spec.rb +12 -0
  139. data/lib/capybara/spec/session/fill_in_spec.rb +46 -5
  140. data/lib/capybara/spec/session/find_link_spec.rb +10 -0
  141. data/lib/capybara/spec/session/find_spec.rb +80 -7
  142. data/lib/capybara/spec/session/first_spec.rb +2 -2
  143. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +14 -1
  144. data/lib/capybara/spec/session/frame/within_frame_spec.rb +14 -1
  145. data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
  146. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  147. data/lib/capybara/spec/session/has_any_selectors_spec.rb +6 -2
  148. data/lib/capybara/spec/session/has_button_spec.rb +81 -0
  149. data/lib/capybara/spec/session/has_css_spec.rb +45 -8
  150. data/lib/capybara/spec/session/has_current_path_spec.rb +22 -7
  151. data/lib/capybara/spec/session/has_element_spec.rb +47 -0
  152. data/lib/capybara/spec/session/has_field_spec.rb +59 -1
  153. data/lib/capybara/spec/session/has_link_spec.rb +40 -0
  154. data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
  155. data/lib/capybara/spec/session/has_select_spec.rb +42 -8
  156. data/lib/capybara/spec/session/has_selector_spec.rb +19 -4
  157. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  158. data/lib/capybara/spec/session/has_table_spec.rb +177 -0
  159. data/lib/capybara/spec/session/has_text_spec.rb +31 -3
  160. data/lib/capybara/spec/session/html_spec.rb +1 -1
  161. data/lib/capybara/spec/session/matches_style_spec.rb +6 -4
  162. data/lib/capybara/spec/session/node_spec.rb +697 -23
  163. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  164. data/lib/capybara/spec/session/refresh_spec.rb +2 -1
  165. data/lib/capybara/spec/session/reset_session_spec.rb +21 -7
  166. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  167. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  168. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  169. data/lib/capybara/spec/session/scroll_spec.rb +9 -7
  170. data/lib/capybara/spec/session/select_spec.rb +5 -10
  171. data/lib/capybara/spec/session/selectors_spec.rb +24 -3
  172. data/lib/capybara/spec/session/uncheck_spec.rb +3 -3
  173. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  174. data/lib/capybara/spec/session/visit_spec.rb +20 -0
  175. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  176. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +1 -1
  178. data/lib/capybara/spec/session/window/window_spec.rb +54 -57
  179. data/lib/capybara/spec/session/window/windows_spec.rb +2 -2
  180. data/lib/capybara/spec/session/within_spec.rb +36 -0
  181. data/lib/capybara/spec/spec_helper.rb +30 -19
  182. data/lib/capybara/spec/test_app.rb +122 -34
  183. data/lib/capybara/spec/views/animated.erb +49 -0
  184. data/lib/capybara/spec/views/form.erb +86 -8
  185. data/lib/capybara/spec/views/frame_child.erb +3 -2
  186. data/lib/capybara/spec/views/frame_one.erb +2 -1
  187. data/lib/capybara/spec/views/frame_parent.erb +1 -1
  188. data/lib/capybara/spec/views/frame_two.erb +1 -1
  189. data/lib/capybara/spec/views/initial_alert.erb +2 -1
  190. data/lib/capybara/spec/views/layout.erb +10 -0
  191. data/lib/capybara/spec/views/obscured.erb +10 -10
  192. data/lib/capybara/spec/views/offset.erb +33 -0
  193. data/lib/capybara/spec/views/path.erb +2 -2
  194. data/lib/capybara/spec/views/popup_one.erb +1 -1
  195. data/lib/capybara/spec/views/popup_two.erb +1 -1
  196. data/lib/capybara/spec/views/react.erb +45 -0
  197. data/lib/capybara/spec/views/scroll.erb +2 -1
  198. data/lib/capybara/spec/views/spatial.erb +31 -0
  199. data/lib/capybara/spec/views/tables.erb +67 -0
  200. data/lib/capybara/spec/views/with_animation.erb +39 -4
  201. data/lib/capybara/spec/views/with_base_tag.erb +2 -2
  202. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  203. data/lib/capybara/spec/views/with_fixed_header_footer.erb +2 -1
  204. data/lib/capybara/spec/views/with_hover.erb +3 -2
  205. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  206. data/lib/capybara/spec/views/with_html.erb +34 -6
  207. data/lib/capybara/spec/views/with_jquery_animation.erb +24 -0
  208. data/lib/capybara/spec/views/with_js.erb +7 -4
  209. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  210. data/lib/capybara/spec/views/with_namespace.erb +1 -0
  211. data/lib/capybara/spec/views/with_scope.erb +2 -2
  212. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  213. data/lib/capybara/spec/views/with_shadow.erb +31 -0
  214. data/lib/capybara/spec/views/with_slow_unload.erb +2 -1
  215. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  216. data/lib/capybara/spec/views/with_unload_alert.erb +1 -0
  217. data/lib/capybara/spec/views/with_windows.erb +1 -1
  218. data/lib/capybara/spec/views/within_frames.erb +1 -1
  219. data/lib/capybara/version.rb +1 -1
  220. data/lib/capybara/window.rb +14 -18
  221. data/lib/capybara.rb +91 -126
  222. data/spec/basic_node_spec.rb +30 -16
  223. data/spec/capybara_spec.rb +40 -28
  224. data/spec/counter_spec.rb +35 -0
  225. data/spec/css_builder_spec.rb +3 -1
  226. data/spec/css_splitter_spec.rb +1 -1
  227. data/spec/dsl_spec.rb +33 -22
  228. data/spec/filter_set_spec.rb +5 -5
  229. data/spec/fixtures/selenium_driver_rspec_failure.rb +3 -3
  230. data/spec/fixtures/selenium_driver_rspec_success.rb +3 -3
  231. data/spec/minitest_spec.rb +24 -2
  232. data/spec/minitest_spec_spec.rb +60 -45
  233. data/spec/per_session_config_spec.rb +1 -1
  234. data/spec/rack_test_spec.rb +131 -98
  235. data/spec/regexp_dissassembler_spec.rb +53 -39
  236. data/spec/result_spec.rb +68 -66
  237. data/spec/rspec/features_spec.rb +9 -4
  238. data/spec/rspec/scenarios_spec.rb +6 -2
  239. data/spec/rspec/shared_spec_matchers.rb +137 -98
  240. data/spec/rspec_matchers_spec.rb +25 -0
  241. data/spec/rspec_spec.rb +23 -21
  242. data/spec/sauce_spec_chrome.rb +43 -0
  243. data/spec/selector_spec.rb +77 -21
  244. data/spec/selenium_spec_chrome.rb +141 -39
  245. data/spec/selenium_spec_chrome_remote.rb +32 -17
  246. data/spec/selenium_spec_edge.rb +36 -8
  247. data/spec/selenium_spec_firefox.rb +110 -68
  248. data/spec/selenium_spec_firefox_remote.rb +22 -15
  249. data/spec/selenium_spec_ie.rb +29 -22
  250. data/spec/selenium_spec_safari.rb +162 -0
  251. data/spec/server_spec.rb +153 -81
  252. data/spec/session_spec.rb +11 -4
  253. data/spec/shared_selenium_node.rb +79 -0
  254. data/spec/shared_selenium_session.rb +179 -74
  255. data/spec/spec_helper.rb +80 -5
  256. data/spec/whitespace_normalizer_spec.rb +54 -0
  257. data/spec/xpath_builder_spec.rb +3 -1
  258. metadata +218 -30
  259. data/lib/capybara/spec/session/source_spec.rb +0 -0
  260. data/lib/capybara/spec/views/with_title.erb +0 -5
@@ -23,32 +23,35 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
23
23
  it 'freshly reset session should not be touched' do
24
24
  session.instance_variable_set(:@touched, true)
25
25
  session.reset!
26
- expect(session.instance_variable_get(:@touched)).to eq false
26
+ expect(session.instance_variable_get(:@touched)).to be false
27
27
  end
28
28
  end
29
29
 
30
30
  describe 'exit codes' do
31
+ let(:env) { { 'SELENIUM_BROWSER' => session.driver.options[:browser].to_s } }
32
+ let!(:orig_dir) { Dir.getwd }
33
+
31
34
  before do
32
- @current_dir = Dir.getwd
33
35
  Dir.chdir(File.join(File.dirname(__FILE__), '..'))
34
- @env = { 'SELENIUM_BROWSER' => session.driver.options[:browser].to_s }
35
36
  end
36
37
 
37
38
  after do
38
- Dir.chdir(@current_dir)
39
+ Dir.chdir(orig_dir)
39
40
  end
40
41
 
41
42
  it 'should have return code 1 when running selenium_driver_rspec_failure.rb' do
42
43
  skip 'only setup for local non-headless' if headless_or_remote?
44
+ skip 'Not setup for edge' if edge?(session)
43
45
 
44
- system(@env, 'rspec spec/fixtures/selenium_driver_rspec_failure.rb', out: File::NULL, err: File::NULL)
46
+ system(env, 'rspec spec/fixtures/selenium_driver_rspec_failure.rb', out: File::NULL, err: File::NULL)
45
47
  expect($CHILD_STATUS.exitstatus).to eq(1)
46
48
  end
47
49
 
48
50
  it 'should have return code 0 when running selenium_driver_rspec_success.rb' do
49
51
  skip 'only setup for local non-headless' if headless_or_remote?
52
+ skip 'Not setup for edge' if edge?(session)
50
53
 
51
- system(@env, 'rspec spec/fixtures/selenium_driver_rspec_success.rb', out: File::NULL, err: File::NULL)
54
+ system(env, 'rspec spec/fixtures/selenium_driver_rspec_success.rb', out: File::NULL, err: File::NULL)
52
55
  expect($CHILD_STATUS.exitstatus).to eq(0)
53
56
  end
54
57
  end
@@ -69,7 +72,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
69
72
  end
70
73
  end
71
74
 
72
- context '#fill_in_with empty string and no options' do
75
+ describe '#fill_in_with empty string and no options' do
73
76
  it 'should trigger change when clearing a field' do
74
77
  session.visit('/with_js')
75
78
  session.fill_in('with_change_event', with: '')
@@ -79,7 +82,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
79
82
  end
80
83
  end
81
84
 
82
- context '#fill_in with { :clear => :backspace } fill_option', requires: [:js] do
85
+ describe '#fill_in with { :clear => :backspace } fill_option', requires: [:js] do
83
86
  before do
84
87
  # Firefox has an issue with change events if the main window doesn't think it's focused
85
88
  session.execute_script('window.focus()')
@@ -115,6 +118,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
115
118
 
116
119
  it 'should only trigger onchange once' do
117
120
  session.visit('/with_js')
121
+ sleep 2 if safari?(session) # Safari needs a delay (to load event handlers maybe ???)
118
122
  session.fill_in('with_change_event',
119
123
  with: 'some value',
120
124
  fill_options: { clear: :backspace })
@@ -139,22 +143,34 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
139
143
  with: '',
140
144
  fill_options: { clear: :backspace })
141
145
  # click outside the field to trigger the change event
142
- session.find(:css, 'body').click
146
+ # session.find(:css, 'body').click
147
+ session.find(:css, 'h1', text: 'FooBar').click
143
148
  expect(session).to have_xpath('//p[@class="input_event_triggered"]', count: 13)
144
149
  end
145
150
  end
146
151
 
147
- context '#fill_in with { clear: :none } fill_options' do
152
+ describe '#fill_in with { clear: :none } fill_options' do
148
153
  it 'should append to content in a field' do
154
+ pending 'Safari overwrites by default - need to figure out a workaround' if safari?(session)
155
+
149
156
  session.visit('/form')
150
157
  session.fill_in('form_first_name',
151
158
  with: 'Harry',
152
159
  fill_options: { clear: :none })
153
160
  expect(session.find(:fillable_field, 'form_first_name').value).to eq('JohnHarry')
154
161
  end
162
+
163
+ it 'works with rapid fill' do
164
+ pending 'Safari overwrites by default - need to figure out a workaround' if safari?(session)
165
+
166
+ long_string = (0...60).map { |i| ((i % 26) + 65).chr }.join
167
+ session.visit('/form')
168
+ session.fill_in('form_first_name', with: long_string, fill_options: { clear: :none })
169
+ expect(session.find(:fillable_field, 'form_first_name').value).to eq("John#{long_string}")
170
+ end
155
171
  end
156
172
 
157
- context '#fill_in with Date' do
173
+ describe '#fill_in with Date' do
158
174
  before do
159
175
  session.visit('/form')
160
176
  session.find(:css, '#form_date').execute_script <<-JS
@@ -165,7 +181,8 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
165
181
  });
166
182
  JS
167
183
  # work around weird FF issue where it would create an extra focus issue in some cases
168
- session.find(:css, 'body').click
184
+ session.find(:css, 'h1', text: 'Form').click
185
+ # session.find(:css, 'body').click
169
186
  end
170
187
 
171
188
  it 'should generate standard events on changing value' do
@@ -183,7 +200,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
183
200
  end
184
201
  end
185
202
 
186
- context '#fill_in with { clear: Array } fill_options' do
203
+ describe '#fill_in with { clear: Array } fill_options' do
187
204
  it 'should pass the array through to the element' do
188
205
  # this is mainly for use with [[:control, 'a'], :backspace] - however since that is platform dependant I'm testing with something less useful
189
206
  session.visit('/form')
@@ -194,6 +211,26 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
194
211
  end
195
212
  end
196
213
 
214
+ describe '#fill_in with Emoji' do
215
+ it 'sends emojis' do
216
+ session.visit('/form')
217
+ session.fill_in('form_first_name', with: 'a😀cd😴 🛌🏽🇵🇹 e🤾🏽‍♀️f')
218
+ expect(session.find(:fillable_field, 'form_first_name').value).to eq('a😀cd😴 🛌🏽🇵🇹 e🤾🏽‍♀️f')
219
+ end
220
+ end
221
+
222
+ describe '#send_keys' do
223
+ it 'defaults to sending keys to the active_element' do
224
+ session.visit('/form')
225
+
226
+ expect(session.active_element).to match_selector(:css, 'body')
227
+
228
+ session.send_keys(:tab)
229
+
230
+ expect(session.active_element).to match_selector(:css, '[tabindex="1"]')
231
+ end
232
+ end
233
+
197
234
  describe '#path' do
198
235
  it 'returns xpath' do
199
236
  # this is here because it is testing for an XPath that is specific to the algorithm used in the selenium driver
@@ -243,6 +280,13 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
243
280
  expect(element).to eq session.find(:id, 'form_title')
244
281
  end
245
282
 
283
+ it 'returns a shadow root' do
284
+ session.visit('/with_shadow')
285
+ shadow = session.find(:css, '#shadow_host')
286
+ element = session.evaluate_script('arguments[0].shadowRoot', shadow)
287
+ expect(element).to be_instance_of(Capybara::Node::Element)
288
+ end
289
+
246
290
  it 'can return arrays of nested elements' do
247
291
  session.visit('/form')
248
292
  elements = session.evaluate_script('document.querySelectorAll("#form_city option")')
@@ -263,6 +307,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
263
307
 
264
308
  describe '#evaluate_async_script' do
265
309
  it 'will timeout if the script takes too long' do
310
+ skip 'safaridriver returns the wrong error type' if safari?(session)
266
311
  session.visit('/with_js')
267
312
  expect do
268
313
  session.using_wait_time(1) do
@@ -287,65 +332,35 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
287
332
  describe 'Element#click' do
288
333
  it 'should handle fixed headers/footers' do
289
334
  session.visit('/with_fixed_header_footer')
290
- # session.click_link('Go to root')
291
- session.find(:link, 'Go to root').click
335
+ session.using_wait_time(2) do
336
+ session.find(:link, 'Go to root').click
337
+ end
292
338
  expect(session).to have_current_path('/')
293
339
  end
294
340
  end
295
341
 
296
- describe 'Element#drag_to' do
297
- before do
298
- skip "Firefox < 62 doesn't support a DataTransfer constuctor" if firefox_lt?(62.0, session)
299
- skip "IE doesn't support a DataTransfer constuctor" if ie?(session)
300
- end
301
-
302
- it 'should HTML5 drag and drop an object' do
303
- session.visit('/with_js')
304
- element = session.find('//div[@id="drag_html5"]')
305
- target = session.find('//div[@id="drop_html5"]')
306
- element.drag_to(target)
307
- expect(session).to have_xpath('//div[contains(., "HTML5 Dropped drag_html5")]')
308
- end
309
-
310
- it 'should not HTML5 drag and drop on a non HTML5 drop element' do
311
- session.visit('/with_js')
312
- element = session.find('//div[@id="drag_html5"]')
313
- target = session.find('//div[@id="drop_html5"]')
314
- target.execute_script("$(this).removeClass('drop');")
315
- element.drag_to(target)
316
- sleep 1
317
- expect(session).not_to have_xpath('//div[contains(., "HTML5 Dropped drag_html5")]')
318
- end
319
-
320
- it 'should HTML5 drag and drop when scrolling needed' do
321
- session.visit('/with_js')
322
- element = session.find('//div[@id="drag_html5_scroll"]')
323
- target = session.find('//div[@id="drop_html5_scroll"]')
324
- element.drag_to(target)
325
- expect(session).to have_xpath('//div[contains(., "HTML5 Dropped drag_html5_scroll")]')
326
- end
327
-
328
- it 'should drag HTML5 default draggable elements' do
329
- session.visit('/with_js')
330
- link = session.find_link('drag_link_html5')
331
- target = session.find(:id, 'drop_html5')
332
- link.drag_to target
333
- expect(session).to have_xpath('//div[contains(., "HTML5 Dropped")]')
334
- end
335
- end
336
-
337
342
  describe 'Capybara#Node#attach_file' do
338
343
  it 'can attach a directory' do
339
344
  pending "Geckodriver doesn't support uploading a directory" if firefox?(session)
340
345
  pending "Selenium remote doesn't support transferring a directory" if remote?(session)
341
- pending "Headless Chrome doesn't support directory upload - https://bugs.chromium.org/p/chromedriver/issues/detail?id=2521&q=directory%20upload&colspec=ID%20Status%20Pri%20Owner%20Summary" if chrome?(session) && ENV['HEADLESS']
346
+ pending "Headless Chrome doesn't support directory upload - https://bugs.chromium.org/p/chromedriver/issues/detail?id=2521&q=directory%20upload&colspec=ID%20Status%20Pri%20Owner%20Summary" if chrome?(session) && chrome_lt?(110, session) && ENV.fetch('HEADLESS', nil)
342
347
  pending "IE doesn't support uploading a directory" if ie?(session)
348
+ pending 'Chrome/chromedriver 73 breaks this' if chrome?(session) && chrome_gte?(73, session) && chrome_lt?(75, session)
343
349
 
344
350
  session.visit('/form')
345
- @test_file_dir = File.expand_path('./fixtures', File.dirname(__FILE__))
346
- session.attach_file('Directory Upload', @test_file_dir)
351
+ test_file_dir = File.expand_path('./fixtures', File.dirname(__FILE__))
352
+ session.attach_file('Directory Upload', test_file_dir)
347
353
  session.click_button('Upload Multiple')
348
- expect(session.body).to include('5 | ') # number of files
354
+ expect(session).to have_text('5 | ') # number of files
355
+ end
356
+
357
+ it 'can attach a relative file' do
358
+ pending 'Geckdoriver on windows requires alternate file separator which path expansion replaces' if Gem.win_platform? && firefox?(session)
359
+
360
+ session.visit('/form')
361
+ session.attach_file('Single Document', 'spec/fixtures/capybara.csv')
362
+ session.click_button('Upload Single')
363
+ expect(session).to have_text('Content-type: text/csv')
349
364
  end
350
365
  end
351
366
 
@@ -357,17 +372,21 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
357
372
  end
358
373
  end
359
374
 
375
+ # rubocop:disable RSpec/InstanceVariable
360
376
  describe 'Capybara#disable_animation' do
361
377
  context 'when set to `true`' do
362
378
  before(:context) do # rubocop:disable RSpec/BeforeAfterAll
379
+ skip "Safari doesn't support multiple sessions" if safari?(session)
363
380
  # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
364
381
  # it doesn't affect any of these tests because the settings are applied per-session
365
382
  Capybara.disable_animation = true
366
383
  @animation_session = Capybara::Session.new(session.mode, TestApp.new)
367
384
  end
368
385
 
369
- after(:context) do # rubocop:disable RSpec/BeforeAfterAll
370
- @animation_session = nil
386
+ it 'should add CSS to the <head> element' do
387
+ @animation_session.visit('with_animation')
388
+
389
+ expect(@animation_session).to have_selector(:css, 'head > style', text: 'transition: none', visible: :hidden)
371
390
  end
372
391
 
373
392
  it 'should disable CSS transitions' do
@@ -376,33 +395,87 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
376
395
  expect(@animation_session).to have_no_link('transition me away', wait: 0.5)
377
396
  end
378
397
 
379
- it 'should disable CSS animations' do
398
+ it 'should disable CSS animations (set to 0s)' do
380
399
  @animation_session.visit('with_animation')
400
+ sleep 1
381
401
  @animation_session.click_link('animate me away')
382
402
  expect(@animation_session).to have_no_link('animate me away', wait: 0.5)
383
403
  end
404
+
405
+ it 'should disable CSS animations on pseudo elements (set to 0s)' do
406
+ @animation_session.visit('with_animation')
407
+ sleep 1
408
+ @animation_session.find_link('animate me away').right_click
409
+ expect(@animation_session).to have_content('Animation Ended', wait: 0.1)
410
+ end
411
+
412
+ it 'should scroll the page instantly', requires: [:js] do
413
+ @animation_session.visit('with_animation')
414
+ scroll_y = @animation_session.evaluate_script(<<~JS)
415
+ (function(){
416
+ window.scrollTo(0,500);
417
+ return window.scrollY;
418
+ })()
419
+ JS
420
+ expect(scroll_y).to eq 500
421
+ end
422
+
423
+ it 'should scroll the page instantly without jquery animation', requires: [:js] do
424
+ @animation_session.visit('with_jquery_animation')
425
+ @animation_session.click_link('scroll top 500')
426
+ scroll_y = @animation_session.evaluate_script('window.scrollY')
427
+ expect(scroll_y).to eq 500
428
+ end
429
+ end
430
+
431
+ context 'when set to `false`' do
432
+ before(:context) do # rubocop:disable RSpec/BeforeAfterAll
433
+ skip "Safari doesn't support multiple sessions" if safari?(session)
434
+ # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
435
+ # it doesn't affect any of these tests because the settings are applied per-session
436
+ Capybara.disable_animation = false
437
+ @animation_session = Capybara::Session.new(session.mode, TestApp.new)
438
+ end
439
+
440
+ it 'should scroll the page with a smooth animation', requires: [:js] do
441
+ @animation_session.visit('with_animation')
442
+ scroll_y = @animation_session.evaluate_script(<<~JS)
443
+ (function(){
444
+ window.scrollTo(0,500);
445
+ return window.scrollY;
446
+ })()
447
+ JS
448
+ # measured over 0.5 seconds: 0, 75, 282, 478, 500
449
+ expect(scroll_y).to be < 500
450
+ end
451
+
452
+ it 'should scroll the page with jquery animation', requires: [:js] do
453
+ @animation_session.visit('with_jquery_animation')
454
+ @animation_session.click_link('scroll top 500')
455
+ scroll_y = @animation_session.evaluate_script('window.scrollY')
456
+ expect(scroll_y).to be < 500
457
+ end
384
458
  end
385
459
 
386
460
  context 'if we pass in css that matches elements' do
387
461
  before(:context) do # rubocop:disable RSpec/BeforeAfterAll
462
+ skip "safaridriver doesn't support multiple sessions" if safari?(session)
388
463
  # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
389
464
  # it doesn't affect any of these tests because the settings are applied per-session
390
465
  Capybara.disable_animation = '#with_animation a'
391
466
  @animation_session_with_matching_css = Capybara::Session.new(session.mode, TestApp.new)
392
467
  end
393
468
 
394
- after(:context) do # rubocop:disable RSpec/BeforeAfterAll
395
- @animation_session_with_matching_css = nil
396
- end
397
-
398
469
  it 'should disable CSS transitions' do
399
470
  @animation_session_with_matching_css.visit('with_animation')
471
+ sleep 1
400
472
  @animation_session_with_matching_css.click_link('transition me away')
401
473
  expect(@animation_session_with_matching_css).to have_no_link('transition me away', wait: 0.5)
402
474
  end
403
475
 
404
476
  it 'should disable CSS animations' do
405
477
  @animation_session_with_matching_css.visit('with_animation')
478
+ sleep 1
406
479
  @animation_session_with_matching_css.click_link('animate me away')
407
480
  expect(@animation_session_with_matching_css).to have_no_link('animate me away', wait: 0.5)
408
481
  end
@@ -410,18 +483,16 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
410
483
 
411
484
  context 'if we pass in css that does not match elements' do
412
485
  before(:context) do # rubocop:disable RSpec/BeforeAfterAll
486
+ skip "Safari doesn't support multiple sessions" if safari?(session)
413
487
  # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
414
488
  # it doesn't affect any of these tests because the settings are applied per-session
415
489
  Capybara.disable_animation = '.this-class-matches-nothing'
416
490
  @animation_session_without_matching_css = Capybara::Session.new(session.mode, TestApp.new)
417
491
  end
418
492
 
419
- after(:context) do # rubocop:disable RSpec/BeforeAfterAll
420
- @animation_session_without_matching_css = nil
421
- end
422
-
423
493
  it 'should not disable CSS transitions' do
424
494
  @animation_session_without_matching_css.visit('with_animation')
495
+ sleep 1
425
496
  @animation_session_without_matching_css.click_link('transition me away')
426
497
  sleep 0.5 # Wait long enough for click to have been processed
427
498
  expect(@animation_session_without_matching_css).to have_link('transition me away', wait: false)
@@ -430,6 +501,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
430
501
 
431
502
  it 'should not disable CSS animations' do
432
503
  @animation_session_without_matching_css.visit('with_animation')
504
+ sleep 1
433
505
  @animation_session_without_matching_css.click_link('animate me away')
434
506
  sleep 0.5 # Wait long enough for click to have been processed
435
507
  expect(@animation_session_without_matching_css).to have_link('animate me away', wait: false)
@@ -437,20 +509,53 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
437
509
  end
438
510
  end
439
511
  end
512
+ # rubocop:enable RSpec/InstanceVariable
440
513
 
441
514
  describe ':element selector' do
442
515
  it 'can find html5 svg elements' do
443
516
  session.visit('with_html5_svg')
444
517
  expect(session).to have_selector(:element, :svg)
445
- expect(session).to have_selector(:element, :rect, visible: true)
518
+ expect(session).to have_selector(:element, :rect, visible: :visible)
446
519
  expect(session).to have_selector(:element, :circle)
447
520
  expect(session).to have_selector(:element, :linearGradient, visible: :all)
448
521
  end
449
522
 
450
523
  it 'can query attributes with strange characters' do
451
524
  session.visit('/form')
452
- expect(session).to have_selector(:element, "{custom}": true)
453
- expect(session).to have_selector(:element, "{custom}": 'abcdef')
525
+ expect(session).to have_selector(:element, '{custom}': true)
526
+ expect(session).to have_selector(:element, '{custom}': 'abcdef')
527
+ end
528
+ end
529
+
530
+ describe 'with react' do
531
+ context 'controlled components' do
532
+ it 'can set and clear a text field' do
533
+ skip "This test doesn't support older browsers" if ie?(session)
534
+
535
+ session.visit 'react'
536
+
537
+ session.fill_in('Name:', with: 'abc')
538
+ session.accept_prompt 'A name was submitted: abc' do
539
+ session.click_button('Submit')
540
+ end
541
+
542
+ session.fill_in('Name:', with: '')
543
+ session.accept_prompt(/A name was submitted: $/) do
544
+ session.click_button('Submit')
545
+ end
546
+ end
547
+
548
+ it 'works with rapid fill' do
549
+ skip "This test doesn't support older browsers" if ie?(session)
550
+
551
+ session.visit 'react'
552
+ long_string = (0...60).map { |i| ((i % 26) + 65).chr }.join
553
+
554
+ session.fill_in('Name:', with: long_string)
555
+ session.accept_prompt "A name was submitted: #{long_string}" do
556
+ session.click_button('Submit')
557
+ end
558
+ end
454
559
  end
455
560
  end
456
561
  end
data/spec/spec_helper.rb CHANGED
@@ -1,15 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec/expectations'
4
- require 'capybara/spec/spec_helper'
5
- require 'webdrivers' if ENV['CI']
4
+ require 'webdrivers' if ENV.fetch('WEBDRIVERS', nil)
6
5
  require 'selenium_statistics'
6
+ if ENV['TRAVIS']
7
+ require 'coveralls'
8
+ Coveralls.wear! do
9
+ add_filter '/lib/capybara/driver/'
10
+ add_filter '/lib/capybara/registrations/'
11
+ end
12
+ end
13
+ require 'capybara/spec/spec_helper'
7
14
 
8
15
  module Capybara
9
16
  module SpecHelper
10
17
  def firefox?(session)
11
18
  browser_name(session) == :firefox &&
12
- session.driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
19
+ ((defined?(::Selenium::WebDriver::VERSION) && (Gem::Version.new(::Selenium::WebDriver::VERSION) >= Gem::Version.new('4'))) ||
20
+ session.driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities))
13
21
  end
14
22
 
15
23
  def firefox_lt?(version, session)
@@ -20,26 +28,80 @@ module Capybara
20
28
  firefox?(session) && (session.driver.browser.capabilities[:browser_version].to_f >= version)
21
29
  end
22
30
 
31
+ def geckodriver_version(session)
32
+ Gem::Version.new(session.driver.browser.capabilities['moz:geckodriverVersion'])
33
+ end
34
+
35
+ def geckodriver_gte?(version, session)
36
+ firefox?(session) && geckodriver_version(session) >= Gem::Version.new(version)
37
+ end
38
+
39
+ def geckodriver_lt?(version, session)
40
+ firefox?(session) && geckodriver_version(session) < Gem::Version.new(version)
41
+ end
42
+
23
43
  def chrome?(session)
24
44
  browser_name(session) == :chrome
25
45
  end
26
46
 
47
+ def chrome_version(session)
48
+ (session.driver.browser.capabilities[:browser_version] ||
49
+ session.driver.browser.capabilities[:version]).to_f
50
+ end
51
+
27
52
  def chrome_lt?(version, session)
28
- chrome?(session) && (session.driver.browser.capabilities[:version].to_f < version)
53
+ chrome?(session) && (chrome_version(session) < version)
29
54
  end
30
55
 
31
56
  def chrome_gte?(version, session)
32
- chrome?(session) && (session.driver.browser.capabilities[:version].to_f >= version)
57
+ chrome?(session) && (chrome_version(session) >= version)
58
+ end
59
+
60
+ def chromedriver_version(session)
61
+ Gem::Version.new(session.driver.browser.capabilities['chrome']['chromedriverVersion'].split[0])
62
+ end
63
+
64
+ def chromedriver_gte?(version, session)
65
+ chrome?(session) && chromedriver_version(session) >= Gem::Version.new(version)
66
+ end
67
+
68
+ def chromedriver_lt?(version, session)
69
+ chrome?(session) && chromedriver_version(session) < Gem::Version.new(version)
70
+ end
71
+
72
+ def selenium?(session)
73
+ session.driver.is_a? Capybara::Selenium::Driver
74
+ end
75
+
76
+ def selenium_lt?(version, session)
77
+ selenium?(session) &&
78
+ Gem::Version.new(::Selenium::WebDriver::VERSION) < Gem::Version.new(version)
33
79
  end
34
80
 
35
81
  def edge?(session)
82
+ browser_name(session).to_s.start_with?('edge')
83
+ end
84
+
85
+ def legacy_edge?(session)
36
86
  browser_name(session) == :edge
37
87
  end
38
88
 
89
+ def edge_lt?(version, session)
90
+ edge?(session) && (chrome_version(session) < version)
91
+ end
92
+
93
+ def edge_gte?(version, session)
94
+ edge?(session) && (chrome_version(session) >= version)
95
+ end
96
+
39
97
  def ie?(session)
40
98
  %i[internet_explorer ie].include?(browser_name(session))
41
99
  end
42
100
 
101
+ def safari?(session)
102
+ %i[safari Safari Safari_Technology_Preview].include?(browser_name(session))
103
+ end
104
+
43
105
  def browser_name(session)
44
106
  session.driver.browser.browser if session.respond_to?(:driver)
45
107
  end
@@ -47,11 +109,24 @@ module Capybara
47
109
  def remote?(session)
48
110
  session.driver.browser.is_a? ::Selenium::WebDriver::Remote::Driver
49
111
  end
112
+
113
+ def self.log_selenium_driver_version(mod)
114
+ mod = mod::Service if ::Selenium::WebDriver::Service.respond_to? :driver_path
115
+ path = mod.driver_path
116
+ path = path.call if path.respond_to? :call
117
+ $stdout.puts `#{path.gsub(' ', '\ ')} --version` if path
118
+ end
50
119
  end
51
120
  end
52
121
 
53
122
  RSpec.configure do |config|
54
123
  Capybara::SpecHelper.configure(config)
124
+ config.expect_with :rspec do |expectations|
125
+ expectations.syntax = :expect
126
+ end
127
+ config.mock_with :rspec do |mocks|
128
+ mocks.verify_partial_doubles = true
129
+ end
55
130
  config.filter_run_including focus_: true unless ENV['CI']
56
131
  config.run_all_when_everything_filtered = true
57
132
  config.after(:suite) { SeleniumStatistics.print_results }
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'capybara/node/whitespace_normalizer'
5
+
6
+ RSpec.describe Capybara::Node::WhitespaceNormalizer do
7
+ subject(:normalizer) do
8
+ klass = Class.new do
9
+ include Capybara::Node::WhitespaceNormalizer
10
+ end
11
+
12
+ klass.new
13
+ end
14
+
15
+ let(:text_needing_correction) do
16
+ <<~TEXT
17
+ Some #{described_class::NON_BREAKING_SPACE}text
18
+ #{described_class::RIGHT_TO_LEFT_MARK}
19
+ #{described_class::ZERO_WIDTH_SPACE * 30}
20
+ #{described_class::LEFT_TO_RIGHT_MARK}
21
+ Here
22
+ TEXT
23
+ end
24
+
25
+ describe '#normalize_spacing' do
26
+ it 'does nothing to text not containing special characters' do
27
+ expect(normalizer.normalize_spacing('text')).to eq('text')
28
+ end
29
+
30
+ it 'compresses excess breaking spacing' do
31
+ expect(
32
+ normalizer.normalize_spacing(text_needing_correction)
33
+ ).to eq('Some text Here')
34
+ end
35
+ end
36
+
37
+ describe '#normalize_visible_spacing' do
38
+ it 'does nothing to text not containing special characters' do
39
+ expect(normalizer.normalize_visible_spacing('text')).to eq('text')
40
+ end
41
+
42
+ it 'compresses excess breaking visible spacing' do
43
+ expect(
44
+ normalizer.normalize_visible_spacing(text_needing_correction)
45
+ ).to eq <<~TEXT.chomp
46
+ Some text
47
+ #{described_class::RIGHT_TO_LEFT_MARK}
48
+ #{described_class::ZERO_WIDTH_SPACE * 30}
49
+ #{described_class::LEFT_TO_RIGHT_MARK}
50
+ Here
51
+ TEXT
52
+ end
53
+ end
54
+ end
@@ -2,9 +2,10 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
+ # rubocop:disable RSpec/InstanceVariable
5
6
  RSpec.describe Capybara::Selector::XPathBuilder do
6
7
  let :builder do
7
- ::Capybara::Selector::XPathBuilder.new(@xpath)
8
+ described_class.new(@xpath)
8
9
  end
9
10
 
10
11
  context 'add_attribute_conditions' do
@@ -89,3 +90,4 @@ RSpec.describe Capybara::Selector::XPathBuilder do
89
90
  end
90
91
  end
91
92
  end
93
+ # rubocop:enable RSpec/InstanceVariable