capybara 3.8.1 → 3.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (242) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/History.md +465 -0
  4. data/License.txt +1 -1
  5. data/README.md +58 -57
  6. data/lib/capybara/config.rb +10 -4
  7. data/lib/capybara/cucumber.rb +1 -1
  8. data/lib/capybara/driver/base.rb +2 -2
  9. data/lib/capybara/driver/node.rb +26 -5
  10. data/lib/capybara/dsl.rb +12 -4
  11. data/lib/capybara/helpers.rb +8 -4
  12. data/lib/capybara/minitest/spec.rb +162 -85
  13. data/lib/capybara/minitest.rb +248 -148
  14. data/lib/capybara/node/actions.rb +149 -96
  15. data/lib/capybara/node/base.rb +27 -10
  16. data/lib/capybara/node/document.rb +12 -0
  17. data/lib/capybara/node/document_matchers.rb +9 -5
  18. data/lib/capybara/node/element.rb +254 -109
  19. data/lib/capybara/node/finders.rb +83 -76
  20. data/lib/capybara/node/matchers.rb +279 -141
  21. data/lib/capybara/node/simple.rb +25 -6
  22. data/lib/capybara/queries/ancestor_query.rb +5 -7
  23. data/lib/capybara/queries/base_query.rb +11 -5
  24. data/lib/capybara/queries/current_path_query.rb +3 -3
  25. data/lib/capybara/queries/match_query.rb +1 -0
  26. data/lib/capybara/queries/selector_query.rb +467 -103
  27. data/lib/capybara/queries/sibling_query.rb +5 -4
  28. data/lib/capybara/queries/style_query.rb +6 -2
  29. data/lib/capybara/queries/text_query.rb +17 -3
  30. data/lib/capybara/queries/title_query.rb +2 -2
  31. data/lib/capybara/rack_test/browser.rb +22 -15
  32. data/lib/capybara/rack_test/driver.rb +10 -1
  33. data/lib/capybara/rack_test/errors.rb +6 -0
  34. data/lib/capybara/rack_test/form.rb +33 -28
  35. data/lib/capybara/rack_test/node.rb +74 -6
  36. data/lib/capybara/registration_container.rb +44 -0
  37. data/lib/capybara/registrations/drivers.rb +36 -0
  38. data/lib/capybara/registrations/patches/puma_ssl.rb +27 -0
  39. data/lib/capybara/registrations/servers.rb +44 -0
  40. data/lib/capybara/result.rb +55 -23
  41. data/lib/capybara/rspec/features.rb +4 -4
  42. data/lib/capybara/rspec/matcher_proxies.rb +36 -15
  43. data/lib/capybara/rspec/matchers/base.rb +111 -0
  44. data/lib/capybara/rspec/matchers/become_closed.rb +33 -0
  45. data/lib/capybara/rspec/matchers/compound.rb +88 -0
  46. data/lib/capybara/rspec/matchers/count_sugar.rb +37 -0
  47. data/lib/capybara/rspec/matchers/have_ancestor.rb +28 -0
  48. data/lib/capybara/rspec/matchers/have_current_path.rb +29 -0
  49. data/lib/capybara/rspec/matchers/have_selector.rb +77 -0
  50. data/lib/capybara/rspec/matchers/have_sibling.rb +27 -0
  51. data/lib/capybara/rspec/matchers/have_text.rb +33 -0
  52. data/lib/capybara/rspec/matchers/have_title.rb +29 -0
  53. data/lib/capybara/rspec/matchers/match_selector.rb +27 -0
  54. data/lib/capybara/rspec/matchers/match_style.rb +38 -0
  55. data/lib/capybara/rspec/matchers/spatial_sugar.rb +39 -0
  56. data/lib/capybara/rspec/matchers.rb +117 -311
  57. data/lib/capybara/selector/builders/css_builder.rb +84 -0
  58. data/lib/capybara/selector/builders/xpath_builder.rb +69 -0
  59. data/lib/capybara/selector/css.rb +17 -15
  60. data/lib/capybara/selector/definition/button.rb +52 -0
  61. data/lib/capybara/selector/definition/checkbox.rb +26 -0
  62. data/lib/capybara/selector/definition/css.rb +10 -0
  63. data/lib/capybara/selector/definition/datalist_input.rb +35 -0
  64. data/lib/capybara/selector/definition/datalist_option.rb +25 -0
  65. data/lib/capybara/selector/definition/element.rb +27 -0
  66. data/lib/capybara/selector/definition/field.rb +40 -0
  67. data/lib/capybara/selector/definition/fieldset.rb +14 -0
  68. data/lib/capybara/selector/definition/file_field.rb +13 -0
  69. data/lib/capybara/selector/definition/fillable_field.rb +33 -0
  70. data/lib/capybara/selector/definition/frame.rb +17 -0
  71. data/lib/capybara/selector/definition/id.rb +6 -0
  72. data/lib/capybara/selector/definition/label.rb +62 -0
  73. data/lib/capybara/selector/definition/link.rb +54 -0
  74. data/lib/capybara/selector/definition/link_or_button.rb +16 -0
  75. data/lib/capybara/selector/definition/option.rb +27 -0
  76. data/lib/capybara/selector/definition/radio_button.rb +27 -0
  77. data/lib/capybara/selector/definition/select.rb +81 -0
  78. data/lib/capybara/selector/definition/table.rb +109 -0
  79. data/lib/capybara/selector/definition/table_row.rb +21 -0
  80. data/lib/capybara/selector/definition/xpath.rb +5 -0
  81. data/lib/capybara/selector/definition.rb +277 -0
  82. data/lib/capybara/selector/filter.rb +1 -0
  83. data/lib/capybara/selector/filter_set.rb +26 -19
  84. data/lib/capybara/selector/filters/base.rb +24 -5
  85. data/lib/capybara/selector/filters/expression_filter.rb +3 -3
  86. data/lib/capybara/selector/filters/locator_filter.rb +29 -0
  87. data/lib/capybara/selector/filters/node_filter.rb +16 -2
  88. data/lib/capybara/selector/regexp_disassembler.rb +214 -0
  89. data/lib/capybara/selector/selector.rb +73 -367
  90. data/lib/capybara/selector/xpath_extensions.rb +17 -0
  91. data/lib/capybara/selector.rb +221 -480
  92. data/lib/capybara/selenium/atoms/getAttribute.min.js +1 -0
  93. data/lib/capybara/selenium/atoms/isDisplayed.min.js +1 -0
  94. data/lib/capybara/selenium/atoms/src/getAttribute.js +161 -0
  95. data/lib/capybara/selenium/atoms/src/isDisplayed.js +454 -0
  96. data/lib/capybara/selenium/driver.rb +203 -86
  97. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +88 -14
  98. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +124 -0
  99. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +89 -0
  100. data/lib/capybara/selenium/driver_specializations/internet_explorer_driver.rb +26 -0
  101. data/lib/capybara/selenium/driver_specializations/safari_driver.rb +24 -0
  102. data/lib/capybara/selenium/extensions/file_input_click_emulation.rb +34 -0
  103. data/lib/capybara/selenium/extensions/find.rb +110 -0
  104. data/lib/capybara/selenium/extensions/html5_drag.rb +191 -22
  105. data/lib/capybara/selenium/extensions/modifier_keys_stack.rb +28 -0
  106. data/lib/capybara/selenium/extensions/scroll.rb +78 -0
  107. data/lib/capybara/selenium/logger_suppressor.rb +34 -0
  108. data/lib/capybara/selenium/node.rb +298 -93
  109. data/lib/capybara/selenium/nodes/chrome_node.rb +100 -8
  110. data/lib/capybara/selenium/nodes/edge_node.rb +104 -0
  111. data/lib/capybara/selenium/nodes/firefox_node.rb +131 -0
  112. data/lib/capybara/selenium/nodes/ie_node.rb +22 -0
  113. data/lib/capybara/selenium/nodes/safari_node.rb +118 -0
  114. data/lib/capybara/selenium/patches/action_pauser.rb +26 -0
  115. data/lib/capybara/selenium/patches/atoms.rb +18 -0
  116. data/lib/capybara/selenium/patches/is_displayed.rb +16 -0
  117. data/lib/capybara/selenium/patches/logs.rb +45 -0
  118. data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -3
  119. data/lib/capybara/selenium/patches/persistent_client.rb +20 -0
  120. data/lib/capybara/server/animation_disabler.rb +4 -3
  121. data/lib/capybara/server/checker.rb +6 -2
  122. data/lib/capybara/server/middleware.rb +23 -13
  123. data/lib/capybara/server.rb +30 -7
  124. data/lib/capybara/session/config.rb +14 -10
  125. data/lib/capybara/session/matchers.rb +11 -7
  126. data/lib/capybara/session.rb +152 -111
  127. data/lib/capybara/spec/public/offset.js +6 -0
  128. data/lib/capybara/spec/public/test.js +101 -10
  129. data/lib/capybara/spec/session/all_spec.rb +96 -6
  130. data/lib/capybara/spec/session/ancestor_spec.rb +5 -0
  131. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +29 -0
  132. data/lib/capybara/spec/session/assert_current_path_spec.rb +5 -2
  133. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  134. data/lib/capybara/spec/session/assert_style_spec.rb +4 -4
  135. data/lib/capybara/spec/session/assert_text_spec.rb +9 -5
  136. data/lib/capybara/spec/session/attach_file_spec.rb +63 -36
  137. data/lib/capybara/spec/session/check_spec.rb +10 -4
  138. data/lib/capybara/spec/session/choose_spec.rb +8 -2
  139. data/lib/capybara/spec/session/click_button_spec.rb +117 -61
  140. data/lib/capybara/spec/session/click_link_or_button_spec.rb +16 -0
  141. data/lib/capybara/spec/session/click_link_spec.rb +17 -6
  142. data/lib/capybara/spec/session/element/matches_selector_spec.rb +40 -39
  143. data/lib/capybara/spec/session/evaluate_script_spec.rb +13 -0
  144. data/lib/capybara/spec/session/execute_script_spec.rb +1 -0
  145. data/lib/capybara/spec/session/fill_in_spec.rb +47 -6
  146. data/lib/capybara/spec/session/find_field_spec.rb +1 -1
  147. data/lib/capybara/spec/session/find_spec.rb +74 -4
  148. data/lib/capybara/spec/session/first_spec.rb +1 -1
  149. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +13 -1
  150. data/lib/capybara/spec/session/frame/within_frame_spec.rb +12 -1
  151. data/lib/capybara/spec/session/has_all_selectors_spec.rb +1 -1
  152. data/lib/capybara/spec/session/has_ancestor_spec.rb +46 -0
  153. data/lib/capybara/spec/session/has_any_selectors_spec.rb +25 -0
  154. data/lib/capybara/spec/session/has_button_spec.rb +16 -0
  155. data/lib/capybara/spec/session/has_css_spec.rb +122 -12
  156. data/lib/capybara/spec/session/has_current_path_spec.rb +6 -4
  157. data/lib/capybara/spec/session/has_field_spec.rb +55 -0
  158. data/lib/capybara/spec/session/has_select_spec.rb +34 -6
  159. data/lib/capybara/spec/session/has_selector_spec.rb +11 -4
  160. data/lib/capybara/spec/session/has_sibling_spec.rb +50 -0
  161. data/lib/capybara/spec/session/has_table_spec.rb +166 -0
  162. data/lib/capybara/spec/session/has_text_spec.rb +48 -1
  163. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  164. data/lib/capybara/spec/session/html_spec.rb +7 -0
  165. data/lib/capybara/spec/session/matches_style_spec.rb +35 -0
  166. data/lib/capybara/spec/session/node_spec.rb +643 -18
  167. data/lib/capybara/spec/session/node_wrapper_spec.rb +1 -1
  168. data/lib/capybara/spec/session/refresh_spec.rb +4 -0
  169. data/lib/capybara/spec/session/reset_session_spec.rb +23 -8
  170. data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +2 -2
  171. data/lib/capybara/spec/session/save_screenshot_spec.rb +4 -4
  172. data/lib/capybara/spec/session/scroll_spec.rb +117 -0
  173. data/lib/capybara/spec/session/select_spec.rb +10 -10
  174. data/lib/capybara/spec/session/selectors_spec.rb +36 -5
  175. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  176. data/lib/capybara/spec/session/unselect_spec.rb +1 -1
  177. data/lib/capybara/spec/session/window/become_closed_spec.rb +20 -17
  178. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -0
  179. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -0
  180. data/lib/capybara/spec/session/window/window_spec.rb +59 -58
  181. data/lib/capybara/spec/session/window/windows_spec.rb +4 -0
  182. data/lib/capybara/spec/session/within_spec.rb +23 -0
  183. data/lib/capybara/spec/spec_helper.rb +16 -6
  184. data/lib/capybara/spec/test_app.rb +28 -23
  185. data/lib/capybara/spec/views/animated.erb +49 -0
  186. data/lib/capybara/spec/views/form.erb +48 -7
  187. data/lib/capybara/spec/views/frame_child.erb +3 -2
  188. data/lib/capybara/spec/views/frame_one.erb +1 -0
  189. data/lib/capybara/spec/views/obscured.erb +47 -0
  190. data/lib/capybara/spec/views/offset.erb +32 -0
  191. data/lib/capybara/spec/views/react.erb +45 -0
  192. data/lib/capybara/spec/views/scroll.erb +20 -0
  193. data/lib/capybara/spec/views/spatial.erb +31 -0
  194. data/lib/capybara/spec/views/tables.erb +67 -0
  195. data/lib/capybara/spec/views/with_animation.erb +29 -1
  196. data/lib/capybara/spec/views/with_dragula.erb +24 -0
  197. data/lib/capybara/spec/views/with_hover.erb +1 -0
  198. data/lib/capybara/spec/views/with_hover1.erb +10 -0
  199. data/lib/capybara/spec/views/with_html.erb +32 -6
  200. data/lib/capybara/spec/views/with_js.erb +3 -1
  201. data/lib/capybara/spec/views/with_jstree.erb +26 -0
  202. data/lib/capybara/spec/views/with_scope_other.erb +6 -0
  203. data/lib/capybara/spec/views/with_sortable_js.erb +21 -0
  204. data/lib/capybara/version.rb +1 -1
  205. data/lib/capybara/window.rb +11 -11
  206. data/lib/capybara.rb +118 -111
  207. data/spec/basic_node_spec.rb +14 -3
  208. data/spec/capybara_spec.rb +29 -29
  209. data/spec/css_builder_spec.rb +101 -0
  210. data/spec/dsl_spec.rb +46 -21
  211. data/spec/filter_set_spec.rb +5 -5
  212. data/spec/fixtures/selenium_driver_rspec_failure.rb +1 -1
  213. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  214. data/spec/minitest_spec.rb +18 -4
  215. data/spec/minitest_spec_spec.rb +59 -44
  216. data/spec/rack_test_spec.rb +117 -89
  217. data/spec/regexp_dissassembler_spec.rb +250 -0
  218. data/spec/result_spec.rb +51 -49
  219. data/spec/rspec/features_spec.rb +3 -0
  220. data/spec/rspec/shared_spec_matchers.rb +112 -97
  221. data/spec/rspec_spec.rb +35 -17
  222. data/spec/sauce_spec_chrome.rb +43 -0
  223. data/spec/selector_spec.rb +244 -28
  224. data/spec/selenium_spec_chrome.rb +125 -54
  225. data/spec/selenium_spec_chrome_remote.rb +26 -12
  226. data/spec/selenium_spec_edge.rb +23 -8
  227. data/spec/selenium_spec_firefox.rb +208 -0
  228. data/spec/selenium_spec_firefox_remote.rb +15 -18
  229. data/spec/selenium_spec_ie.rb +82 -13
  230. data/spec/selenium_spec_safari.rb +148 -0
  231. data/spec/server_spec.rb +118 -77
  232. data/spec/session_spec.rb +19 -3
  233. data/spec/shared_selenium_node.rb +83 -0
  234. data/spec/shared_selenium_session.rb +110 -65
  235. data/spec/spec_helper.rb +57 -9
  236. data/spec/xpath_builder_spec.rb +93 -0
  237. metadata +257 -17
  238. data/lib/capybara/rspec/compound.rb +0 -94
  239. data/lib/capybara/selenium/driver_specializations/marionette_driver.rb +0 -49
  240. data/lib/capybara/selenium/nodes/marionette_node.rb +0 -121
  241. data/lib/capybara/spec/session/has_style_spec.rb +0 -25
  242. data/spec/selenium_spec_marionette.rb +0 -172
@@ -28,27 +28,30 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
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,8 +72,9 @@ 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
77
+ pending "safaridriver doesn't trigger change for clear" if safari?(session)
74
78
  session.visit('/with_js')
75
79
  session.fill_in('with_change_event', with: '')
76
80
  # click outside the field to trigger the change event
@@ -79,7 +83,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
79
83
  end
80
84
  end
81
85
 
82
- context '#fill_in with { :clear => :backspace } fill_option', requires: [:js] do
86
+ describe '#fill_in with { :clear => :backspace } fill_option', requires: [:js] do
83
87
  before do
84
88
  # Firefox has an issue with change events if the main window doesn't think it's focused
85
89
  session.execute_script('window.focus()')
@@ -115,6 +119,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
115
119
 
116
120
  it 'should only trigger onchange once' do
117
121
  session.visit('/with_js')
122
+ sleep 2 if safari?(session) # Safari needs a delay (to load event handlers maybe ???)
118
123
  session.fill_in('with_change_event',
119
124
  with: 'some value',
120
125
  fill_options: { clear: :backspace })
@@ -139,22 +144,34 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
139
144
  with: '',
140
145
  fill_options: { clear: :backspace })
141
146
  # click outside the field to trigger the change event
142
- session.find(:css, 'body').click
147
+ # session.find(:css, 'body').click
148
+ session.find(:css, 'h1', text: 'FooBar').click
143
149
  expect(session).to have_xpath('//p[@class="input_event_triggered"]', count: 13)
144
150
  end
145
151
  end
146
152
 
147
- context '#fill_in with { clear: :none } fill_options' do
153
+ describe '#fill_in with { clear: :none } fill_options' do
148
154
  it 'should append to content in a field' do
155
+ pending 'Safari overwrites by default - need to figure out a workaround' if safari?(session)
156
+
149
157
  session.visit('/form')
150
158
  session.fill_in('form_first_name',
151
159
  with: 'Harry',
152
160
  fill_options: { clear: :none })
153
161
  expect(session.find(:fillable_field, 'form_first_name').value).to eq('JohnHarry')
154
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
155
172
  end
156
173
 
157
- context '#fill_in with Date' do
174
+ describe '#fill_in with Date' do
158
175
  before do
159
176
  session.visit('/form')
160
177
  session.find(:css, '#form_date').execute_script <<-JS
@@ -165,17 +182,20 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
165
182
  });
166
183
  JS
167
184
  # work around weird FF issue where it would create an extra focus issue in some cases
168
- session.find(:css, 'body').click
185
+ session.find(:css, 'h1', text: 'Form').click
186
+ # session.find(:css, 'body').click
169
187
  end
170
188
 
171
189
  it 'should generate standard events on changing value' do
172
190
  pending "IE 11 doesn't support date input type" if ie?(session)
191
+ pending "Safari doesn't support date input type" if safari?(session)
173
192
  session.fill_in('form_date', with: Date.today)
174
193
  expect(session.evaluate_script('window.capybara_formDateFiredEvents')).to eq %w[focus input change]
175
194
  end
176
195
 
177
196
  it 'should not generate input and change events if the value is not changed' do
178
197
  pending "IE 11 doesn't support date input type" if ie?(session)
198
+ pending "Safari doesn't support date input type" if safari?(session)
179
199
  session.fill_in('form_date', with: Date.today)
180
200
  session.fill_in('form_date', with: Date.today)
181
201
  # Chrome adds an extra focus for some reason - ok for now
@@ -183,7 +203,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
183
203
  end
184
204
  end
185
205
 
186
- context '#fill_in with { clear: Array } fill_options' do
206
+ describe '#fill_in with { clear: Array } fill_options' do
187
207
  it 'should pass the array through to the element' do
188
208
  # this is mainly for use with [[:control, 'a'], :backspace] - however since that is platform dependant I'm testing with something less useful
189
209
  session.visit('/form')
@@ -199,14 +219,14 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
199
219
  # this is here because it is testing for an XPath that is specific to the algorithm used in the selenium driver
200
220
  session.visit('/path')
201
221
  element = session.find(:link, 'Second Link')
202
- expect(element.path).to eq('/HTML/BODY/DIV[2]/A[1]')
222
+ expect(element.path).to eq('/HTML/BODY[1]/DIV[2]/A[1]')
203
223
  end
204
224
 
205
225
  it 'handles namespaces in xhtml' do
206
226
  pending "IE 11 doesn't handle all XPath querys (namespace-uri, etc)" if ie?(session)
207
227
  session.visit '/with_namespace'
208
228
  rect = session.find(:css, 'div svg rect:first-of-type')
209
- expect(rect.path).to eq("/HTML/BODY/DIV/./*[((local-name(.) = 'svg') and (namespace-uri(.) = 'http://www.w3.org/2000/svg'))]/./*[((local-name(.) = 'rect') and (namespace-uri(.) = 'http://www.w3.org/2000/svg'))][1]")
229
+ expect(rect.path).to eq("/HTML/BODY[1]/DIV[1]/*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg'][1]/*[local-name()='rect' and namespace-uri()='http://www.w3.org/2000/svg'][1]")
210
230
  expect(session.find(:xpath, rect.path)).to eq rect
211
231
  end
212
232
 
@@ -214,7 +234,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
214
234
  pending "IE 11 doesn't handle all XPath querys (namespace-uri, etc)" if ie?(session)
215
235
  session.visit '/with_html5_svg'
216
236
  rect = session.find(:css, 'div svg rect:first-of-type')
217
- expect(rect.path).to eq("/HTML/BODY/DIV/./*[((local-name(.) = 'svg') and (namespace-uri(.) = 'http://www.w3.org/2000/svg'))]/./*[((local-name(.) = 'rect') and (namespace-uri(.) = 'http://www.w3.org/2000/svg'))][1]")
237
+ expect(rect.path).to eq("/HTML/BODY[1]/DIV[1]/*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg'][1]/*[local-name()='rect' and namespace-uri()='http://www.w3.org/2000/svg'][1]")
218
238
  expect(session.find(:xpath, rect.path)).to eq rect
219
239
  end
220
240
 
@@ -263,6 +283,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
263
283
 
264
284
  describe '#evaluate_async_script' do
265
285
  it 'will timeout if the script takes too long' do
286
+ skip 'safaridriver returns the wrong error type' if safari?(session)
266
287
  session.visit('/with_js')
267
288
  expect do
268
289
  session.using_wait_time(1) do
@@ -287,50 +308,37 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
287
308
  describe 'Element#click' do
288
309
  it 'should handle fixed headers/footers' do
289
310
  session.visit('/with_fixed_header_footer')
290
- # session.click_link('Go to root')
291
- session.find(:link, 'Go to root').click
311
+ session.using_wait_time(2) do
312
+ session.find(:link, 'Go to root').click
313
+ end
292
314
  expect(session).to have_current_path('/')
293
315
  end
294
316
  end
295
317
 
296
- describe 'Element#drag_to' do
297
- before do
298
- skip "Firefox < 62 doesn't support a DataTransfer constuctor" if marionette_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
318
+ describe 'Capybara#Node#attach_file' do
319
+ it 'can attach a directory' do
320
+ pending "Geckodriver doesn't support uploading a directory" if firefox?(session)
321
+ pending "Selenium remote doesn't support transferring a directory" if remote?(session)
322
+ 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']
323
+ pending "IE doesn't support uploading a directory" if ie?(session)
324
+ pending 'Chrome/chromedriver 73 breaks this' if chrome?(session) && chrome_gte?(73, session) && chrome_lt?(75, session)
325
+ pending "Safari doesn't support uploading a directory" if safari?(session)
326
+ # pending "Edge/msedgedriver doesn't support directory upload" if edge?(session) && edge_gte?(75, session)
309
327
 
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")]')
328
+ session.visit('/form')
329
+ test_file_dir = File.expand_path('./fixtures', File.dirname(__FILE__))
330
+ session.attach_file('Directory Upload', test_file_dir)
331
+ session.click_button('Upload Multiple')
332
+ expect(session.body).to include('5 | ') # number of files
318
333
  end
319
334
 
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
335
+ it 'can attach a relative file' do
336
+ pending 'Geckdoriver on windows requires alternate file separator which path expansion replaces' if Gem.win_platform? && firefox?(session)
327
337
 
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")]')
338
+ session.visit('/form')
339
+ session.attach_file('Single Document', 'spec/fixtures/capybara.csv')
340
+ session.click_button('Upload Single')
341
+ expect(session.body).to include('Content-type: text/csv')
334
342
  end
335
343
  end
336
344
 
@@ -342,44 +350,45 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
342
350
  end
343
351
  end
344
352
 
353
+ # rubocop:disable RSpec/InstanceVariable
345
354
  describe 'Capybara#disable_animation' do
346
355
  context 'when set to `true`' do
347
356
  before(:context) do # rubocop:disable RSpec/BeforeAfterAll
357
+ skip "Safari doesn't support multiple sessions" if safari?(session)
348
358
  # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
349
359
  # it doesn't affect any of these tests because the settings are applied per-session
350
360
  Capybara.disable_animation = true
351
361
  @animation_session = Capybara::Session.new(session.mode, TestApp.new)
352
362
  end
353
363
 
354
- after(:context) do # rubocop:disable RSpec/BeforeAfterAll
355
- @animation_session = nil
356
- end
357
-
358
364
  it 'should disable CSS transitions' do
359
365
  @animation_session.visit('with_animation')
360
366
  @animation_session.click_link('transition me away')
361
367
  expect(@animation_session).to have_no_link('transition me away', wait: 0.5)
362
368
  end
363
369
 
364
- it 'should disable CSS animations' do
370
+ it 'should disable CSS animations (set to 0s)' do
365
371
  @animation_session.visit('with_animation')
366
372
  @animation_session.click_link('animate me away')
367
373
  expect(@animation_session).to have_no_link('animate me away', wait: 0.5)
368
374
  end
375
+
376
+ it 'should disable CSS animations on pseudo elements (set to 0s)' do
377
+ @animation_session.visit('with_animation')
378
+ @animation_session.find_link('animate me away').right_click
379
+ expect(@animation_session).to have_content('Animation Ended', wait: 0.1)
380
+ end
369
381
  end
370
382
 
371
383
  context 'if we pass in css that matches elements' do
372
384
  before(:context) do # rubocop:disable RSpec/BeforeAfterAll
385
+ skip "safaridriver doesn't support multiple sessions" if safari?(session)
373
386
  # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
374
387
  # it doesn't affect any of these tests because the settings are applied per-session
375
388
  Capybara.disable_animation = '#with_animation a'
376
389
  @animation_session_with_matching_css = Capybara::Session.new(session.mode, TestApp.new)
377
390
  end
378
391
 
379
- after(:context) do # rubocop:disable RSpec/BeforeAfterAll
380
- @animation_session_with_matching_css = nil
381
- end
382
-
383
392
  it 'should disable CSS transitions' do
384
393
  @animation_session_with_matching_css.visit('with_animation')
385
394
  @animation_session_with_matching_css.click_link('transition me away')
@@ -395,16 +404,13 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
395
404
 
396
405
  context 'if we pass in css that does not match elements' do
397
406
  before(:context) do # rubocop:disable RSpec/BeforeAfterAll
407
+ skip "Safari doesn't support multiple sessions" if safari?(session)
398
408
  # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false,
399
409
  # it doesn't affect any of these tests because the settings are applied per-session
400
410
  Capybara.disable_animation = '.this-class-matches-nothing'
401
411
  @animation_session_without_matching_css = Capybara::Session.new(session.mode, TestApp.new)
402
412
  end
403
413
 
404
- after(:context) do # rubocop:disable RSpec/BeforeAfterAll
405
- @animation_session_without_matching_css = nil
406
- end
407
-
408
414
  it 'should not disable CSS transitions' do
409
415
  @animation_session_without_matching_css.visit('with_animation')
410
416
  @animation_session_without_matching_css.click_link('transition me away')
@@ -422,15 +428,54 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
422
428
  end
423
429
  end
424
430
  end
431
+ # rubocop:enable RSpec/InstanceVariable
425
432
 
426
433
  describe ':element selector' do
427
434
  it 'can find html5 svg elements' do
428
435
  session.visit('with_html5_svg')
429
436
  expect(session).to have_selector(:element, :svg)
430
- expect(session).to have_selector(:element, :rect, visible: true)
437
+ expect(session).to have_selector(:element, :rect, visible: :visible)
431
438
  expect(session).to have_selector(:element, :circle)
432
439
  expect(session).to have_selector(:element, :linearGradient, visible: :all)
433
440
  end
441
+
442
+ it 'can query attributes with strange characters' do
443
+ session.visit('/form')
444
+ expect(session).to have_selector(:element, "{custom}": true)
445
+ expect(session).to have_selector(:element, "{custom}": 'abcdef')
446
+ end
447
+ end
448
+
449
+ describe 'with react' do
450
+ context 'controlled components' do
451
+ it 'can set and clear a text field' do
452
+ skip "This test doesn't support older browsers" if ie?(session)
453
+
454
+ session.visit 'react'
455
+
456
+ session.fill_in('Name:', with: 'abc')
457
+ session.accept_prompt 'A name was submitted: abc' do
458
+ session.click_button('Submit')
459
+ end
460
+
461
+ session.fill_in('Name:', with: '')
462
+ session.accept_prompt(/A name was submitted: $/) do
463
+ session.click_button('Submit')
464
+ end
465
+ end
466
+
467
+ it 'works with rapid fill' do
468
+ skip "This test doesn't support older browsers" if ie?(session)
469
+
470
+ session.visit 'react'
471
+ long_string = (0...60).map { |i| ((i % 26) + 65).chr }.join
472
+
473
+ session.fill_in('Name:', with: long_string)
474
+ session.accept_prompt "A name was submitted: #{long_string}" do
475
+ session.click_button('Submit')
476
+ end
477
+ end
478
+ end
434
479
  end
435
480
  end
436
481
 
data/spec/spec_helper.rb CHANGED
@@ -1,52 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec/expectations'
4
+ require 'webdrivers' if ENV['CI'] || ENV['WEBDRIVERS']
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
4
13
  require 'capybara/spec/spec_helper'
5
- require 'webdrivers' if ENV['CI']
6
14
 
7
15
  module Capybara
8
16
  module SpecHelper
9
- def marionette?(session)
17
+ def firefox?(session)
10
18
  browser_name(session) == :firefox &&
11
- session.driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
19
+ ((defined?(::Selenium::WebDriver::VERSION) && (::Selenium::WebDriver::VERSION.to_f >= 4)) ||
20
+ session.driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities))
12
21
  end
13
22
 
14
- def marionette_lt?(version, session)
15
- marionette?(session) && (session.driver.browser.capabilities[:browser_version].to_f < version)
23
+ def firefox_lt?(version, session)
24
+ firefox?(session) && (session.driver.browser.capabilities[:browser_version].to_f < version)
16
25
  end
17
26
 
18
- def marionette_gte?(version, session)
19
- marionette?(session) && (session.driver.browser.capabilities[:browser_version].to_f >= version)
27
+ def firefox_gte?(version, session)
28
+ firefox?(session) && (session.driver.browser.capabilities[:browser_version].to_f >= version)
20
29
  end
21
30
 
22
31
  def chrome?(session)
23
32
  browser_name(session) == :chrome
24
33
  end
25
34
 
35
+ def chrome_version(session)
36
+ (session.driver.browser.capabilities[:browser_version] ||
37
+ session.driver.browser.capabilities[:version]).to_f
38
+ end
39
+
26
40
  def chrome_lt?(version, session)
27
- chrome?(session) && (session.driver.browser.capabilities[:version].to_f < version)
41
+ chrome?(session) && (chrome_version(session) < version)
28
42
  end
29
43
 
30
44
  def chrome_gte?(version, session)
31
- chrome?(session) && (session.driver.browser.capabilities[:version].to_f >= version)
45
+ chrome?(session) && (chrome_version(session) >= version)
32
46
  end
33
47
 
34
48
  def edge?(session)
49
+ browser_name(session).to_s.start_with?('edge')
50
+ end
51
+
52
+ def legacy_edge?(session)
35
53
  browser_name(session) == :edge
36
54
  end
37
55
 
56
+ def edge_lt?(version, session)
57
+ edge?(session) && (chrome_version(session) < version)
58
+ end
59
+
60
+ def edge_gte?(version, session)
61
+ edge?(session) && (chrome_version(session) >= version)
62
+ end
63
+
38
64
  def ie?(session)
39
65
  %i[internet_explorer ie].include?(browser_name(session))
40
66
  end
41
67
 
68
+ def safari?(session)
69
+ %i[safari Safari Safari_Technology_Preview].include?(browser_name(session))
70
+ end
71
+
42
72
  def browser_name(session)
43
73
  session.driver.browser.browser if session.respond_to?(:driver)
44
74
  end
75
+
76
+ def remote?(session)
77
+ session.driver.browser.is_a? ::Selenium::WebDriver::Remote::Driver
78
+ end
79
+
80
+ def self.log_selenium_driver_version(mod)
81
+ mod = mod::Service if ::Selenium::WebDriver::Service.respond_to? :driver_path
82
+ path = mod.driver_path
83
+ path = path.call if path.respond_to? :call
84
+ $stdout.puts `#{path.gsub(' ', '\ ')} --version`
85
+ end
45
86
  end
46
87
  end
47
88
 
48
89
  RSpec.configure do |config|
49
90
  Capybara::SpecHelper.configure(config)
91
+ config.expect_with :rspec do |expectations|
92
+ expectations.syntax = :expect
93
+ end
94
+ config.mock_with :rspec do |mocks|
95
+ mocks.verify_partial_doubles = true
96
+ end
50
97
  config.filter_run_including focus_: true unless ENV['CI']
51
98
  config.run_all_when_everything_filtered = true
99
+ config.after(:suite) { SeleniumStatistics.print_results }
52
100
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ # rubocop:disable RSpec/InstanceVariable
6
+ RSpec.describe Capybara::Selector::XPathBuilder do
7
+ let :builder do
8
+ ::Capybara::Selector::XPathBuilder.new(@xpath)
9
+ end
10
+
11
+ context 'add_attribute_conditions' do
12
+ it 'adds a single string condition to a single selector' do
13
+ @xpath = './/div'
14
+ selector = builder.add_attribute_conditions(random: 'abc')
15
+ expect(selector).to eq %((.//div)[(./@random = 'abc')])
16
+ end
17
+
18
+ it 'adds multiple string conditions to a single selector' do
19
+ @xpath = './/div'
20
+ selector = builder.add_attribute_conditions(random: 'abc', other: 'def')
21
+ expect(selector).to eq %(((.//div)[(./@random = 'abc')])[(./@other = 'def')])
22
+ end
23
+
24
+ it 'adds a single string condition to a multiple selector' do
25
+ @xpath = XPath.descendant(:div, :ul)
26
+ selector = builder.add_attribute_conditions(random: 'abc')
27
+ expect(selector.to_s).to eq @xpath[XPath.attr(:random) == 'abc'].to_s
28
+ end
29
+
30
+ it 'adds multiple string conditions to a multiple selector' do
31
+ @xpath = XPath.descendant(:div, :ul)
32
+ selector = builder.add_attribute_conditions(random: 'abc', other: 'def')
33
+ expect(selector.to_s).to eq %(.//*[self::div | self::ul][(./@random = 'abc')][(./@other = 'def')])
34
+ end
35
+
36
+ it 'adds simple regexp conditions to a single selector' do
37
+ @xpath = XPath.descendant(:div)
38
+ selector = builder.add_attribute_conditions(random: /abc/, other: /def/)
39
+ expect(selector.to_s).to eq %(.//div[./@random[contains(., 'abc')]][./@other[contains(., 'def')]])
40
+ end
41
+
42
+ it 'adds wildcard regexp conditions to a single selector' do
43
+ @xpath = './/div'
44
+ selector = builder.add_attribute_conditions(random: /abc.*def/, other: /def.*ghi/)
45
+ expect(selector).to eq %(((.//div)[./@random[(contains(., 'abc') and contains(., 'def'))]])[./@other[(contains(., 'def') and contains(., 'ghi'))]])
46
+ end
47
+
48
+ it 'adds alternated regexp conditions to a single selector' do
49
+ @xpath = XPath.descendant(:div)
50
+ selector = builder.add_attribute_conditions(random: /abc|def/, other: /def|ghi/)
51
+ expect(selector.to_s).to eq %(.//div[./@random[(contains(., 'abc') or contains(., 'def'))]][./@other[(contains(., 'def') or contains(., 'ghi'))]])
52
+ end
53
+
54
+ it 'adds alternated regexp conditions to a multiple selector' do
55
+ @xpath = XPath.descendant(:div, :ul)
56
+ selector = builder.add_attribute_conditions(other: /def.*ghi|jkl/)
57
+ expect(selector.to_s).to eq %(.//*[self::div | self::ul][./@other[((contains(., 'def') and contains(., 'ghi')) or contains(., 'jkl'))]])
58
+ end
59
+
60
+ it "returns original selector when regexp can't be substringed" do
61
+ @xpath = './/div'
62
+ selector = builder.add_attribute_conditions(other: /.+/)
63
+ expect(selector).to eq '(.//div)[./@other]'
64
+ end
65
+
66
+ context ':class' do
67
+ it 'handles string' do
68
+ @xpath = './/a'
69
+ selector = builder.add_attribute_conditions(class: 'my_class')
70
+ expect(selector).to eq %((.//a)[contains(concat(' ', normalize-space(./@class), ' '), ' my_class ')])
71
+ end
72
+
73
+ it 'handles negated strings' do
74
+ @xpath = XPath.descendant(:a)
75
+ selector = builder.add_attribute_conditions(class: '!my_class')
76
+ expect(selector.to_s).to eq @xpath[!XPath.attr(:class).contains_word('my_class')].to_s
77
+ end
78
+
79
+ it 'handles array of strings' do
80
+ @xpath = './/a'
81
+ selector = builder.add_attribute_conditions(class: %w[my_class my_other_class])
82
+ expect(selector).to eq %((.//a)[(contains(concat(' ', normalize-space(./@class), ' '), ' my_class ') and contains(concat(' ', normalize-space(./@class), ' '), ' my_other_class '))])
83
+ end
84
+
85
+ it 'handles array of string when negated included' do
86
+ @xpath = XPath.descendant(:a)
87
+ selector = builder.add_attribute_conditions(class: %w[my_class !my_other_class])
88
+ expect(selector.to_s).to eq @xpath[XPath.attr(:class).contains_word('my_class') & !XPath.attr(:class).contains_word('my_other_class')].to_s
89
+ end
90
+ end
91
+ end
92
+ end
93
+ # rubocop:enable RSpec/InstanceVariable