capybara 3.36.0 → 3.39.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +94 -4
- data/README.md +24 -12
- data/lib/capybara/driver/node.rb +4 -0
- data/lib/capybara/dsl.rb +4 -10
- data/lib/capybara/helpers.rb +6 -2
- data/lib/capybara/minitest/spec.rb +2 -2
- data/lib/capybara/node/actions.rb +4 -4
- data/lib/capybara/node/base.rb +2 -1
- data/lib/capybara/node/element.rb +12 -1
- data/lib/capybara/node/finders.rb +10 -1
- data/lib/capybara/node/whitespace_normalizer.rb +81 -0
- data/lib/capybara/queries/base_query.rb +2 -2
- data/lib/capybara/queries/selector_query.rb +24 -9
- data/lib/capybara/queries/text_query.rb +1 -1
- data/lib/capybara/rack_test/browser.rb +63 -8
- data/lib/capybara/rack_test/driver.rb +4 -4
- data/lib/capybara/rack_test/form.rb +29 -7
- data/lib/capybara/rack_test/node.rb +19 -16
- data/lib/capybara/registration_container.rb +0 -3
- data/lib/capybara/registrations/drivers.rb +3 -3
- data/lib/capybara/registrations/servers.rb +30 -10
- data/lib/capybara/rspec/matcher_proxies.rb +3 -3
- data/lib/capybara/rspec/matchers/base.rb +8 -6
- data/lib/capybara/rspec/matchers/compound.rb +1 -1
- data/lib/capybara/rspec/matchers/have_selector.rb +4 -4
- data/lib/capybara/rspec/matchers.rb +14 -14
- data/lib/capybara/selector/definition/link.rb +2 -1
- data/lib/capybara/selector/definition.rb +3 -2
- data/lib/capybara/selector/filter_set.rb +4 -5
- data/lib/capybara/selector/regexp_disassembler.rb +2 -5
- data/lib/capybara/selector/selector.rb +5 -1
- data/lib/capybara/selenium/driver.rb +11 -4
- data/lib/capybara/selenium/driver_specializations/edge_driver.rb +8 -4
- data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
- data/lib/capybara/selenium/extensions/html5_drag.rb +5 -4
- data/lib/capybara/selenium/logger_suppressor.rb +6 -2
- data/lib/capybara/selenium/node.rb +72 -25
- data/lib/capybara/selenium/nodes/chrome_node.rb +5 -1
- data/lib/capybara/selenium/nodes/edge_node.rb +24 -2
- data/lib/capybara/selenium/nodes/firefox_node.rb +2 -2
- data/lib/capybara/selenium/nodes/safari_node.rb +2 -2
- data/lib/capybara/selenium/patches/action_pauser.rb +3 -3
- data/lib/capybara/selenium/patches/atoms.rb +1 -1
- data/lib/capybara/selenium/patches/pause_duration_fix.rb +1 -1
- data/lib/capybara/server/animation_disabler.rb +36 -25
- data/lib/capybara/server/middleware.rb +1 -1
- data/lib/capybara/session/config.rb +4 -2
- data/lib/capybara/session.rb +19 -30
- data/lib/capybara/spec/public/test.js +4 -0
- data/lib/capybara/spec/session/all_spec.rb +6 -8
- data/lib/capybara/spec/session/assert_text_spec.rb +17 -17
- data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
- data/lib/capybara/spec/session/check_spec.rb +1 -0
- data/lib/capybara/spec/session/click_link_spec.rb +11 -0
- data/lib/capybara/spec/session/current_scope_spec.rb +1 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
- data/lib/capybara/spec/session/find_link_spec.rb +10 -0
- data/lib/capybara/spec/session/find_spec.rb +7 -1
- data/lib/capybara/spec/session/first_spec.rb +1 -1
- data/lib/capybara/spec/session/frame/within_frame_spec.rb +2 -0
- data/lib/capybara/spec/session/has_all_selectors_spec.rb +5 -5
- data/lib/capybara/spec/session/has_ancestor_spec.rb +2 -2
- data/lib/capybara/spec/session/has_any_selectors_spec.rb +2 -2
- data/lib/capybara/spec/session/has_button_spec.rb +6 -0
- data/lib/capybara/spec/session/has_current_path_spec.rb +3 -3
- data/lib/capybara/spec/session/has_field_spec.rb +1 -1
- data/lib/capybara/spec/session/has_link_spec.rb +16 -0
- data/lib/capybara/spec/session/has_none_selectors_spec.rb +7 -7
- data/lib/capybara/spec/session/has_select_spec.rb +10 -4
- data/lib/capybara/spec/session/has_selector_spec.rb +15 -0
- data/lib/capybara/spec/session/has_text_spec.rb +5 -13
- data/lib/capybara/spec/session/matches_style_spec.rb +2 -0
- data/lib/capybara/spec/session/node_spec.rb +81 -0
- data/lib/capybara/spec/session/reset_session_spec.rb +13 -0
- data/lib/capybara/spec/session/scroll_spec.rb +3 -1
- data/lib/capybara/spec/session/visit_spec.rb +20 -0
- data/lib/capybara/spec/session/window/window_spec.rb +1 -1
- data/lib/capybara/spec/session/window/windows_spec.rb +1 -1
- data/lib/capybara/spec/session/within_spec.rb +13 -0
- data/lib/capybara/spec/spec_helper.rb +8 -2
- data/lib/capybara/spec/test_app.rb +74 -6
- data/lib/capybara/spec/views/form.erb +17 -0
- data/lib/capybara/spec/views/with_html.erb +3 -3
- data/lib/capybara/spec/views/with_scope.erb +2 -2
- data/lib/capybara/spec/views/with_shadow.erb +31 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +1 -1
- data/lib/capybara.rb +5 -2
- data/spec/capybara_spec.rb +12 -0
- data/spec/counter_spec.rb +35 -0
- data/spec/css_builder_spec.rb +1 -1
- data/spec/css_splitter_spec.rb +1 -1
- data/spec/dsl_spec.rb +4 -2
- data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
- data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
- data/spec/minitest_spec.rb +4 -0
- data/spec/minitest_spec_spec.rb +4 -0
- data/spec/per_session_config_spec.rb +1 -1
- data/spec/rack_test_spec.rb +16 -2
- data/spec/result_spec.rb +27 -29
- data/spec/rspec/features_spec.rb +2 -2
- data/spec/rspec/scenarios_spec.rb +2 -2
- data/spec/rspec/shared_spec_matchers.rb +1 -1
- data/spec/rspec_matchers_spec.rb +25 -0
- data/spec/rspec_spec.rb +2 -2
- data/spec/sauce_spec_chrome.rb +4 -4
- data/spec/selector_spec.rb +4 -4
- data/spec/selenium_spec_chrome.rb +8 -7
- data/spec/selenium_spec_chrome_remote.rb +9 -9
- data/spec/selenium_spec_edge.rb +12 -6
- data/spec/selenium_spec_firefox.rb +21 -5
- data/spec/selenium_spec_firefox_remote.rb +19 -4
- data/spec/selenium_spec_ie.rb +4 -2
- data/spec/selenium_spec_safari.rb +8 -2
- data/spec/server_spec.rb +7 -7
- data/spec/shared_selenium_session.rb +13 -6
- data/spec/spec_helper.rb +35 -2
- data/spec/whitespace_normalizer_spec.rb +54 -0
- data/spec/xpath_builder_spec.rb +1 -1
- metadata +9 -4
@@ -179,10 +179,16 @@ Capybara::SpecHelper.spec '#has_select?' do
|
|
179
179
|
end
|
180
180
|
end
|
181
181
|
|
182
|
+
it 'should raise an error if an invalid option is passed' do
|
183
|
+
expect do
|
184
|
+
expect(@session).to have_select('form_languages', invalid: true)
|
185
|
+
end.to raise_error(ArgumentError, 'Invalid option(s) :invalid, should be one of :above, :below, :left_of, :right_of, :near, :count, :minimum, :maximum, :between, :text, :id, :class, :style, :visible, :obscured, :exact, :exact_text, :normalize_ws, :match, :wait, :filter_set, :focused, :disabled, :name, :placeholder, :options, :enabled_options, :disabled_options, :selected, :with_selected, :multiple, :with_options')
|
186
|
+
end
|
187
|
+
|
182
188
|
it 'should support locator-less usage' do
|
183
|
-
expect(@session.has_select?(with_options: %w[Norway Sweden])).to
|
189
|
+
expect(@session.has_select?(with_options: %w[Norway Sweden])).to be true
|
184
190
|
expect(@session).to have_select(with_options: ['London'])
|
185
|
-
expect(@session.has_select?(with_selected: %w[Commando Boxerbriefs])).to
|
191
|
+
expect(@session.has_select?(with_selected: %w[Commando Boxerbriefs])).to be true
|
186
192
|
expect(@session).to have_select(with_selected: ['Briefs'])
|
187
193
|
end
|
188
194
|
end
|
@@ -302,9 +308,9 @@ Capybara::SpecHelper.spec '#has_no_select?' do
|
|
302
308
|
end
|
303
309
|
|
304
310
|
it 'should support locator-less usage' do
|
305
|
-
expect(@session.has_no_select?(with_options: %w[Norway Sweden Finland Latvia])).to
|
311
|
+
expect(@session.has_no_select?(with_options: %w[Norway Sweden Finland Latvia])).to be true
|
306
312
|
expect(@session).to have_no_select(with_options: ['New London'])
|
307
|
-
expect(@session.has_no_select?(id: 'form_underwear', with_selected: ['Boxers'])).to
|
313
|
+
expect(@session.has_no_select?(id: 'form_underwear', with_selected: ['Boxers'])).to be true
|
308
314
|
expect(@session).to have_no_select(id: 'form_underwear', with_selected: %w[Commando Boxers])
|
309
315
|
end
|
310
316
|
end
|
@@ -117,6 +117,21 @@ Capybara::SpecHelper.spec '#has_selector?' do
|
|
117
117
|
expect(@session).to have_selector(:id, 'h2one', text: 'Header Class Test One', exact_text: false)
|
118
118
|
expect(@session).to have_selector(:id, 'h2one', text: 'Header Class Test', exact_text: false)
|
119
119
|
end
|
120
|
+
|
121
|
+
it 'should warn if text option is a regexp that it is ignoring exact_text' do
|
122
|
+
allow(Capybara::Helpers).to receive(:warn)
|
123
|
+
expect(@session).to have_selector(:id, 'h2one', text: /Class Test/, exact_text: true)
|
124
|
+
expect(Capybara::Helpers).to have_received(:warn).with(/'exact_text' option is not supported/)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'regexp' do
|
129
|
+
it 'should only match when it fully matches' do
|
130
|
+
expect(@session).to have_selector(:id, 'h2one', exact_text: /Header Class Test One/)
|
131
|
+
expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Header Class Test/)
|
132
|
+
expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Class Test One/)
|
133
|
+
expect(@session).to have_no_selector(:id, 'h2one', exact_text: /Class Test/)
|
134
|
+
end
|
120
135
|
end
|
121
136
|
end
|
122
137
|
|
@@ -127,11 +127,7 @@ Capybara::SpecHelper.spec '#has_text?' do
|
|
127
127
|
def to_hash; { value: 'Other hash' } end
|
128
128
|
end.new
|
129
129
|
@session.visit('/with_html')
|
130
|
-
|
131
|
-
expect(@session).to have_text(:visible, with_to_hash, **{})
|
132
|
-
else
|
133
|
-
expect(@session).to have_text(:visible, with_to_hash, {})
|
134
|
-
end
|
130
|
+
expect(@session).to have_text(:visible, with_to_hash, **{})
|
135
131
|
end
|
136
132
|
end
|
137
133
|
|
@@ -157,16 +153,12 @@ Capybara::SpecHelper.spec '#has_text?' do
|
|
157
153
|
expect(@session).to have_text(42)
|
158
154
|
end
|
159
155
|
|
160
|
-
it 'should be true when passed nil' do
|
156
|
+
it 'should be true when passed nil, and warn about it' do
|
161
157
|
# nil is converted to '' when to_s is invoked
|
162
158
|
@session.visit('/with_html')
|
163
|
-
expect
|
164
|
-
|
165
|
-
|
166
|
-
it 'should warn when passed nil' do
|
167
|
-
@session.visit('/with_html')
|
168
|
-
expect_any_instance_of(Kernel).to receive(:warn).with(/Checking for expected text of nil is confusing/) # rubocop:disable RSpec/AnyInstance
|
169
|
-
expect(@session).to have_text(nil)
|
159
|
+
expect do
|
160
|
+
expect(@session).to have_text(nil)
|
161
|
+
end.to output(/Checking for expected text of nil is confusing/).to_stderr
|
170
162
|
end
|
171
163
|
|
172
164
|
it 'should wait for text to appear', requires: [:js] do
|
@@ -23,6 +23,7 @@ Capybara::SpecHelper.spec '#matches_style?', requires: [:css] do
|
|
23
23
|
expect(@session.find(:css, '#first').matches_style?(display: /^in/)).to be false
|
24
24
|
end
|
25
25
|
|
26
|
+
# rubocop:disable Capybara/MatchStyle
|
26
27
|
it 'deprecated has_style?' do
|
27
28
|
expect { have_style(display: /^bl/) }.to \
|
28
29
|
output(/have_style is deprecated/).to_stderr
|
@@ -32,4 +33,5 @@ Capybara::SpecHelper.spec '#matches_style?', requires: [:css] do
|
|
32
33
|
el.has_style?('display' => /^bl/)
|
33
34
|
expect(Capybara::Helpers).to have_received(:warn)
|
34
35
|
end
|
36
|
+
# rubocop:enable Capybara/MatchStyle
|
35
37
|
end
|
@@ -157,6 +157,12 @@ Capybara::SpecHelper.spec 'node' do
|
|
157
157
|
expect(@session.find(:css, '#existing_content_editable_child_parent').text).to eq("Some content\nWYSIWYG")
|
158
158
|
end
|
159
159
|
end
|
160
|
+
|
161
|
+
it 'should submit single text input forms if ended with \n' do
|
162
|
+
@session.visit('/form')
|
163
|
+
@session.find(:css, '#single_input').set("my entry\n")
|
164
|
+
expect(extract_results(@session)['single_input']).to eq('my entry')
|
165
|
+
end
|
160
166
|
end
|
161
167
|
|
162
168
|
describe '#tag_name' do
|
@@ -651,6 +657,16 @@ Capybara::SpecHelper.spec 'node' do
|
|
651
657
|
expect(target).to have_text(%r{^HTML5 Dropped string: text/plain drag_html5-#{key}$}m, exact: true)
|
652
658
|
end
|
653
659
|
end
|
660
|
+
|
661
|
+
it 'should trigger a dragenter event, before the first dragover event' do
|
662
|
+
@session.visit('/with_js')
|
663
|
+
element = @session.find('//div[@id="drag_html5"]')
|
664
|
+
target = @session.find('//div[@id="drop_html5"]')
|
665
|
+
element.drag_to(target)
|
666
|
+
|
667
|
+
# Events are listed in reverse chronological order
|
668
|
+
expect(@session).to have_text(/DragOver.*DragEnter/m)
|
669
|
+
end
|
654
670
|
end
|
655
671
|
end
|
656
672
|
|
@@ -775,6 +791,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
775
791
|
end
|
776
792
|
|
777
793
|
it 'should allow to adjust the click offset', requires: [:js] do
|
794
|
+
Capybara.w3c_click_offset = false
|
778
795
|
@session.visit('with_js')
|
779
796
|
@session.find(:css, '#click-test').click(x: 5, y: 5)
|
780
797
|
link = @session.find(:link, 'has-been-clicked')
|
@@ -901,6 +918,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
901
918
|
end
|
902
919
|
|
903
920
|
it 'should allow to adjust the offset', requires: [:js] do
|
921
|
+
Capybara.w3c_click_offset = false
|
904
922
|
@session.visit('with_js')
|
905
923
|
@session.find(:css, '#click-test').double_click(x: 10, y: 5)
|
906
924
|
link = @session.find(:link, 'has-been-double-clicked')
|
@@ -987,6 +1005,7 @@ Capybara::SpecHelper.spec 'node' do
|
|
987
1005
|
end
|
988
1006
|
|
989
1007
|
it 'should allow to adjust the offset', requires: [:js] do
|
1008
|
+
Capybara.w3c_click_offset = false
|
990
1009
|
@session.visit('with_js')
|
991
1010
|
@session.find(:css, '#click-test').right_click(x: 10, y: 10)
|
992
1011
|
link = @session.find(:link, 'has-been-right-clicked')
|
@@ -1176,6 +1195,68 @@ Capybara::SpecHelper.spec 'node' do
|
|
1176
1195
|
end
|
1177
1196
|
end
|
1178
1197
|
|
1198
|
+
describe '#shadow_root', requires: %i[js] do
|
1199
|
+
it 'should get the shadow root' do
|
1200
|
+
@session.visit('/with_shadow')
|
1201
|
+
expect do
|
1202
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1203
|
+
expect(shadow_root).to be_a(Capybara::Node::Element)
|
1204
|
+
end.not_to raise_error
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
it 'should find elements inside the shadow dom using CSS' do
|
1208
|
+
@session.visit('/with_shadow')
|
1209
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1210
|
+
expect(shadow_root).to have_css('#shadow_content', text: 'some text')
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
it 'should find nested shadow roots' do
|
1214
|
+
@session.visit('/with_shadow')
|
1215
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1216
|
+
nested_shadow_root = shadow_root.find(:css, '#nested_shadow_host').shadow_root
|
1217
|
+
expect(nested_shadow_root).to have_css('#nested_shadow_content', text: 'nested text')
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
it 'should click on elements' do
|
1221
|
+
@session.visit('/with_shadow')
|
1222
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1223
|
+
checkbox = shadow_root.find(:css, 'input[type="checkbox"]')
|
1224
|
+
expect(checkbox).not_to be_checked
|
1225
|
+
checkbox.click
|
1226
|
+
expect(checkbox).to be_checked
|
1227
|
+
end
|
1228
|
+
|
1229
|
+
it 'should use convenience methods once moved to a descendant of the shadow root' do
|
1230
|
+
@session.visit('/with_shadow')
|
1231
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1232
|
+
descendant = shadow_root.find(:css, '#controls_wrapper')
|
1233
|
+
expect do
|
1234
|
+
descendant.check('shadow_checkbox')
|
1235
|
+
end.not_to raise_error
|
1236
|
+
expect(descendant).to have_checked_field('shadow_checkbox')
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
it 'should produce error messages when failing' do
|
1240
|
+
@session.visit('/with_shadow')
|
1241
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1242
|
+
expect do
|
1243
|
+
expect(shadow_root).to have_css('#shadow_content', text: 'Not in the document')
|
1244
|
+
end.to raise_error(/tag="ShadowRoot"/)
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
it 'should get visible text' do
|
1248
|
+
@session.visit('/with_shadow')
|
1249
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1250
|
+
expect(shadow_root).to have_text('some text scroll.html')
|
1251
|
+
end
|
1252
|
+
|
1253
|
+
it 'should get all text' do
|
1254
|
+
@session.visit('/with_shadow')
|
1255
|
+
shadow_root = @session.find(:css, '#shadow_host').shadow_root
|
1256
|
+
expect(shadow_root).to have_text(:all, 'some text scroll.html')
|
1257
|
+
end
|
1258
|
+
end
|
1259
|
+
|
1179
1260
|
describe '#reload', requires: [:js] do
|
1180
1261
|
it 'should reload elements found via ancestor with CSS' do
|
1181
1262
|
@session.visit('/with_js')
|
@@ -95,6 +95,19 @@ Capybara::SpecHelper.spec '#reset_session!' do
|
|
95
95
|
expect(@session.windows.size).to eq 1
|
96
96
|
end
|
97
97
|
|
98
|
+
it 'does not block opening a new window after a frame was switched to and not switched back', requires: [:windows] do
|
99
|
+
@session.visit('/with_iframe?id=test_iframe&url=/')
|
100
|
+
@session.switch_to_frame(@session.find(:frame, 'test_iframe'))
|
101
|
+
within_window_test = lambda do
|
102
|
+
@session.within_window(@session.open_new_window) do
|
103
|
+
@session.visit('/')
|
104
|
+
end
|
105
|
+
end
|
106
|
+
expect(&within_window_test).to raise_error(Capybara::ScopeError)
|
107
|
+
@session.reset_session!
|
108
|
+
expect(&within_window_test).not_to raise_error
|
109
|
+
end
|
110
|
+
|
98
111
|
context 'When reuse_server == false' do
|
99
112
|
let!(:orig_reuse_server) { Capybara.reuse_server }
|
100
113
|
|
@@ -36,7 +36,9 @@ Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
|
|
36
36
|
it 'can scroll the window to the vertical bottom' do
|
37
37
|
@session.scroll_to :bottom
|
38
38
|
max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
|
39
|
-
|
39
|
+
scrolled_location_x, scrolled_location_y = @session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')
|
40
|
+
expect(scrolled_location_x).to be_within(0.5).of(0)
|
41
|
+
expect(scrolled_location_y).to be_within(0.5).of(max_scroll)
|
40
42
|
end
|
41
43
|
|
42
44
|
it 'can scroll the window to the vertical center' do
|
@@ -201,4 +201,24 @@ Capybara::SpecHelper.spec '#visit' do
|
|
201
201
|
@session.visit('/get_cookie')
|
202
202
|
expect(@session).to have_content('root cookie')
|
203
203
|
end
|
204
|
+
|
205
|
+
context 'with base element' do
|
206
|
+
it 'should use base href with relative links' do
|
207
|
+
@session.visit('/base/with_base')
|
208
|
+
@session.click_link('Title page')
|
209
|
+
expect(@session).to have_current_path('/with_title')
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should use base href with bare queries' do
|
213
|
+
@session.visit('/base/with_base')
|
214
|
+
@session.click_link('Bare query')
|
215
|
+
expect(@session).to have_current_path('/?a=3')
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should not use the base href with a new visit call' do
|
219
|
+
@session.visit('/base/with_other_base')
|
220
|
+
@session.visit('with_html')
|
221
|
+
expect(@session).to have_current_path('/with_html')
|
222
|
+
end
|
223
|
+
end
|
204
224
|
end
|
@@ -59,7 +59,7 @@ Capybara::SpecHelper.spec Capybara::Window, requires: [:windows] do
|
|
59
59
|
it 'should return false if window is closed' do
|
60
60
|
@session.switch_to_window(other_window)
|
61
61
|
other_window.close
|
62
|
-
expect(other_window.current?).to
|
62
|
+
expect(other_window.current?).to be(false)
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
@@ -29,6 +29,6 @@ Capybara::SpecHelper.spec '#windows', requires: [:windows] do
|
|
29
29
|
titles = @session.windows.map do |window|
|
30
30
|
@session.within_window(window) { @session.title }
|
31
31
|
end
|
32
|
-
expect(titles).to match_array(['With Windows', 'Title of the first popup', 'Title of popup two'])
|
32
|
+
expect(titles).to match_array(['With Windows', 'Title of the first popup', 'Title of popup two']) # rubocop:disable RSpec/MatchArray
|
33
33
|
end
|
34
34
|
end
|
@@ -49,6 +49,19 @@ Capybara::SpecHelper.spec '#within' do
|
|
49
49
|
expect { @session.text }.to raise_error(StandardError)
|
50
50
|
end
|
51
51
|
end
|
52
|
+
|
53
|
+
it 'should pass scope element to the block' do
|
54
|
+
@session.within(:css, '#another_foo') do |scope|
|
55
|
+
expect(scope).to match_css('#another_foo')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should scope click', requires: [:js] do
|
60
|
+
@session.within(:css, '#another_foo') do |scope|
|
61
|
+
@session.click
|
62
|
+
expect(scope).to have_text('I was clicked')
|
63
|
+
end
|
64
|
+
end
|
52
65
|
end
|
53
66
|
|
54
67
|
context 'with XPath selector' do
|
@@ -24,6 +24,7 @@ module Capybara
|
|
24
24
|
Capybara.app_host = nil
|
25
25
|
Capybara.default_selector = :xpath
|
26
26
|
Capybara.default_max_wait_time = 1
|
27
|
+
Capybara.default_retry_interval = 0.01
|
27
28
|
Capybara.ignore_hidden_elements = true
|
28
29
|
Capybara.exact = false
|
29
30
|
Capybara.raise_server_errors = true
|
@@ -37,7 +38,7 @@ module Capybara
|
|
37
38
|
Capybara.predicates_wait = true
|
38
39
|
Capybara.default_normalize_ws = false
|
39
40
|
Capybara.use_html5_parsing = !ENV['HTML5_PARSING'].nil?
|
40
|
-
Capybara.w3c_click_offset =
|
41
|
+
Capybara.w3c_click_offset = true
|
41
42
|
reset_threadsafe
|
42
43
|
end
|
43
44
|
|
@@ -122,6 +123,11 @@ module Capybara
|
|
122
123
|
YAML.safe_load results, permitted_classes: perms
|
123
124
|
end
|
124
125
|
|
126
|
+
def extract_content_type(session)
|
127
|
+
expect(session).to have_xpath("//pre[@id='content_type']")
|
128
|
+
Capybara::HTML(session.body).xpath("//pre[@id='content_type']").first.text
|
129
|
+
end
|
130
|
+
|
125
131
|
def be_an_invalid_element_error(session)
|
126
132
|
satisfy { |error| session.driver.invalid_element_errors.any? { |e| error.is_a? e } }
|
127
133
|
end
|
@@ -132,4 +138,4 @@ module Capybara
|
|
132
138
|
end
|
133
139
|
end
|
134
140
|
|
135
|
-
Dir["#{File.dirname(__FILE__)}/session/**/*.rb"].each { |file| require_relative file }
|
141
|
+
Dir["#{File.dirname(__FILE__)}/session/**/*.rb"].sort.each { |file| require_relative file }
|
@@ -177,12 +177,81 @@ class TestApp < Sinatra::Base
|
|
177
177
|
HTML
|
178
178
|
end
|
179
179
|
|
180
|
+
get '/with_iframe' do
|
181
|
+
<<-HTML
|
182
|
+
<!DOCTYPE html>
|
183
|
+
<html lang="en">
|
184
|
+
<head>
|
185
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
186
|
+
<title>Test with Iframe</title>
|
187
|
+
</head>
|
188
|
+
|
189
|
+
<body>
|
190
|
+
<iframe src="#{params[:url]}" id="#{params[:id]}"></iframe>
|
191
|
+
</body>
|
192
|
+
</html>
|
193
|
+
HTML
|
194
|
+
end
|
195
|
+
|
196
|
+
get '/base/with_base' do
|
197
|
+
<<-HTML
|
198
|
+
<!DOCTYPE html>
|
199
|
+
<html>
|
200
|
+
<head>
|
201
|
+
<base href="/">
|
202
|
+
<title>Origin</title>
|
203
|
+
</head>
|
204
|
+
<body>
|
205
|
+
<a href="with_title">Title page</a>
|
206
|
+
<a href="?a=3">Bare query</a>
|
207
|
+
</body>
|
208
|
+
</html>
|
209
|
+
HTML
|
210
|
+
end
|
211
|
+
|
212
|
+
get '/base/with_other_base' do
|
213
|
+
<<-HTML
|
214
|
+
<!DOCTYPE html>
|
215
|
+
<html>
|
216
|
+
<head>
|
217
|
+
<base href="/base/">
|
218
|
+
<title>Origin</title>
|
219
|
+
</head>
|
220
|
+
<body>
|
221
|
+
<a href="with_title">Title page</a>
|
222
|
+
<a href="?a=3">Bare query</a>
|
223
|
+
</body>
|
224
|
+
</html>
|
225
|
+
HTML
|
226
|
+
end
|
227
|
+
|
228
|
+
get '/csp' do
|
229
|
+
response.headers['Content-Security-Policy'] = "default-src 'none'; connect-src 'self'; base-uri 'none'; font-src 'self'; img-src 'self' data:; object-src 'none'; script-src 'self' 'nonce-jAviMuMisoTisVXjgLoWdA=='; style-src 'self' 'nonce-jAviMuMisoTisVXjgLoWdA=='; form-action 'self';"
|
230
|
+
<<-HTML
|
231
|
+
<!DOCTYPE html>
|
232
|
+
<html lang="en">
|
233
|
+
<head>
|
234
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
235
|
+
<title>CSP</title>
|
236
|
+
</head>
|
237
|
+
|
238
|
+
<body>
|
239
|
+
<div>CSP</div>
|
240
|
+
</body>
|
241
|
+
</html>
|
242
|
+
HTML
|
243
|
+
end
|
244
|
+
|
180
245
|
get '/download.csv' do
|
181
246
|
content_type 'text/csv'
|
182
247
|
'This, is, comma, separated' \
|
183
248
|
'Thomas, Walpole, was , here'
|
184
249
|
end
|
185
250
|
|
251
|
+
get '/apple-touch-icon-precomposed.png' do
|
252
|
+
halt(404)
|
253
|
+
end
|
254
|
+
|
186
255
|
get '/:view' do |view|
|
187
256
|
view_template = "#{__dir__}/views/#{view}.erb"
|
188
257
|
has_layout = File.exist?(view_template) && File.open(view_template) { |f| f.first.downcase.include?('doctype') }
|
@@ -191,12 +260,15 @@ class TestApp < Sinatra::Base
|
|
191
260
|
|
192
261
|
post '/form' do
|
193
262
|
self.class.form_post_count += 1
|
194
|
-
%(
|
263
|
+
%(
|
264
|
+
<pre id="content_type">#{request.content_type}</pre>
|
265
|
+
<pre id="results">#{params.fetch(:form, {}).merge('post_count' => self.class.form_post_count).to_yaml}</pre>
|
266
|
+
)
|
195
267
|
end
|
196
268
|
|
197
269
|
post '/upload_empty' do
|
198
270
|
if params[:form][:file].nil?
|
199
|
-
|
271
|
+
"Successfully ignored empty file field. Content type was #{request.content_type}"
|
200
272
|
else
|
201
273
|
'Something went wrong.'
|
202
274
|
end
|
@@ -223,10 +295,6 @@ class TestApp < Sinatra::Base
|
|
223
295
|
'No files uploaded'
|
224
296
|
end
|
225
297
|
|
226
|
-
get '/apple-touch-icon-precomposed.png' do
|
227
|
-
halt(404)
|
228
|
-
end
|
229
|
-
|
230
298
|
class << self
|
231
299
|
attr_accessor :form_post_count
|
232
300
|
end
|
@@ -586,6 +586,19 @@ New line after and before textarea tag
|
|
586
586
|
<p>
|
587
587
|
</form>
|
588
588
|
|
589
|
+
<form action="/form" method="post" enctype="multipart/form-data">
|
590
|
+
<input type="hidden" name="form[document2]" value="hidden_field"/>
|
591
|
+
|
592
|
+
<p>
|
593
|
+
<label for="form_document">Document with hidden</label>
|
594
|
+
<input type="file" name="form[document2]" id="form_document2"/>
|
595
|
+
</p>
|
596
|
+
|
597
|
+
<p>
|
598
|
+
<input type="submit" value="Upload Empty With Hidden"/>
|
599
|
+
<p>
|
600
|
+
</form>
|
601
|
+
|
589
602
|
<form action="/upload_multiple" method="post" enctype="multipart/form-data">
|
590
603
|
<p>
|
591
604
|
<label for="form_multiple_file_name">File Name</label>
|
@@ -682,6 +695,10 @@ New line after and before textarea tag
|
|
682
695
|
</p>
|
683
696
|
</form>
|
684
697
|
|
698
|
+
<form id="single_input_form" action="/form" method="post">
|
699
|
+
<input type="text" name="form[single_input]" id="single_input"/>
|
700
|
+
</form>
|
701
|
+
|
685
702
|
<label>Confusion
|
686
703
|
<input type="checkbox" id="confusion_checkbox" class="confusion-checkbox confusion"/>
|
687
704
|
</label>
|
@@ -19,7 +19,7 @@
|
|
19
19
|
|
20
20
|
<p class="para" id="first" data-random="abc\def" style="line-height: 25px;">
|
21
21
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
|
22
|
-
tempor incididunt ut <a href="/with_simple_html" title="awesome title" class="simple" tabindex="1">labore</a>
|
22
|
+
tempor incididunt ut <a href="/with_simple_html" title="awesome title" class="simple" tabindex="1" target="_self">labore</a>
|
23
23
|
et dolore magna aliqua. Ut enim ad minim veniam,
|
24
24
|
quis nostrud exercitation <a href="/foo" id="foo" data-test-id="test-foo">ullamco</a> laboris nisi
|
25
25
|
ut aliquip ex ea commodo consequat.
|
@@ -88,7 +88,7 @@ banana</textarea>
|
|
88
88
|
</div>
|
89
89
|
|
90
90
|
<div style="display: none;">
|
91
|
-
<a id="
|
91
|
+
<a id="first_invisible" class="hidden">first hidden link</a>
|
92
92
|
</div>
|
93
93
|
|
94
94
|
<div style="display: none;">
|
@@ -138,7 +138,7 @@ banana</textarea>
|
|
138
138
|
Ancestor
|
139
139
|
<div id="child">Child</div>
|
140
140
|
</div>
|
141
|
-
<div id="
|
141
|
+
<div id="ancestor1_sibling">
|
142
142
|
ASibling
|
143
143
|
</div>
|
144
144
|
</div>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<%# Borrowed from Titus Fortner %>
|
3
|
+
<html>
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
6
|
+
<title>Shadow DOM</title>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<div id="no_host"></div>
|
10
|
+
<div id="shadow_host"></div>
|
11
|
+
<a href="scroll.html">scroll.html</a>
|
12
|
+
<script>
|
13
|
+
let shadowRoot = document.getElementById('shadow_host').attachShadow({mode: 'open'});
|
14
|
+
shadowRoot.innerHTML = `
|
15
|
+
<span class="wrapper" id="shadow_content"><span class="info">some text</span></span>
|
16
|
+
<div id="nested_shadow_host"></div>
|
17
|
+
<a href="scroll.html">scroll.html</a>
|
18
|
+
<div id="controls_wrapper">
|
19
|
+
<input type="text" />
|
20
|
+
<input type="checkbox" id="shadow_checkbox" />
|
21
|
+
<input type="file" />
|
22
|
+
</div>
|
23
|
+
`;
|
24
|
+
|
25
|
+
let nestedShadowRoot = shadowRoot.getElementById('nested_shadow_host').attachShadow({mode: 'open'});
|
26
|
+
nestedShadowRoot.innerHTML = `
|
27
|
+
<div id="nested_shadow_content"><div>nested text</div></div>
|
28
|
+
`;
|
29
|
+
</script>
|
30
|
+
</body>
|
31
|
+
</html>
|
data/lib/capybara/version.rb
CHANGED
data/lib/capybara/window.rb
CHANGED
data/lib/capybara.rb
CHANGED
@@ -79,6 +79,7 @@ module Capybara
|
|
79
79
|
# - **automatic_reload** (Boolean = `true`) - Whether to automatically reload elements as Capybara is waiting.
|
80
80
|
# - **default_max_wait_time** (Numeric = `2`) - The maximum number of seconds to wait for asynchronous processes to finish.
|
81
81
|
# - **default_normalize_ws** (Boolean = `false`) - Whether text predicates and matchers use normalize whitespace behavior.
|
82
|
+
# - **default_retry_interval** (Numeric = `0.01`) - The number of seconds to delay the next check in asynchronous processes.
|
82
83
|
# - **default_selector** (`:css`, `:xpath` = `:css`) - Methods which take a selector use the given type by default. See also {Capybara::Selector}.
|
83
84
|
# - **default_set_options** (Hash = `{}`) - The default options passed to {Capybara::Node::Element#set Element#set}.
|
84
85
|
# - **enable_aria_label** (Boolean = `false`) - Whether fields, links, and buttons will match against `aria-label` attribute.
|
@@ -96,11 +97,12 @@ module Capybara
|
|
96
97
|
# {Capybara::Session#save_and_open_page save_and_open_page}, or {Capybara::Session#save_and_open_screenshot save_and_open_screenshot}.
|
97
98
|
# - **server** (Symbol = `:default` (which uses puma)) - The name of the registered server to use when running the app under test.
|
98
99
|
# - **server_port** (Integer) - The port Capybara will run the application server on, if not specified a random port will be used.
|
100
|
+
# - **server_host** (String = "127.0.0.1") - The IP address Capybara will bind the application server to. If the test application is to be accessed from an external host, you will want to change this to "0.0.0.0" or to a more specific IP address that your test client can reach.
|
99
101
|
# - **server_errors** (Array\<Class> = `[Exception]`) - Error classes that should be raised in the tests if they are raised in the server
|
100
102
|
# and {configure raise_server_errors} is `true`.
|
101
103
|
# - **test_id** (Symbol, String, `nil` = `nil`) - Optional attribute to match locator against with built-in selectors along with id.
|
102
104
|
# - **threadsafe** (Boolean = `false`) - Whether sessions can be configured individually.
|
103
|
-
# - **w3c_click_offset** (Boolean = '
|
105
|
+
# - **w3c_click_offset** (Boolean = 'true') - Whether click offsets should be from element center (true) or top left (false)
|
104
106
|
#
|
105
107
|
# #### DSL Options
|
106
108
|
#
|
@@ -495,6 +497,7 @@ Capybara.configure do |config|
|
|
495
497
|
config.server = :default
|
496
498
|
config.default_selector = :css
|
497
499
|
config.default_max_wait_time = 2
|
500
|
+
config.default_retry_interval = 0.01
|
498
501
|
config.ignore_hidden_elements = true
|
499
502
|
config.default_host = 'http://www.example.com'
|
500
503
|
config.automatic_reload = true
|
@@ -513,5 +516,5 @@ Capybara.configure do |config|
|
|
513
516
|
config.predicates_wait = true
|
514
517
|
config.default_normalize_ws = false
|
515
518
|
config.use_html5_parsing = false
|
516
|
-
config.w3c_click_offset =
|
519
|
+
config.w3c_click_offset = true
|
517
520
|
end
|
data/spec/capybara_spec.rb
CHANGED
@@ -15,6 +15,18 @@ RSpec.describe Capybara do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
describe 'default_retry_interval' do
|
19
|
+
before { @previous_default_interval = described_class.default_retry_interval }
|
20
|
+
|
21
|
+
after { described_class.default_retry_interval = @previous_default_interval } # rubocop:disable RSpec/InstanceVariable
|
22
|
+
|
23
|
+
it 'should be changeable' do
|
24
|
+
expect(described_class.default_retry_interval).not_to eq(0.1)
|
25
|
+
described_class.default_retry_interval = 0.1
|
26
|
+
expect(described_class.default_retry_interval).to eq(0.1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
describe '.register_driver' do
|
19
31
|
it 'should add a new driver' do
|
20
32
|
described_class.register_driver :schmoo do |app|
|