capybara 3.33.0 → 3.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +44 -15
  3. data/README.md +0 -2
  4. data/lib/capybara.rb +1 -1
  5. data/lib/capybara/config.rb +4 -6
  6. data/lib/capybara/driver/base.rb +4 -0
  7. data/lib/capybara/helpers.rb +25 -1
  8. data/lib/capybara/minitest.rb +2 -2
  9. data/lib/capybara/minitest/spec.rb +14 -11
  10. data/lib/capybara/node/actions.rb +1 -2
  11. data/lib/capybara/node/element.rb +1 -5
  12. data/lib/capybara/node/finders.rb +7 -6
  13. data/lib/capybara/node/matchers.rb +7 -5
  14. data/lib/capybara/node/simple.rb +5 -1
  15. data/lib/capybara/queries/ancestor_query.rb +1 -1
  16. data/lib/capybara/queries/current_path_query.rb +14 -4
  17. data/lib/capybara/queries/selector_query.rb +8 -9
  18. data/lib/capybara/queries/sibling_query.rb +1 -1
  19. data/lib/capybara/queries/text_query.rb +2 -2
  20. data/lib/capybara/rack_test/browser.rb +7 -3
  21. data/lib/capybara/rack_test/driver.rb +1 -0
  22. data/lib/capybara/rack_test/form.rb +1 -1
  23. data/lib/capybara/rack_test/node.rb +1 -1
  24. data/lib/capybara/registration_container.rb +3 -3
  25. data/lib/capybara/registrations/patches/puma_ssl.rb +3 -1
  26. data/lib/capybara/registrations/servers.rb +1 -0
  27. data/lib/capybara/result.rb +3 -7
  28. data/lib/capybara/rspec.rb +2 -0
  29. data/lib/capybara/rspec/matcher_proxies.rb +1 -1
  30. data/lib/capybara/rspec/matchers.rb +7 -6
  31. data/lib/capybara/rspec/matchers/have_current_path.rb +2 -2
  32. data/lib/capybara/rspec/matchers/match_style.rb +5 -0
  33. data/lib/capybara/selector/definition.rb +6 -5
  34. data/lib/capybara/selector/definition/button.rb +7 -5
  35. data/lib/capybara/selector/definition/css.rb +1 -1
  36. data/lib/capybara/selector/definition/datalist_input.rb +1 -1
  37. data/lib/capybara/selector/definition/element.rb +2 -1
  38. data/lib/capybara/selector/definition/label.rb +1 -1
  39. data/lib/capybara/selector/definition/select.rb +1 -1
  40. data/lib/capybara/selector/definition/table_row.rb +1 -1
  41. data/lib/capybara/selector/filter_set.rb +2 -2
  42. data/lib/capybara/selector/selector.rb +5 -1
  43. data/lib/capybara/selenium/driver.rb +29 -3
  44. data/lib/capybara/selenium/driver_specializations/chrome_driver.rb +3 -3
  45. data/lib/capybara/selenium/driver_specializations/edge_driver.rb +3 -3
  46. data/lib/capybara/selenium/driver_specializations/firefox_driver.rb +1 -1
  47. data/lib/capybara/selenium/extensions/find.rb +3 -3
  48. data/lib/capybara/selenium/extensions/scroll.rb +8 -10
  49. data/lib/capybara/selenium/node.rb +6 -3
  50. data/lib/capybara/selenium/nodes/chrome_node.rb +20 -2
  51. data/lib/capybara/selenium/nodes/safari_node.rb +1 -1
  52. data/lib/capybara/selenium/patches/atoms.rb +4 -4
  53. data/lib/capybara/selenium/patches/logs.rb +4 -4
  54. data/lib/capybara/server/animation_disabler.rb +3 -2
  55. data/lib/capybara/server/middleware.rb +4 -2
  56. data/lib/capybara/session.rb +20 -11
  57. data/lib/capybara/session/matchers.rb +11 -11
  58. data/lib/capybara/spec/public/test.js +6 -1
  59. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  60. data/lib/capybara/spec/session/check_spec.rb +6 -0
  61. data/lib/capybara/spec/session/current_url_spec.rb +11 -1
  62. data/lib/capybara/spec/session/has_button_spec.rb +2 -0
  63. data/lib/capybara/spec/session/has_css_spec.rb +2 -1
  64. data/lib/capybara/spec/session/has_current_path_spec.rb +13 -0
  65. data/lib/capybara/spec/session/has_text_spec.rb +0 -11
  66. data/lib/capybara/spec/session/matches_style_spec.rb +2 -2
  67. data/lib/capybara/spec/session/node_spec.rb +22 -2
  68. data/lib/capybara/spec/session/refresh_spec.rb +1 -0
  69. data/lib/capybara/spec/session/save_page_spec.rb +4 -4
  70. data/lib/capybara/spec/spec_helper.rb +11 -12
  71. data/lib/capybara/spec/test_app.rb +8 -3
  72. data/lib/capybara/spec/views/form.erb +18 -0
  73. data/lib/capybara/spec/views/with_animation.erb +8 -0
  74. data/lib/capybara/spec/views/with_js.erb +1 -0
  75. data/lib/capybara/spec/views/with_sortable_js.erb +1 -1
  76. data/lib/capybara/version.rb +1 -1
  77. data/lib/capybara/window.rb +3 -7
  78. data/spec/basic_node_spec.rb +9 -8
  79. data/spec/dsl_spec.rb +1 -1
  80. data/spec/fixtures/selenium_driver_rspec_success.rb +1 -1
  81. data/spec/minitest_spec.rb +2 -1
  82. data/spec/rack_test_spec.rb +15 -5
  83. data/spec/rspec/features_spec.rb +3 -1
  84. data/spec/rspec/scenarios_spec.rb +4 -0
  85. data/spec/rspec/shared_spec_matchers.rb +2 -2
  86. data/spec/rspec_spec.rb +4 -0
  87. data/spec/selector_spec.rb +1 -0
  88. data/spec/server_spec.rb +15 -0
  89. data/spec/shared_selenium_session.rb +60 -1
  90. metadata +22 -23
  91. data/lib/capybara/spec/session/source_spec.rb +0 -0
@@ -13,14 +13,14 @@ module Capybara
13
13
  # @param string [String] The string that the current 'path' should equal
14
14
  # @overload $0(regexp, **options)
15
15
  # @param regexp [Regexp] The regexp that the current 'path' should match to
16
- # @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
16
+ # @option options [Boolean] :url (true if `string` is a full url, otherwise false) Whether the comparison should be done against the full current url or just the path
17
17
  # @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
18
18
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current url/path to eq/match given string/regexp argument
19
19
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
20
20
  # @return [true]
21
21
  #
22
- def assert_current_path(path, **options)
23
- _verify_current_path(path, **options) do |query|
22
+ def assert_current_path(path, **options, &optional_filter_block)
23
+ _verify_current_path(path, optional_filter_block, **options) do |query|
24
24
  raise Capybara::ExpectationNotMet, query.failure_message unless query.resolves_for?(self)
25
25
  end
26
26
  end
@@ -35,8 +35,8 @@ module Capybara
35
35
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
36
36
  # @return [true]
37
37
  #
38
- def assert_no_current_path(path, **options)
39
- _verify_current_path(path, **options) do |query|
38
+ def assert_no_current_path(path, **options, &optional_filter_block)
39
+ _verify_current_path(path, optional_filter_block, **options) do |query|
40
40
  raise Capybara::ExpectationNotMet, query.negative_failure_message if query.resolves_for?(self)
41
41
  end
42
42
  end
@@ -50,8 +50,8 @@ module Capybara
50
50
  # @macro current_path_query_params
51
51
  # @return [Boolean]
52
52
  #
53
- def has_current_path?(path, **options)
54
- make_predicate(options) { assert_current_path(path, **options) }
53
+ def has_current_path?(path, **options, &optional_filter_block)
54
+ make_predicate(options) { assert_current_path(path, **options, &optional_filter_block) }
55
55
  end
56
56
 
57
57
  ##
@@ -63,14 +63,14 @@ module Capybara
63
63
  # @macro current_path_query_params
64
64
  # @return [Boolean]
65
65
  #
66
- def has_no_current_path?(path, **options)
67
- make_predicate(options) { assert_no_current_path(path, **options) }
66
+ def has_no_current_path?(path, **options, &optional_filter_block)
67
+ make_predicate(options) { assert_no_current_path(path, **options, &optional_filter_block) }
68
68
  end
69
69
 
70
70
  private
71
71
 
72
- def _verify_current_path(path, **options)
73
- query = Capybara::Queries::CurrentPathQuery.new(path, **options)
72
+ def _verify_current_path(path, filter_block, **options)
73
+ query = Capybara::Queries::CurrentPathQuery.new(path, **options, &filter_block)
74
74
  document.synchronize(query.wait) do
75
75
  yield(query)
76
76
  end
@@ -271,5 +271,10 @@ $(function() {
271
271
  });
272
272
  $('#multiple-file, #hidden_file').change(function(e){
273
273
  $('body').append($('<p class="file_change">File input changed</p>'));
274
- })
274
+ });
275
+
276
+ var shadow = document.querySelector('#shadow').attachShadow({mode: 'open'});
277
+ var span = document.createElement('span');
278
+ span.textContent = 'The things we do in the shadows';
279
+ shadow.appendChild(span);
275
280
  });
@@ -53,7 +53,7 @@ Capybara::SpecHelper.spec '#accept_alert', requires: [:modals] do
53
53
  @session.click_link('Alert page change')
54
54
  sleep 1 # ensure page change occurs before the accept_alert block exits
55
55
  end
56
- expect(@session).to have_current_path('/with_html')
56
+ expect(@session).to have_current_path('/with_html', wait: 5)
57
57
  end
58
58
 
59
59
  context 'with an asynchronous alert' do
@@ -229,6 +229,12 @@ Capybara::SpecHelper.spec '#check' do
229
229
  @session.click_button('awesome')
230
230
  expect(extract_results(@session)['cars']).to include('bugatti')
231
231
  end
232
+
233
+ it 'should check via label if multiple labels' do
234
+ expect(@session).to have_field('multi_label_checkbox', checked: false, visible: :hidden)
235
+ @session.check('Label to click', allow_label_click: true)
236
+ expect(@session).to have_field('multi_label_checkbox', checked: true, visible: :hidden)
237
+ end
232
238
  end
233
239
  end
234
240
  end
@@ -22,7 +22,7 @@ Capybara::SpecHelper.spec '#current_url, #current_path, #current_host' do
22
22
 
23
23
  expect(@session.current_url.chomp('?')).to eq("#{scheme}://#{s.host}:#{s.port}#{path}")
24
24
  expect(@session.current_host).to eq("#{scheme}://#{s.host}") # no port
25
- expect(@session.current_path).to eq(path)
25
+ expect(@session.current_path).to eq(path.split('#')[0])
26
26
  # Server should agree with us
27
27
  expect(@session).to have_content("Current host is #{scheme}://#{s.host}:#{s.port}") if path == '/host'
28
28
  end
@@ -84,6 +84,16 @@ Capybara::SpecHelper.spec '#current_url, #current_path, #current_host' do
84
84
  should_be_on 0, '/landed'
85
85
  end
86
86
 
87
+ it 'maintains fragment' do
88
+ @session.visit("#{bases[0]}/redirect#fragment")
89
+ should_be_on 0, '/landed#fragment'
90
+ end
91
+
92
+ it 'redirects to a fragment' do
93
+ @session.visit("#{bases[0]}/redirect_with_fragment")
94
+ should_be_on 0, '/landed#with_fragment'
95
+ end
96
+
87
97
  it 'is affected by pushState', requires: [:js] do
88
98
  @session.visit('/with_js')
89
99
  @session.execute_script("window.history.pushState({}, '', '/pushed')")
@@ -9,6 +9,8 @@ Capybara::SpecHelper.spec '#has_button?' do
9
9
  expect(@session).to have_button('med')
10
10
  expect(@session).to have_button('crap321')
11
11
  expect(@session).to have_button(:crap321)
12
+ expect(@session).to have_button('button with label element')
13
+ expect(@session).to have_button('button within label element')
12
14
  end
13
15
 
14
16
  it 'should be true for disabled buttons if disabled: true' do
@@ -14,8 +14,9 @@ Capybara::SpecHelper.spec '#has_css?' do
14
14
  # This was never a specifically accepted format but it has worked for a
15
15
  # lot of versions.
16
16
  # TODO: Remove in 4.0
17
- expect_any_instance_of(Kernel).to receive(:warn) # rubocop:disable RSpec/AnyInstance
17
+ allow(Capybara::Helpers).to receive(:warn).and_return(nil)
18
18
  expect(@session).to have_css(:p)
19
+ expect(Capybara::Helpers).to have_received(:warn)
19
20
  end
20
21
 
21
22
  it 'should be false if the given selector is not on the page' do
@@ -94,6 +94,13 @@ Capybara::SpecHelper.spec '#has_current_path?' do
94
94
  expect(@session).to have_current_path(nil, ignore_query: true)
95
95
  end.not_to raise_exception
96
96
  end
97
+
98
+ it 'should accept a filter block that receives Addressable::URL' do
99
+ @session.visit('/with_js?a=3&b=defgh')
100
+ expect(@session).to have_current_path('/with_js', ignore_query: true) { |url|
101
+ url.query_values.keys == %w[a b]
102
+ }
103
+ end
97
104
  end
98
105
 
99
106
  Capybara::SpecHelper.spec '#has_no_current_path?' do
@@ -135,4 +142,10 @@ Capybara::SpecHelper.spec '#has_no_current_path?' do
135
142
  expect(@session).not_to have_current_path('/with_js', ignore_query: true)
136
143
  end.not_to raise_exception
137
144
  end
145
+
146
+ it 'should accept a filter block that receives Addressable::URL' do
147
+ expect(@session).to have_no_current_path('/with_js', ignore_query: true) { |url|
148
+ !url.query.nil?
149
+ }
150
+ end
138
151
  end
@@ -133,17 +133,6 @@ Capybara::SpecHelper.spec '#has_text?' do
133
133
  expect(@session).to have_text(:visible, with_to_hash, {})
134
134
  end
135
135
  end
136
-
137
- it 'should fail if passed without empty options' do
138
- with_to_hash = Class.new do
139
- def to_s; '42' end
140
- def to_hash; { blah: 'Other hash' } end
141
- end.new
142
- @session.visit('/with_html')
143
- expect do
144
- expect(@session).to have_text(:visible, with_to_hash)
145
- end.to raise_error(ArgumentError)
146
- end
147
136
  end
148
137
 
149
138
  context 'with exact: true option' do
@@ -28,8 +28,8 @@ Capybara::SpecHelper.spec '#matches_style?', requires: [:css] do
28
28
  output(/have_style is deprecated/).to_stderr
29
29
 
30
30
  el = @session.find(:css, '#first')
31
- allow(el).to receive(:warn).and_return(nil)
31
+ allow(Capybara::Helpers).to receive(:warn).and_return(nil)
32
32
  el.has_style?('display' => /^bl/)
33
- expect(el).to have_received(:warn)
33
+ expect(Capybara::Helpers).to have_received(:warn)
34
34
  end
35
35
  end
@@ -388,8 +388,8 @@ Capybara::SpecHelper.spec 'node' do
388
388
 
389
389
  describe '#==' do
390
390
  it 'preserve object identity' do
391
- expect(@session.find('//h1') == @session.find('//h1')).to be true # rubocop:disable Lint/UselessComparison
392
- expect(@session.find('//h1') === @session.find('//h1')).to be true # rubocop:disable Style/CaseEquality, Lint/UselessComparison
391
+ expect(@session.find('//h1') == @session.find('//h1')).to be true # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands
392
+ expect(@session.find('//h1') === @session.find('//h1')).to be true # rubocop:disable Style/CaseEquality, Lint/BinaryOperatorWithIdenticalOperands
393
393
  expect(@session.find('//h1').eql?(@session.find('//h1'))).to be false
394
394
  end
395
395
 
@@ -409,6 +409,17 @@ Capybara::SpecHelper.spec 'node' do
409
409
  element = @session.find(:link, 'Second Link')
410
410
  expect(@session.find(:xpath, element.path)).to eq(element)
411
411
  end
412
+
413
+ it 'reports when element in shadow dom', requires: [:shadow_dom] do
414
+ @session.visit('/with_js')
415
+ shadow = @session.find(:css, '#shadow')
416
+ element = @session.evaluate_script(<<~JS, shadow)
417
+ (function(root){
418
+ return root.shadowRoot.querySelector('span');
419
+ })(arguments[0])
420
+ JS
421
+ expect(element.path).to eq '(: Shadow DOM element - no XPath :)'
422
+ end
412
423
  end
413
424
 
414
425
  describe '#trigger', requires: %i[js trigger] do
@@ -671,6 +682,15 @@ Capybara::SpecHelper.spec 'node' do
671
682
  expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped string: text/plain Some dropped text")]')
672
683
  end
673
684
 
685
+ it 'can drop a pathname' do
686
+ @session.visit('/with_js')
687
+ target = @session.find('//div[@id="drop_html5"]')
688
+ target.drop(
689
+ Pathname.new(with_os_path_separators(File.expand_path('../fixtures/capybara.jpg', File.dirname(__FILE__))))
690
+ )
691
+ expect(@session).to have_xpath('//div[contains(., "HTML5 Dropped file: capybara.jpg")]')
692
+ end
693
+
674
694
  it 'can drop multiple strings' do
675
695
  @session.visit('/with_js')
676
696
  target = @session.find('//div[@id="drop_html5"]')
@@ -25,6 +25,7 @@ Capybara::SpecHelper.spec '#refresh' do
25
25
  @session.visit('/form')
26
26
  @session.select('Sweden', from: 'form_region')
27
27
  @session.click_button('awesome')
28
+ sleep 2
28
29
  expect do
29
30
  @session.refresh
30
31
  sleep 2
@@ -31,7 +31,7 @@ Capybara::SpecHelper.spec '#save_page' do
31
31
  it 'can store files in a specified directory' do
32
32
  Capybara.save_path = alternative_path
33
33
  @session.save_page
34
- path = Dir.glob(alternative_path + '/capybara-*.html').first
34
+ path = Dir.glob("#{alternative_path}/capybara-*.html").first
35
35
  expect(File.read(path)).to include('Another World')
36
36
  end
37
37
 
@@ -43,14 +43,14 @@ Capybara::SpecHelper.spec '#save_page' do
43
43
  it 'can store files in a specified directory with a given filename' do
44
44
  Capybara.save_path = alternative_path
45
45
  @session.save_page('capybara-001133.html')
46
- path = alternative_path + '/capybara-001133.html'
46
+ path = "#{alternative_path}/capybara-001133.html"
47
47
  expect(File.read(path)).to include('Another World')
48
48
  end
49
49
 
50
50
  it 'can store files in a specified directory with a given relative filename' do
51
51
  Capybara.save_path = alternative_path
52
52
  @session.save_page('tmp/capybara-001144.html')
53
- path = alternative_path + '/tmp/capybara-001144.html'
53
+ path = "#{alternative_path}/tmp/capybara-001144.html"
54
54
  expect(File.read(path)).to include('Another World')
55
55
  end
56
56
 
@@ -63,7 +63,7 @@ Capybara::SpecHelper.spec '#save_page' do
63
63
  it 'returns an absolute path in given directory' do
64
64
  Capybara.save_path = alternative_path
65
65
  result = @session.save_page
66
- path = File.expand_path(Dir.glob(alternative_path + '/capybara-*.html').first, alternative_path)
66
+ path = File.expand_path(Dir.glob("#{alternative_path}/capybara-*.html").first, alternative_path)
67
67
  expect(result).to eq(path)
68
68
  end
69
69
 
@@ -58,7 +58,7 @@ module Capybara
58
58
 
59
59
  def run_specs(session, name, **options, &filter_block)
60
60
  specs = @specs
61
- RSpec.describe Capybara::Session, name, options do # rubocop:disable RSpec/EmptyExampleGroup
61
+ RSpec.describe Capybara::Session, name, options do
62
62
  include Capybara::SpecHelper
63
63
  include Capybara::RSpecMatchers
64
64
 
@@ -72,11 +72,11 @@ module Capybara
72
72
  end
73
73
 
74
74
  before :each, psc: true do
75
- SpecHelper.reset_threadsafe(true, session)
75
+ SpecHelper.reset_threadsafe(bool: true, session: session)
76
76
  end
77
77
 
78
78
  after psc: true do
79
- SpecHelper.reset_threadsafe(false, session)
79
+ SpecHelper.reset_threadsafe(session: session)
80
80
  end
81
81
 
82
82
  before :each, :exact_false do
@@ -84,15 +84,16 @@ module Capybara
84
84
  end
85
85
 
86
86
  specs.each do |spec_name, spec_options, block|
87
- describe spec_name, *spec_options do # rubocop:disable RSpec/EmptyExampleGroup
87
+ describe spec_name, *spec_options do
88
88
  class_eval(&block)
89
89
  end
90
90
  end
91
91
  end
92
92
  end
93
93
 
94
- def reset_threadsafe(bool = false, session = nil)
95
- Capybara::Session.class_variable_set(:@@instance_created, false) # Work around limit on when threadsafe can be changed
94
+ def reset_threadsafe(bool: false, session: nil)
95
+ # Work around limit on when threadsafe can be changed
96
+ Capybara::Session.class_variable_set(:@@instance_created, false) # rubocop:disable Style/ClassVars
96
97
  Capybara.threadsafe = bool
97
98
  session = session.current_session if session.respond_to?(:current_session)
98
99
  session&.instance_variable_set(:@config, nil)
@@ -108,11 +109,9 @@ module Capybara
108
109
  stream.reopen(old_stream)
109
110
  end
110
111
 
111
- def quietly
112
- silence_stream(STDOUT) do
113
- silence_stream(STDERR) do
114
- yield
115
- end
112
+ def quietly(&block)
113
+ silence_stream($stdout) do
114
+ silence_stream($stderr, &block)
116
115
  end
117
116
  end
118
117
 
@@ -132,4 +131,4 @@ module Capybara
132
131
  end
133
132
  end
134
133
 
135
- Dir[File.dirname(__FILE__) + '/session/**/*.rb'].each { |file| require_relative file }
134
+ Dir["#{File.dirname(__FILE__)}/session/**/*.rb"].each { |file| require_relative file }
@@ -9,6 +9,7 @@ class TestApp < Sinatra::Base
9
9
  class TestAppError < Exception; end # rubocop:disable Lint/InheritException
10
10
  class TestAppOtherError < Exception # rubocop:disable Lint/InheritException
11
11
  def initialize(string1, msg)
12
+ super()
12
13
  @something = string1
13
14
  @message = msg
14
15
  end
@@ -33,6 +34,10 @@ class TestApp < Sinatra::Base
33
34
  redirect '/redirect_again'
34
35
  end
35
36
 
37
+ get '/redirect_with_fragment' do
38
+ redirect '/landed#with_fragment'
39
+ end
40
+
36
41
  get '/redirect_again' do
37
42
  redirect '/landed'
38
43
  end
@@ -85,11 +90,11 @@ class TestApp < Sinatra::Base
85
90
  end
86
91
 
87
92
  get '/form/get' do
88
- '<pre id="results">' + params[:form].to_yaml + '</pre>'
93
+ %(<pre id="results">#{params[:form].to_yaml}</pre>)
89
94
  end
90
95
 
91
96
  post '/relative' do
92
- '<pre id="results">' + params[:form].to_yaml + '</pre>'
97
+ %(<pre id="results">#{params[:form].to_yaml}</pre>)
93
98
  end
94
99
 
95
100
  get '/favicon.ico' do
@@ -176,7 +181,7 @@ class TestApp < Sinatra::Base
176
181
 
177
182
  post '/form' do
178
183
  self.class.form_post_count += 1
179
- '<pre id="results">' + params[:form].merge('post_count' => self.class.form_post_count).to_yaml + '</pre>'
184
+ %(<pre id="results">#{params[:form].merge('post_count' => self.class.form_post_count).to_yaml}</pre>)
180
185
  end
181
186
 
182
187
  post '/upload_empty' do
@@ -456,6 +456,12 @@ New line after and before textarea tag
456
456
  <button type="submit" name="form[no_value]">No Value!</button>
457
457
  <button id="no_type">No Type!</button>
458
458
  <button><img alt="A horse eating hay"/></button>
459
+ <button id="button_with_label"></button>
460
+ <label for="button_with_label">button with label element</label>
461
+ <label>
462
+ button within label element
463
+ <button></button>
464
+ </label>
459
465
  <input type="button" disabled="disabled" value="Disabled button"/>
460
466
  <span role="button">ARIA button</span>
461
467
  </p>
@@ -684,3 +690,15 @@ New line after and before textarea tag
684
690
  <p>
685
691
  <input id="special" {custom}="abcdef" value="custom attribute"/>
686
692
  </p>
693
+
694
+
695
+ <label for="multi_label_checkbox">
696
+ Label to click
697
+ </label>
698
+ <div>Something random that justifies the usage of a separate label</div>
699
+ <label>
700
+ <div>
701
+ <input type="checkbox" id="multi_label_checkbox" style="display: none"/>
702
+ <div>Visual representation of the checkbox</div>
703
+ </div>
704
+ </label>
@@ -18,6 +18,14 @@
18
18
  });
19
19
  </script>
20
20
  <style>
21
+ html {
22
+ scroll-behavior: smooth;
23
+ }
24
+
25
+ body {
26
+ min-height: 2000px;
27
+ }
28
+
21
29
  .transition.away {
22
30
  width: 0%;
23
31
  }
@@ -152,6 +152,7 @@
152
152
  <p>This is an HTML5 draggable element.</p>
153
153
  </div>
154
154
 
155
+ <div id="shadow"></div>
155
156
  <script type="text/javascript">
156
157
  // a javascript comment
157
158
  var aVar = 123;
@@ -2,7 +2,7 @@
2
2
  <head>
3
3
  <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
4
4
  <title>with_sortable_js</title>
5
- <script src="https://sortablejs.github.io/Sortable/Sortable.js" type="text/javascript"></script>
5
+ <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.12.0/dist/sortable.umd.min.js" type="text/javascript"></script>
6
6
  </head>
7
7
 
8
8
  <body id="with_sortable_js">