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
@@ -5,19 +5,19 @@ require 'spec_helper'
5
5
  RSpec.describe Capybara do
6
6
  describe 'Selectors' do
7
7
  let :string do
8
- Capybara.string <<-STRING
8
+ described_class.string <<-STRING
9
9
  <html>
10
10
  <head>
11
11
  <title>selectors</title>
12
12
  </head>
13
13
  <body>
14
- <div class="a" id="page">
15
- <div class="b" id="content">
16
- <h1 class="a">Totally awesome</h1>
14
+ <div class="aa" id="page">
15
+ <div class="bb" id="content">
16
+ <h1 class="aa">Totally awesome</h1>
17
17
  <p>Yes it is</p>
18
18
  </div>
19
- <p class="b c">Some Content</p>
20
- <p class="b d"></p>
19
+ <p class="bb cc">Some Content</p>
20
+ <p class="bb dd !mine"></p>
21
21
  </div>
22
22
  <div id="#special">
23
23
  </div>
@@ -31,6 +31,7 @@ RSpec.describe Capybara do
31
31
  <input type="file" id="file" class=".special file"/>
32
32
  <input type="hidden" id="hidden_field" value="this is hidden"/>
33
33
  <input type="submit" value="click me" title="submit button"/>
34
+ <input type="button" value="don't click me" title="Other button 1" style="line-height: 30px;"/>
34
35
  <a href="#">link</a>
35
36
  <fieldset></fieldset>
36
37
  <select id="select">
@@ -41,29 +42,67 @@ RSpec.describe Capybara do
41
42
  <table>
42
43
  <tr><td></td></tr>
43
44
  </table>
45
+ <table id="rows">
46
+ <tr>
47
+ <td>A</td><td>B</td><td>C</td>
48
+ </tr>
49
+ <tr>
50
+ <td>D</td><td>E</td><td>F</td>
51
+ </tr>
52
+ </table>
53
+ <table id="cols">
54
+ <tbody>
55
+ <tr>
56
+ <td>A</td><td>D</td>
57
+ </tr>
58
+ <tr>
59
+ <td>B</td><td>E</td>
60
+ </tr>
61
+ <tr>
62
+ <td>C</td><td>F</td>
63
+ </tr>
64
+ </tbody>
65
+ </table>
44
66
  </body>
45
67
  </html>
46
68
  STRING
47
69
  end
48
70
 
49
71
  before do
50
- Capybara.add_selector :custom_selector do
72
+ described_class.add_selector :custom_selector do
51
73
  css { |css_class| "div.#{css_class}" }
52
74
  node_filter(:not_empty, boolean: true, default: true, skip_if: :all) { |node, value| value ^ (node.text == '') }
53
75
  end
54
76
 
55
- Capybara.add_selector :custom_css_selector do
56
- css { |selector| selector }
77
+ described_class.add_selector :custom_css_selector do
78
+ css(:name, :other_name) do |selector, name: nil, **|
79
+ selector ||= ''
80
+ selector += "[name='#{name}']" if name
81
+ selector
82
+ end
83
+
84
+ expression_filter(:placeholder) do |expr, val|
85
+ expr + "[placeholder='#{val}']"
86
+ end
87
+
88
+ expression_filter(:value) do |expr, val|
89
+ expr + "[value='#{val}']"
90
+ end
91
+
92
+ expression_filter(:title) do |expr, val|
93
+ expr + "[title='#{val}']"
94
+ end
57
95
  end
58
96
 
59
- Capybara.add_selector :custom_xpath_selector do
60
- xpath { |selector| selector }
97
+ described_class.add_selector :custom_xpath_selector do
98
+ xpath(:valid1, :valid2) { |selector| selector }
99
+ match { |value| value == 'match_me' }
61
100
  end
62
101
  end
63
102
 
64
103
  it 'supports `filter` as an alias for `node_filter`' do
65
104
  expect do
66
- Capybara.add_selector :filter_alias_selector do
105
+ described_class.add_selector :filter_alias_selector do
67
106
  css { |_unused| 'div' }
68
107
  filter(:something) { |_node, _value| true }
69
108
  end
@@ -72,7 +111,7 @@ RSpec.describe Capybara do
72
111
 
73
112
  describe 'adding a selector' do
74
113
  it 'can set default visiblity' do
75
- Capybara.add_selector :hidden_field do
114
+ described_class.add_selector :hidden_field do
76
115
  visible :hidden
77
116
  css { |_sel| 'input[type="hidden"]' }
78
117
  end
@@ -84,22 +123,113 @@ RSpec.describe Capybara do
84
123
 
85
124
  describe 'modify_selector' do
86
125
  it 'allows modifying a selector' do
87
- el = string.find(:custom_selector, 'a')
126
+ el = string.find(:custom_selector, 'aa')
88
127
  expect(el.tag_name).to eq 'div'
89
- Capybara.modify_selector :custom_selector do
128
+ described_class.modify_selector :custom_selector do
90
129
  css { |css_class| "h1.#{css_class}" }
91
130
  end
92
- el = string.find(:custom_selector, 'a')
131
+ el = string.find(:custom_selector, 'aa')
93
132
  expect(el.tag_name).to eq 'h1'
94
133
  end
95
134
 
96
135
  it "doesn't change existing filters" do
97
- Capybara.modify_selector :custom_selector do
136
+ described_class.modify_selector :custom_selector do
98
137
  css { |css_class| "p.#{css_class}" }
99
138
  end
100
- expect(string).to have_selector(:custom_selector, 'b', count: 1)
101
- expect(string).to have_selector(:custom_selector, 'b', not_empty: false, count: 1)
102
- expect(string).to have_selector(:custom_selector, 'b', not_empty: :all, count: 2)
139
+ expect(string).to have_selector(:custom_selector, 'bb', count: 1)
140
+ expect(string).to have_selector(:custom_selector, 'bb', not_empty: false, count: 1)
141
+ expect(string).to have_selector(:custom_selector, 'bb', not_empty: :all, count: 2)
142
+ end
143
+ end
144
+
145
+ describe '::[]' do
146
+ it 'can find a selector' do
147
+ expect(Capybara::Selector[:field]).not_to be_nil
148
+ end
149
+
150
+ it 'raises if no selector found' do
151
+ expect { Capybara::Selector[:no_exist] }.to raise_error(ArgumentError, /Unknown selector type/)
152
+ end
153
+ end
154
+
155
+ describe '::for' do
156
+ it 'finds selector that matches the locator' do
157
+ expect(Capybara::Selector.for('match_me').name).to eq :custom_xpath_selector
158
+ end
159
+
160
+ it 'returns nil if no match' do
161
+ expect(Capybara::Selector.for('nothing')).to be nil
162
+ end
163
+ end
164
+
165
+ describe 'xpath' do
166
+ it 'uses filter names passed in' do
167
+ described_class.add_selector :test do
168
+ xpath(:something, :other) { |_locator| XPath.descendant }
169
+ end
170
+ selector = Capybara::Selector.new :test, config: nil, format: nil
171
+
172
+ expect(selector.expression_filters.keys).to include(:something, :other)
173
+ end
174
+
175
+ it 'gets filter names from block if none passed to xpath method' do
176
+ described_class.add_selector :test do
177
+ xpath { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" }
178
+ end
179
+ selector = Capybara::Selector.new :test, config: nil, format: nil
180
+
181
+ expect(selector.expression_filters.keys).to include(:valid3, :valid4)
182
+ end
183
+
184
+ it 'ignores block parameters if names passed in' do
185
+ described_class.add_selector :test do
186
+ xpath(:valid1) { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" }
187
+ end
188
+ selector = Capybara::Selector.new :test, config: nil, format: nil
189
+
190
+ expect(selector.expression_filters.keys).to include(:valid1)
191
+ expect(selector.expression_filters.keys).not_to include(:valid3, :valid4)
192
+ end
193
+ end
194
+
195
+ describe 'css' do
196
+ it "supports filters specified in 'css' definition" do
197
+ expect(string).to have_selector(:custom_css_selector, 'input', name: 'form[my_text_input]')
198
+ expect(string).to have_no_selector(:custom_css_selector, 'input', name: 'form[not_my_text_input]')
199
+ end
200
+
201
+ it 'supports explicitly defined expression filters' do
202
+ expect(string).to have_selector(:custom_css_selector, placeholder: 'my text')
203
+ expect(string).to have_no_selector(:custom_css_selector, placeholder: 'not my text')
204
+ expect(string).to have_selector(:custom_css_selector, value: 'click me', title: 'submit button')
205
+ end
206
+
207
+ it 'uses filter names passed in' do
208
+ described_class.add_selector :test do
209
+ css(:name, :other_name) { |_locator| '' }
210
+ end
211
+ selector = Capybara::Selector.new :test, config: nil, format: nil
212
+
213
+ expect(selector.expression_filters.keys).to include(:name, :other_name)
214
+ end
215
+
216
+ it 'gets filter names from block if none passed to css method' do
217
+ described_class.add_selector :test do
218
+ css { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" }
219
+ end
220
+ selector = Capybara::Selector.new :test, config: nil, format: nil
221
+
222
+ expect(selector.expression_filters.keys).to include(:valid3, :valid4)
223
+ end
224
+
225
+ it 'ignores block parameters if names passed in' do
226
+ described_class.add_selector :test do
227
+ css(:valid1) { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" }
228
+ end
229
+ selector = Capybara::Selector.new :test, config: nil, format: nil
230
+
231
+ expect(selector.expression_filters.keys).to include(:valid1)
232
+ expect(selector.expression_filters.keys).not_to include(:valid3, :valid4)
103
233
  end
104
234
  end
105
235
 
@@ -147,19 +277,33 @@ RSpec.describe Capybara do
147
277
  expect { string.find(:custom_css_selector, 'div', id: XPath.contains('peci')) }
148
278
  .to raise_error(ArgumentError, /not supported/)
149
279
  end
280
+
281
+ it 'accepts Regexp for xpath based selectors' do
282
+ expect(string.find(:custom_xpath_selector, './/div', id: /peci/)[:id]).to eq '#special'
283
+ expect(string.find(:custom_xpath_selector, './/div', id: /pEcI/i)[:id]).to eq '#special'
284
+ end
285
+
286
+ it 'accepts Regexp for css based selectors' do
287
+ expect(string.find(:custom_css_selector, 'div', id: /sp.*al/)[:id]).to eq '#special'
288
+ end
150
289
  end
151
290
 
152
291
  context 'with :class option' do
153
292
  it 'works with compound css selectors' do
154
- expect(string.all(:custom_css_selector, 'div, h1', class: 'a').size).to eq 2
155
- expect(string.all(:custom_css_selector, 'h1, div', class: 'a').size).to eq 2
293
+ expect(string.all(:custom_css_selector, 'div, h1', class: 'aa').size).to eq 2
294
+ expect(string.all(:custom_css_selector, 'h1, div', class: 'aa').size).to eq 2
156
295
  end
157
296
 
158
297
  it 'handles negated classes' do
159
- expect(string.all(:custom_css_selector, 'div, p', class: ['b', '!c']).size).to eq 2
160
- expect(string.all(:custom_css_selector, 'div, p', class: ['!c', '!d', 'b']).size).to eq 1
161
- expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['b', '!c']).size).to eq 2
162
- expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['!c', '!d', 'b']).size).to eq 1
298
+ expect(string.all(:custom_css_selector, 'div, p', class: ['bb', '!cc']).size).to eq 2
299
+ expect(string.all(:custom_css_selector, 'div, p', class: ['!cc', '!dd', 'bb']).size).to eq 1
300
+ expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['bb', '!cc']).size).to eq 2
301
+ expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['!cc', '!dd', 'bb']).size).to eq 1
302
+ end
303
+
304
+ it 'handles classes starting with ! by requiring negated negated first' do
305
+ expect(string.all(:custom_css_selector, 'div, p', class: ['!!!mine']).size).to eq 1
306
+ expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['!!!mine']).size).to eq 1
163
307
  end
164
308
 
165
309
  it "works with 'special' characters" do
@@ -176,6 +320,25 @@ RSpec.describe Capybara do
176
320
  expect { string.find(:custom_css_selector, 'div', class: XPath.contains('random')) }
177
321
  .to raise_error(ArgumentError, /not supported/)
178
322
  end
323
+
324
+ it 'accepts Regexp for XPath based selectors' do
325
+ expect(string.find(:custom_xpath_selector, './/div', class: /dom wor/)[:id]).to eq 'random_words'
326
+ expect(string.find(:custom_xpath_selector, './/div', class: /dOm WoR/i)[:id]).to eq 'random_words'
327
+ end
328
+
329
+ it 'accepts Regexp for CSS base selectors' do
330
+ expect(string.find(:custom_css_selector, 'div', class: /random/)[:id]).to eq 'random_words'
331
+ end
332
+ end
333
+
334
+ context 'with :style option' do
335
+ it 'accepts string for CSS based selectors' do
336
+ expect(string.find(:custom_css_selector, 'input', style: 'line-height: 30px;')[:title]).to eq 'Other button 1'
337
+ end
338
+
339
+ it 'accepts Regexp for CSS base selectors' do
340
+ expect(string.find(:custom_css_selector, 'input', style: /30px/)[:title]).to eq 'Other button 1'
341
+ end
179
342
  end
180
343
 
181
344
  # :css, :xpath, :id, :field, :fieldset, :link, :button, :link_or_button, :fillable_field, :radio_button, :checkbox, :select,
@@ -196,6 +359,9 @@ RSpec.describe Capybara do
196
359
  describe ':id selector' do
197
360
  it 'finds by locator' do
198
361
  expect(string.find(:id, 'my_text_input')[:name]).to eq 'form[my_text_input]'
362
+ expect(string.find(:id, /my_text_input/)[:name]).to eq 'form[my_text_input]'
363
+ expect(string.find(:id, /_text_/)[:name]).to eq 'form[my_text_input]'
364
+ expect(string.find(:id, /i[nmo]/)[:name]).to eq 'form[my_text_input]'
199
365
  end
200
366
  end
201
367
 
@@ -206,10 +372,14 @@ RSpec.describe Capybara do
206
372
  expect(string.find(:field, 'form[my_text_input]')[:id]).to eq 'my_text_input'
207
373
  end
208
374
 
209
- it 'finds by id' do
375
+ it 'finds by id string' do
210
376
  expect(string.find(:field, id: 'my_text_input')[:name]).to eq 'form[my_text_input]'
211
377
  end
212
378
 
379
+ it 'finds by id regexp' do
380
+ expect(string.find(:field, id: /my_text_inp/)[:name]).to eq 'form[my_text_input]'
381
+ end
382
+
213
383
  it 'finds by name' do
214
384
  expect(string.find(:field, name: 'form[my_text_input]')[:id]).to eq 'my_text_input'
215
385
  end
@@ -257,6 +427,18 @@ RSpec.describe Capybara do
257
427
  expect(string.find(:element, 'input', type: 'submit').value).to eq 'click me'
258
428
  end
259
429
 
430
+ it 'supports regexp matching' do
431
+ expect(string.find(:element, 'input', type: /sub/).value).to eq 'click me'
432
+ expect(string.find(:element, 'input', title: /sub\w.*button/).value).to eq 'click me'
433
+ expect(string.find(:element, 'input', title: /sub.* b.*ton/).value).to eq 'click me'
434
+ expect(string.find(:element, 'input', title: /sub.*mit.*/).value).to eq 'click me'
435
+ expect(string.find(:element, 'input', title: /^submit button$/).value).to eq 'click me'
436
+ expect(string.find(:element, 'input', title: /^(?:submit|other) button$/).value).to eq 'click me'
437
+ expect(string.find(:element, 'input', title: /SuB.*mIt/i).value).to eq 'click me'
438
+ expect(string.find(:element, 'input', title: /^Su.*Bm.*It/i).value).to eq 'click me'
439
+ expect(string.find(:element, 'input', title: /^Ot.*he.*r b.*\d/i).value).to eq "don't click me"
440
+ end
441
+
260
442
  it 'still works with system keys' do
261
443
  expect { string.all(:element, 'input', type: 'submit', count: 1) }.not_to raise_error
262
444
  end
@@ -289,7 +471,41 @@ RSpec.describe Capybara do
289
471
  expect(string.find(:element, 'input', type: XPath.ends_with('ext'))[:type]).to eq 'text'
290
472
  expect(string.find(:element, 'input', type: XPath.contains('ckb'))[:type]).to eq 'checkbox'
291
473
  expect(string.find(:element, 'input', title: XPath.contains_word('submit'))[:type]).to eq 'submit'
292
- expect(string.find(:element, 'input', title: XPath.contains_word('button'))[:type]).to eq 'submit'
474
+ expect(string.find(:element, 'input', title: XPath.contains_word('button 1'))[:type]).to eq 'button'
475
+ end
476
+ end
477
+
478
+ describe ':link_or_button selector' do
479
+ around do |example|
480
+ described_class.modify_selector(:link_or_button) do
481
+ expression_filter(:random) { |xpath, _| xpath } # do nothing filter
482
+ end
483
+ example.run
484
+ Capybara::Selector[:link_or_button].expression_filters.delete(:random)
485
+ end
486
+
487
+ it 'should not find links when disabled == true' do
488
+ expect(string.all(:link_or_button, disabled: true).size).to eq 0
489
+ end
490
+
491
+ context 'when modified' do
492
+ it 'should still work' do
493
+ filter = Capybara::Selector[:link_or_button].expression_filters[:random]
494
+ allow(filter).to receive(:apply_filter).and_call_original
495
+
496
+ expect(string.find(:link_or_button, 'click me', random: 'blah').value).to eq 'click me'
497
+ expect(filter).to have_received(:apply_filter).with(anything, :random, 'blah', anything)
498
+ end
499
+ end
500
+ end
501
+
502
+ describe ':table selector' do
503
+ it 'finds by rows' do
504
+ expect(string.find(:table, with_rows: [%w[D E F]])[:id]).to eq 'rows'
505
+ end
506
+
507
+ it 'finds by columns' do
508
+ expect(string.find(:table, with_cols: [%w[A B C]])[:id]).to eq 'cols'
293
509
  end
294
510
  end
295
511
  end
@@ -3,36 +3,55 @@
3
3
  require 'spec_helper'
4
4
  require 'selenium-webdriver'
5
5
  require 'shared_selenium_session'
6
+ require 'shared_selenium_node'
6
7
  require 'rspec/shared_spec_matchers'
7
8
 
8
- CHROME_DRIVER = ENV['HEADLESS'] ? :selenium_chrome_headless : :selenium_chrome
9
+ CHROME_DRIVER = :selenium_chrome
9
10
 
10
- # if ENV['HEADLESS'] && ENV['TRAVIS']
11
- # Selenium::WebDriver::Chrome.path='/usr/bin/google-chrome-beta'
12
- # end
11
+ Selenium::WebDriver::Chrome.path = '/usr/bin/google-chrome-beta' if ENV['CI'] && ENV['CHROME_BETA']
12
+
13
+ browser_options = ::Selenium::WebDriver::Chrome::Options.new
14
+ browser_options.headless! if ENV['HEADLESS']
15
+ browser_options.add_option(:w3c, ENV['W3C'] != 'false')
16
+ # Chromedriver 77 requires setting this for headless mode on linux
17
+ # Different versions of Chrome/selenium-webdriver require setting differently - jus set them all
18
+ browser_options.add_preference('download.default_directory', Capybara.save_path)
19
+ browser_options.add_preference(:download, default_directory: Capybara.save_path)
13
20
 
14
21
  Capybara.register_driver :selenium_chrome do |app|
15
- driver = Capybara::Selenium::Driver.new(app, browser: :chrome)
16
- driver.browser.download_path = Capybara.save_path
17
- driver
22
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options, timeout: 30).tap do |driver|
23
+ # Set download dir for Chrome < 77
24
+ driver.browser.download_path = Capybara.save_path
25
+ end
18
26
  end
19
27
 
20
- Capybara.register_driver :selenium_chrome_headless do |app|
21
- browser_options = ::Selenium::WebDriver::Chrome::Options.new
22
- browser_options.args << '--headless'
23
- browser_options.args << '--disable-gpu' if Gem.win_platform?
24
- driver = Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options)
25
- driver.browser.download_path = Capybara.save_path
26
- driver
28
+ Capybara.register_driver :selenium_chrome_not_clear_storage do |app|
29
+ chrome_options = {
30
+ browser: :chrome,
31
+ options: browser_options
32
+ }
33
+ Capybara::Selenium::Driver.new(app, **chrome_options.merge(clear_local_storage: false, clear_session_storage: false))
27
34
  end
28
35
 
29
- Capybara.register_driver :selenium_chrome_clear_storage do |app|
36
+ Capybara.register_driver :selenium_chrome_not_clear_session_storage do |app|
30
37
  chrome_options = {
31
38
  browser: :chrome,
32
- options: ::Selenium::WebDriver::Chrome::Options.new
39
+ options: browser_options
33
40
  }
34
- chrome_options[:options].args << 'headless' if ENV['HEADLESS']
35
- Capybara::Selenium::Driver.new(app, chrome_options.merge(clear_local_storage: true, clear_session_storage: true))
41
+ Capybara::Selenium::Driver.new(app, **chrome_options.merge(clear_session_storage: false))
42
+ end
43
+
44
+ Capybara.register_driver :selenium_chrome_not_clear_local_storage do |app|
45
+ chrome_options = {
46
+ browser: :chrome,
47
+ options: browser_options
48
+ }
49
+ Capybara::Selenium::Driver.new(app, **chrome_options.merge(clear_local_storage: false))
50
+ end
51
+
52
+ Capybara.register_driver :selenium_driver_subclass_with_chrome do |app|
53
+ subclass = Class.new(Capybara::Selenium::Driver)
54
+ subclass.new(app, browser: :chrome, options: browser_options, timeout: 30)
36
55
  end
37
56
 
38
57
  module TestSessions
@@ -40,76 +59,128 @@ module TestSessions
40
59
  end
41
60
 
42
61
  skipped_tests = %i[response_headers status_code trigger]
43
- # skip window tests when headless for now - closing a window not supported by chromedriver/chrome
44
- skipped_tests << :windows if ENV['TRAVIS'] && (ENV['SKIP_WINDOW'] || ENV['HEADLESS'])
45
62
 
46
- $stdout.puts `#{Selenium::WebDriver::Chrome.driver_path} --version` if ENV['CI']
63
+ Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::Chrome) if ENV['CI']
47
64
 
48
65
  Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_DRIVER.to_s, capybara_skip: skipped_tests do |example|
49
66
  case example.metadata[:full_description]
50
67
  when /#click_link can download a file$/
51
68
  skip 'Need to figure out testing of file downloading on windows platform' if Gem.win_platform?
69
+ when /Capybara::Session selenium_chrome Capybara::Window#maximize/
70
+ pending "Chrome headless doesn't support maximize" if ENV['HEADLESS']
71
+ when /Capybara::Window#fullscreen should be able to fullscreen the window/
72
+ skip 'Chromedriver hangs on attempts to fullscreen in headless mode' if ENV['HEADLESS']
73
+ when /node #right_click delay should delay the mouse up/
74
+ skip "Legacy selenium doesn't support separate right button down/up" if ENV['W3C'] == 'false'
52
75
  end
53
76
  end
54
77
 
55
78
  RSpec.describe 'Capybara::Session with chrome' do
56
79
  include Capybara::SpecHelper
57
- include_examples 'Capybara::Session', TestSessions::Chrome, CHROME_DRIVER
58
- include_examples Capybara::RSpecMatchers, TestSessions::Chrome, CHROME_DRIVER
80
+ ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples|
81
+ include_examples examples, TestSessions::Chrome, CHROME_DRIVER
82
+ end
59
83
 
60
84
  context 'storage' do
61
85
  describe '#reset!' do
62
- it 'does not clear either storage by default' do
63
- @session = TestSessions::Chrome
64
- @session.visit('/with_js')
65
- @session.find(:css, '#set-storage').click
66
- @session.reset!
67
- @session.visit('/with_js')
68
- # expect(@session.driver.browser.local_storage.keys).not_to be_empty
69
- # expect(@session.driver.browser.session_storage.keys).not_to be_empty
70
- expect(@session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
71
- expect(@session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
86
+ it 'clears storage by default' do
87
+ session = TestSessions::Chrome
88
+ session.visit('/with_js')
89
+ session.find(:css, '#set-storage').click
90
+ session.reset!
91
+ session.visit('/with_js')
92
+ expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty
93
+ expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
94
+ end
95
+
96
+ it 'does not clear storage when false' do
97
+ session = Capybara::Session.new(:selenium_chrome_not_clear_storage, TestApp)
98
+ session.visit('/with_js')
99
+ session.find(:css, '#set-storage').click
100
+ session.reset!
101
+ session.visit('/with_js')
102
+ expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
103
+ expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
72
104
  end
73
105
 
74
- it 'clears storage when set' do
75
- @session = Capybara::Session.new(:selenium_chrome_clear_storage, TestApp)
76
- @session.visit('/with_js')
77
- @session.find(:css, '#set-storage').click
78
- @session.reset!
79
- @session.visit('/with_js')
80
- # expect(@session.driver.browser.local_storage.keys).to be_empty
81
- # expect(@session.driver.browser.session_storage.keys).to be_empty
82
- expect(@session.evaluate_script('Object.keys(localStorage)')).to be_empty
83
- expect(@session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
106
+ it 'can not clear session storage' do
107
+ session = Capybara::Session.new(:selenium_chrome_not_clear_session_storage, TestApp)
108
+ session.visit('/with_js')
109
+ session.find(:css, '#set-storage').click
110
+ session.reset!
111
+ session.visit('/with_js')
112
+ expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty
113
+ expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
114
+ end
115
+
116
+ it 'can not clear local storage' do
117
+ session = Capybara::Session.new(:selenium_chrome_not_clear_local_storage, TestApp)
118
+ session.visit('/with_js')
119
+ session.find(:css, '#set-storage').click
120
+ session.reset!
121
+ session.visit('/with_js')
122
+ expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
123
+ expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
84
124
  end
85
125
  end
86
126
  end
87
127
 
128
+ context 'timeout' do
129
+ it 'sets the http client read timeout' do
130
+ expect(TestSessions::Chrome.driver.browser.send(:bridge).http.read_timeout).to eq 30
131
+ end
132
+ end
133
+
88
134
  describe 'filling in Chrome-specific date and time fields with keystrokes' do
89
135
  let(:datetime) { Time.new(1983, 6, 19, 6, 30) }
136
+ let(:session) { TestSessions::Chrome }
90
137
 
91
138
  before do
92
- @session = TestSessions::Chrome
93
- @session.visit('/form')
139
+ session.visit('/form')
94
140
  end
95
141
 
96
142
  it 'should fill in a date input with a String' do
97
- @session.fill_in('form_date', with: "06/19/1983")
98
- @session.click_button('awesome')
99
- expect(Date.parse(extract_results(@session)['date'])).to eq datetime.to_date
143
+ session.fill_in('form_date', with: '06/19/1983')
144
+ session.click_button('awesome')
145
+ expect(Date.parse(extract_results(session)['date'])).to eq datetime.to_date
100
146
  end
101
147
 
102
148
  it 'should fill in a time input with a String' do
103
- @session.fill_in('form_time', with: "06:30A")
104
- @session.click_button('awesome')
105
- results = extract_results(@session)['time']
149
+ session.fill_in('form_time', with: '06:30A')
150
+ session.click_button('awesome')
151
+ results = extract_results(session)['time']
106
152
  expect(Time.parse(results).strftime('%r')).to eq datetime.strftime('%r')
107
153
  end
108
154
 
109
155
  it 'should fill in a datetime input with a String' do
110
- @session.fill_in('form_datetime', with: "06/19/1983\t06:30A")
111
- @session.click_button('awesome')
112
- expect(Time.parse(extract_results(@session)['datetime'])).to eq datetime
156
+ session.fill_in('form_datetime', with: "06/19/1983\t06:30A")
157
+ session.click_button('awesome')
158
+ expect(Time.parse(extract_results(session)['datetime'])).to eq datetime
159
+ end
160
+ end
161
+
162
+ describe 'using subclass of selenium driver' do
163
+ it 'works' do
164
+ session = Capybara::Session.new(:selenium_driver_subclass_with_chrome, TestApp)
165
+ session.visit('/form')
166
+ expect(session).to have_current_path('/form')
167
+ end
168
+ end
169
+
170
+ describe 'log access' do
171
+ before { skip 'Only makes sense in W3C mode' if ENV['W3C'] == 'false' }
172
+
173
+ it 'does not error getting log types' do
174
+ skip if Gem::Version.new(session.driver.browser.capabilities['chrome']['chromedriverVersion'].split[0]) < Gem::Version.new('75.0.3770.90')
175
+ expect do
176
+ session.driver.browser.manage.logs.available_types
177
+ end.not_to raise_error
178
+ end
179
+
180
+ it 'does not error when getting logs' do
181
+ expect do
182
+ session.driver.browser.manage.logs.get(:browser)
183
+ end.not_to raise_error
113
184
  end
114
185
  end
115
186
  end