actionpack 4.2.10 → 7.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +86 -600
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -14
- data/lib/abstract_controller/asset_paths.rb +5 -1
- data/lib/abstract_controller/base.rb +166 -136
- data/lib/abstract_controller/caching/fragments.rb +149 -0
- data/lib/abstract_controller/caching.rb +68 -0
- data/lib/abstract_controller/callbacks.rb +126 -57
- data/lib/abstract_controller/collector.rb +13 -15
- data/lib/abstract_controller/deprecator.rb +9 -0
- data/lib/abstract_controller/error.rb +8 -0
- data/lib/abstract_controller/helpers.rb +181 -132
- data/lib/abstract_controller/logger.rb +5 -1
- data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
- data/lib/abstract_controller/rendering.rb +56 -56
- data/lib/abstract_controller/translation.rb +29 -15
- data/lib/abstract_controller/url_for.rb +15 -11
- data/lib/abstract_controller.rb +21 -5
- data/lib/action_controller/api/api_rendering.rb +18 -0
- data/lib/action_controller/api.rb +154 -0
- data/lib/action_controller/base.rb +219 -155
- data/lib/action_controller/caching.rb +28 -68
- data/lib/action_controller/deprecator.rb +9 -0
- data/lib/action_controller/form_builder.rb +55 -0
- data/lib/action_controller/log_subscriber.rb +35 -22
- data/lib/action_controller/metal/allow_browser.rb +119 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
- data/lib/action_controller/metal/conditional_get.rb +259 -122
- data/lib/action_controller/metal/content_security_policy.rb +86 -0
- data/lib/action_controller/metal/cookies.rb +9 -5
- data/lib/action_controller/metal/data_streaming.rb +87 -104
- data/lib/action_controller/metal/default_headers.rb +21 -0
- data/lib/action_controller/metal/etag_with_flash.rb +22 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
- data/lib/action_controller/metal/exceptions.rb +71 -24
- data/lib/action_controller/metal/flash.rb +26 -19
- data/lib/action_controller/metal/head.rb +45 -36
- data/lib/action_controller/metal/helpers.rb +80 -64
- data/lib/action_controller/metal/http_authentication.rb +297 -244
- data/lib/action_controller/metal/implicit_render.rb +57 -9
- data/lib/action_controller/metal/instrumentation.rb +76 -64
- data/lib/action_controller/metal/live.rb +238 -176
- data/lib/action_controller/metal/logging.rb +22 -0
- data/lib/action_controller/metal/mime_responds.rb +177 -166
- data/lib/action_controller/metal/parameter_encoding.rb +84 -0
- data/lib/action_controller/metal/params_wrapper.rb +145 -118
- data/lib/action_controller/metal/permissions_policy.rb +38 -0
- data/lib/action_controller/metal/rate_limiting.rb +62 -0
- data/lib/action_controller/metal/redirecting.rb +203 -64
- data/lib/action_controller/metal/renderers.rb +108 -65
- data/lib/action_controller/metal/rendering.rb +216 -56
- data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
- data/lib/action_controller/metal/rescue.rb +19 -21
- data/lib/action_controller/metal/streaming.rb +179 -138
- data/lib/action_controller/metal/strong_parameters.rb +1058 -382
- data/lib/action_controller/metal/testing.rb +11 -17
- data/lib/action_controller/metal/url_for.rb +37 -21
- data/lib/action_controller/metal.rb +236 -138
- data/lib/action_controller/railtie.rb +89 -11
- data/lib/action_controller/railties/helpers.rb +5 -1
- data/lib/action_controller/renderer.rb +161 -0
- data/lib/action_controller/template_assertions.rb +13 -0
- data/lib/action_controller/test_case.rb +425 -497
- data/lib/action_controller.rb +44 -22
- data/lib/action_dispatch/constants.rb +34 -0
- data/lib/action_dispatch/deprecator.rb +9 -0
- data/lib/action_dispatch/http/cache.rb +119 -63
- data/lib/action_dispatch/http/content_disposition.rb +47 -0
- data/lib/action_dispatch/http/content_security_policy.rb +364 -0
- data/lib/action_dispatch/http/filter_parameters.rb +36 -34
- data/lib/action_dispatch/http/filter_redirect.rb +24 -12
- data/lib/action_dispatch/http/headers.rb +66 -31
- data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
- data/lib/action_dispatch/http/mime_type.rb +196 -136
- data/lib/action_dispatch/http/mime_types.rb +25 -7
- data/lib/action_dispatch/http/parameters.rb +97 -45
- data/lib/action_dispatch/http/permissions_policy.rb +187 -0
- data/lib/action_dispatch/http/rack_cache.rb +6 -0
- data/lib/action_dispatch/http/request.rb +299 -170
- data/lib/action_dispatch/http/response.rb +311 -160
- data/lib/action_dispatch/http/upload.rb +52 -23
- data/lib/action_dispatch/http/url.rb +201 -125
- data/lib/action_dispatch/journey/formatter.rb +110 -50
- data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
- data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
- data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
- data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
- data/lib/action_dispatch/journey/nodes/node.rb +100 -20
- data/lib/action_dispatch/journey/parser.rb +19 -17
- data/lib/action_dispatch/journey/parser.y +4 -3
- data/lib/action_dispatch/journey/parser_extras.rb +14 -4
- data/lib/action_dispatch/journey/path/pattern.rb +79 -63
- data/lib/action_dispatch/journey/route.rb +108 -44
- data/lib/action_dispatch/journey/router/utils.rb +41 -29
- data/lib/action_dispatch/journey/router.rb +64 -57
- data/lib/action_dispatch/journey/routes.rb +23 -21
- data/lib/action_dispatch/journey/scanner.rb +28 -17
- data/lib/action_dispatch/journey/visitors.rb +100 -54
- data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
- data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
- data/lib/action_dispatch/journey.rb +7 -5
- data/lib/action_dispatch/log_subscriber.rb +25 -0
- data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
- data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
- data/lib/action_dispatch/middleware/callbacks.rb +7 -6
- data/lib/action_dispatch/middleware/cookies.rb +471 -328
- data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
- data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
- data/lib/action_dispatch/middleware/debug_view.rb +73 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
- data/lib/action_dispatch/middleware/executor.rb +32 -0
- data/lib/action_dispatch/middleware/flash.rb +143 -101
- data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
- data/lib/action_dispatch/middleware/reloader.rb +10 -92
- data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
- data/lib/action_dispatch/middleware/request_id.rb +29 -15
- data/lib/action_dispatch/middleware/server_timing.rb +78 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
- data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
- data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
- data/lib/action_dispatch/middleware/ssl.rb +134 -36
- data/lib/action_dispatch/middleware/stack.rb +109 -44
- data/lib/action_dispatch/middleware/static.rb +159 -90
- data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
- data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
- data/lib/action_dispatch/railtie.rb +44 -16
- data/lib/action_dispatch/request/session.rb +159 -69
- data/lib/action_dispatch/request/utils.rb +97 -23
- data/lib/action_dispatch/routing/endpoint.rb +11 -2
- data/lib/action_dispatch/routing/inspector.rb +195 -106
- data/lib/action_dispatch/routing/mapper.rb +1338 -955
- data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
- data/lib/action_dispatch/routing/redirection.rb +78 -51
- data/lib/action_dispatch/routing/route_set.rb +460 -374
- data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
- data/lib/action_dispatch/routing/url_for.rb +172 -124
- data/lib/action_dispatch/routing.rb +159 -158
- data/lib/action_dispatch/system_test_case.rb +206 -0
- data/lib/action_dispatch/system_testing/browser.rb +84 -0
- data/lib/action_dispatch/system_testing/driver.rb +85 -0
- data/lib/action_dispatch/system_testing/server.rb +33 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
- data/lib/action_dispatch/testing/assertion_response.rb +48 -0
- data/lib/action_dispatch/testing/assertions/response.rb +71 -39
- data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
- data/lib/action_dispatch/testing/assertions.rb +9 -6
- data/lib/action_dispatch/testing/integration.rb +486 -306
- data/lib/action_dispatch/testing/request_encoder.rb +60 -0
- data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
- data/lib/action_dispatch/testing/test_process.rb +35 -22
- data/lib/action_dispatch/testing/test_request.rb +29 -34
- data/lib/action_dispatch/testing/test_response.rb +48 -15
- data/lib/action_dispatch.rb +82 -40
- data/lib/action_pack/gem_version.rb +8 -4
- data/lib/action_pack/version.rb +6 -2
- data/lib/action_pack.rb +21 -18
- metadata +146 -56
- data/lib/action_controller/caching/fragments.rb +0 -103
- data/lib/action_controller/metal/force_ssl.rb +0 -97
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/http/parameter_filter.rb +0 -72
- data/lib/action_dispatch/journey/backwards.rb +0 -5
- data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
- data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
- data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/middleware/params_parser.rb +0 -60
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module SystemTesting
|
7
|
+
class Browser # :nodoc:
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(name)
|
11
|
+
@name = name
|
12
|
+
set_default_options
|
13
|
+
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
case name
|
17
|
+
when :headless_chrome
|
18
|
+
:chrome
|
19
|
+
when :headless_firefox
|
20
|
+
:firefox
|
21
|
+
else
|
22
|
+
name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def options
|
27
|
+
@options ||=
|
28
|
+
case type
|
29
|
+
when :chrome
|
30
|
+
::Selenium::WebDriver::Chrome::Options.new
|
31
|
+
when :firefox
|
32
|
+
::Selenium::WebDriver::Firefox::Options.new
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def configure
|
37
|
+
yield options if block_given?
|
38
|
+
end
|
39
|
+
|
40
|
+
# driver_path is lazily initialized by default. Eagerly set it to avoid race
|
41
|
+
# conditions when using parallel tests.
|
42
|
+
def preload
|
43
|
+
case type
|
44
|
+
when :chrome
|
45
|
+
resolve_driver_path(::Selenium::WebDriver::Chrome)
|
46
|
+
when :firefox
|
47
|
+
resolve_driver_path(::Selenium::WebDriver::Firefox)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def set_default_options
|
53
|
+
case name
|
54
|
+
when :headless_chrome
|
55
|
+
set_headless_chrome_browser_options
|
56
|
+
when :headless_firefox
|
57
|
+
set_headless_firefox_browser_options
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_headless_chrome_browser_options
|
62
|
+
configure do |capabilities|
|
63
|
+
capabilities.add_argument("--headless")
|
64
|
+
capabilities.add_argument("--disable-gpu") if Gem.win_platform?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_headless_firefox_browser_options
|
69
|
+
configure do |capabilities|
|
70
|
+
capabilities.add_argument("-headless")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def resolve_driver_path(namespace)
|
75
|
+
# The path method has been deprecated in 4.20.0
|
76
|
+
if Gem::Version.new(::Selenium::WebDriver::VERSION) >= Gem::Version.new("4.20.0")
|
77
|
+
namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.new(options, namespace::Service.new).driver_path
|
78
|
+
else
|
79
|
+
namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.path(options, namespace::Service)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module SystemTesting
|
7
|
+
class Driver # :nodoc:
|
8
|
+
attr_reader :name
|
9
|
+
|
10
|
+
def initialize(driver_type, **options, &capabilities)
|
11
|
+
@driver_type = driver_type
|
12
|
+
@screen_size = options[:screen_size]
|
13
|
+
@options = options[:options] || {}
|
14
|
+
@name = @options.delete(:name) || driver_type
|
15
|
+
@capabilities = capabilities
|
16
|
+
|
17
|
+
if driver_type == :selenium
|
18
|
+
gem "selenium-webdriver", ">= 4.0.0"
|
19
|
+
require "selenium/webdriver"
|
20
|
+
@browser = Browser.new(options[:using])
|
21
|
+
@browser.preload unless @options[:browser] == :remote
|
22
|
+
else
|
23
|
+
@browser = nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def use
|
28
|
+
register if registerable?
|
29
|
+
|
30
|
+
setup
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def registerable?
|
35
|
+
[:selenium, :cuprite, :rack_test, :playwright].include?(@driver_type)
|
36
|
+
end
|
37
|
+
|
38
|
+
def register
|
39
|
+
@browser&.configure(&@capabilities)
|
40
|
+
|
41
|
+
Capybara.register_driver name do |app|
|
42
|
+
case @driver_type
|
43
|
+
when :selenium then register_selenium(app)
|
44
|
+
when :cuprite then register_cuprite(app)
|
45
|
+
when :rack_test then register_rack_test(app)
|
46
|
+
when :playwright then register_playwright(app)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def browser_options
|
52
|
+
@options.merge(options: @browser.options).compact
|
53
|
+
end
|
54
|
+
|
55
|
+
def register_selenium(app)
|
56
|
+
Capybara::Selenium::Driver.new(app, browser: @browser.type, **browser_options).tap do |driver|
|
57
|
+
driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def register_cuprite(app)
|
62
|
+
Capybara::Cuprite::Driver.new(app, @options.merge(window_size: @screen_size))
|
63
|
+
end
|
64
|
+
|
65
|
+
def register_rack_test(app)
|
66
|
+
Capybara::RackTest::Driver.new(app, respect_data_method: true, **@options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def register_playwright(app)
|
70
|
+
screen = { width: @screen_size[0], height: @screen_size[1] } if @screen_size
|
71
|
+
options = {
|
72
|
+
screen: screen,
|
73
|
+
viewport: screen,
|
74
|
+
**@options
|
75
|
+
}.compact
|
76
|
+
|
77
|
+
Capybara::Playwright::Driver.new(app, **options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def setup
|
81
|
+
Capybara.current_driver = name
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module SystemTesting
|
7
|
+
class Server # :nodoc:
|
8
|
+
class << self
|
9
|
+
attr_accessor :silence_puma
|
10
|
+
end
|
11
|
+
|
12
|
+
self.silence_puma = false
|
13
|
+
|
14
|
+
def run
|
15
|
+
setup
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def setup
|
20
|
+
set_server
|
21
|
+
set_port
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_server
|
25
|
+
Capybara.server = :puma, { Silent: self.class.silence_puma } if Capybara.server == Capybara.servers[:default]
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_port
|
29
|
+
Capybara.always_include_port = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module SystemTesting
|
7
|
+
module TestHelpers
|
8
|
+
# Screenshot helper for system testing.
|
9
|
+
module ScreenshotHelper
|
10
|
+
# Takes a screenshot of the current page in the browser.
|
11
|
+
#
|
12
|
+
# `take_screenshot` can be used at any point in your system tests to take a
|
13
|
+
# screenshot of the current state. This can be useful for debugging or
|
14
|
+
# automating visual testing. You can take multiple screenshots per test to
|
15
|
+
# investigate changes at different points during your test. These will be named
|
16
|
+
# with a sequential prefix (or 'failed' for failing tests)
|
17
|
+
#
|
18
|
+
# The default screenshots directory is `tmp/screenshots` but you can set a
|
19
|
+
# different one with `Capybara.save_path`
|
20
|
+
#
|
21
|
+
# You can use the `html` argument or set the
|
22
|
+
# `RAILS_SYSTEM_TESTING_SCREENSHOT_HTML` environment variable to save the HTML
|
23
|
+
# from the page that is being screenshotted so you can investigate the elements
|
24
|
+
# on the page at the time of the screenshot
|
25
|
+
#
|
26
|
+
# You can use the `screenshot` argument or set the
|
27
|
+
# `RAILS_SYSTEM_TESTING_SCREENSHOT` environment variable to control the output.
|
28
|
+
# Possible values are:
|
29
|
+
# `simple` (default)
|
30
|
+
# : Only displays the screenshot path. This is the default value.
|
31
|
+
#
|
32
|
+
# `inline`
|
33
|
+
# : Display the screenshot in the terminal using the iTerm image protocol
|
34
|
+
# (https://iterm2.com/documentation-images.html).
|
35
|
+
#
|
36
|
+
# `artifact`
|
37
|
+
# : Display the screenshot in the terminal, using the terminal artifact
|
38
|
+
# format (https://buildkite.github.io/terminal-to-html/inline-images/).
|
39
|
+
#
|
40
|
+
#
|
41
|
+
def take_screenshot(html: false, screenshot: nil)
|
42
|
+
showing_html = html || html_from_env?
|
43
|
+
|
44
|
+
increment_unique
|
45
|
+
save_html if showing_html
|
46
|
+
save_image
|
47
|
+
show display_image(html: showing_html, screenshot_output: screenshot)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Takes a screenshot of the current page in the browser if the test failed.
|
51
|
+
#
|
52
|
+
# `take_failed_screenshot` is called during system test teardown.
|
53
|
+
def take_failed_screenshot
|
54
|
+
return unless failed? && supports_screenshot? && Capybara::Session.instance_created?
|
55
|
+
|
56
|
+
take_screenshot
|
57
|
+
metadata[:failure_screenshot_path] = relative_image_path if Minitest::Runnable.method_defined?(:metadata)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
attr_accessor :_screenshot_counter
|
62
|
+
|
63
|
+
def html_from_env?
|
64
|
+
ENV["RAILS_SYSTEM_TESTING_SCREENSHOT_HTML"] == "1"
|
65
|
+
end
|
66
|
+
|
67
|
+
def increment_unique
|
68
|
+
@_screenshot_counter ||= 0
|
69
|
+
@_screenshot_counter += 1
|
70
|
+
end
|
71
|
+
|
72
|
+
def unique
|
73
|
+
failed? ? "failures" : (_screenshot_counter || 0).to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def image_name
|
77
|
+
sanitized_method_name = method_name.gsub(/[^\w]+/, "-")
|
78
|
+
name = "#{unique}_#{sanitized_method_name}"
|
79
|
+
name[0...225]
|
80
|
+
end
|
81
|
+
|
82
|
+
def image_path
|
83
|
+
absolute_image_path.to_s
|
84
|
+
end
|
85
|
+
|
86
|
+
def html_path
|
87
|
+
absolute_html_path.to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def absolute_path
|
91
|
+
Rails.root.join(screenshots_dir, image_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def screenshots_dir
|
95
|
+
Capybara.save_path.presence || "tmp/screenshots"
|
96
|
+
end
|
97
|
+
|
98
|
+
def absolute_image_path
|
99
|
+
"#{absolute_path}.png"
|
100
|
+
end
|
101
|
+
|
102
|
+
def relative_image_path
|
103
|
+
"#{absolute_path.relative_path_from(Rails.root)}.png"
|
104
|
+
end
|
105
|
+
|
106
|
+
def absolute_html_path
|
107
|
+
"#{absolute_path}.html"
|
108
|
+
end
|
109
|
+
|
110
|
+
# rubocop:disable Lint/Debugger
|
111
|
+
def save_html
|
112
|
+
page.save_page(absolute_html_path)
|
113
|
+
end
|
114
|
+
|
115
|
+
def save_image
|
116
|
+
page.save_screenshot(absolute_image_path)
|
117
|
+
end
|
118
|
+
# rubocop:enable Lint/Debugger
|
119
|
+
|
120
|
+
def output_type
|
121
|
+
# Environment variables have priority
|
122
|
+
output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] || ENV["CAPYBARA_INLINE_SCREENSHOT"]
|
123
|
+
|
124
|
+
# Default to outputting a path to the screenshot
|
125
|
+
output_type ||= "simple"
|
126
|
+
|
127
|
+
output_type
|
128
|
+
end
|
129
|
+
|
130
|
+
def show(img)
|
131
|
+
puts img
|
132
|
+
end
|
133
|
+
|
134
|
+
def display_image(html:, screenshot_output:)
|
135
|
+
message = +"[Screenshot Image]: #{image_path}\n"
|
136
|
+
message << +"[Screenshot HTML]: #{html_path}\n" if html
|
137
|
+
|
138
|
+
case screenshot_output || output_type
|
139
|
+
when "artifact"
|
140
|
+
message << "\e]1338;url=artifact://#{absolute_image_path}\a\n"
|
141
|
+
when "inline"
|
142
|
+
name = inline_base64(File.basename(absolute_image_path))
|
143
|
+
image = inline_base64(File.read(absolute_image_path))
|
144
|
+
message << "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n"
|
145
|
+
end
|
146
|
+
|
147
|
+
message
|
148
|
+
end
|
149
|
+
|
150
|
+
def inline_base64(path)
|
151
|
+
Base64.strict_encode64(path)
|
152
|
+
end
|
153
|
+
|
154
|
+
def failed?
|
155
|
+
!passed? && !skipped?
|
156
|
+
end
|
157
|
+
|
158
|
+
def supports_screenshot?
|
159
|
+
Capybara.current_driver != :rack_test
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module SystemTesting
|
7
|
+
module TestHelpers
|
8
|
+
module SetupAndTeardown # :nodoc:
|
9
|
+
def before_teardown
|
10
|
+
take_failed_screenshot
|
11
|
+
ensure
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def after_teardown
|
16
|
+
Capybara.reset_sessions!
|
17
|
+
ensure
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
# This is a class that abstracts away an asserted response. It purposely does
|
7
|
+
# not inherit from Response because it doesn't need it. That means it does not
|
8
|
+
# have headers or a body.
|
9
|
+
class AssertionResponse
|
10
|
+
attr_reader :code, :name
|
11
|
+
|
12
|
+
GENERIC_RESPONSE_CODES = { # :nodoc:
|
13
|
+
success: "2XX",
|
14
|
+
missing: "404",
|
15
|
+
redirect: "3XX",
|
16
|
+
error: "5XX"
|
17
|
+
}
|
18
|
+
|
19
|
+
# Accepts a specific response status code as an Integer (404) or String ('404')
|
20
|
+
# or a response status range as a Symbol pseudo-code (:success, indicating any
|
21
|
+
# 200-299 status code).
|
22
|
+
def initialize(code_or_name)
|
23
|
+
if code_or_name.is_a?(Symbol)
|
24
|
+
@name = code_or_name
|
25
|
+
@code = code_from_name(code_or_name)
|
26
|
+
else
|
27
|
+
@name = name_from_code(code_or_name)
|
28
|
+
@code = code_or_name
|
29
|
+
end
|
30
|
+
|
31
|
+
raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
|
32
|
+
raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
def code_and_name
|
36
|
+
"#{code}: #{name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def code_from_name(name)
|
41
|
+
GENERIC_RESPONSE_CODES[name] || Rack::Utils.status_code(name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def name_from_code(code)
|
45
|
+
GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,63 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
1
4
|
|
2
5
|
module ActionDispatch
|
3
6
|
module Assertions
|
4
|
-
# A small suite of assertions that test responses from
|
7
|
+
# A small suite of assertions that test responses from Rails applications.
|
5
8
|
module ResponseAssertions
|
9
|
+
RESPONSE_PREDICATES = { # :nodoc:
|
10
|
+
success: :successful?,
|
11
|
+
missing: :not_found?,
|
12
|
+
redirect: :redirection?,
|
13
|
+
error: :server_error?,
|
14
|
+
}
|
15
|
+
|
6
16
|
# Asserts that the response is one of the following types:
|
7
17
|
#
|
8
|
-
# *
|
9
|
-
# *
|
10
|
-
# *
|
11
|
-
# *
|
18
|
+
# * `:success` - Status code was in the 200-299 range
|
19
|
+
# * `:redirect` - Status code was in the 300-399 range
|
20
|
+
# * `:missing` - Status code was 404
|
21
|
+
# * `:error` - Status code was in the 500-599 range
|
22
|
+
#
|
12
23
|
#
|
13
|
-
# You can also pass an explicit status number like
|
14
|
-
#
|
15
|
-
#
|
24
|
+
# You can also pass an explicit status number like `assert_response(501)` or its
|
25
|
+
# symbolic equivalent `assert_response(:not_implemented)`. See
|
26
|
+
# `Rack::Utils::SYMBOL_TO_STATUS_CODE` for a full list.
|
16
27
|
#
|
17
|
-
#
|
18
|
-
#
|
28
|
+
# # Asserts that the response was a redirection
|
29
|
+
# assert_response :redirect
|
19
30
|
#
|
20
|
-
#
|
21
|
-
#
|
31
|
+
# # Asserts that the response code was status code 401 (unauthorized)
|
32
|
+
# assert_response 401
|
22
33
|
def assert_response(type, message = nil)
|
23
|
-
message ||=
|
34
|
+
message ||= generate_response_message(type)
|
24
35
|
|
25
|
-
if
|
26
|
-
|
27
|
-
assert @response.send("#{type}?"), message
|
28
|
-
else
|
29
|
-
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
|
30
|
-
if code.nil?
|
31
|
-
raise ArgumentError, "Invalid response type :#{type}"
|
32
|
-
end
|
33
|
-
assert_equal code, @response.response_code, message
|
34
|
-
end
|
36
|
+
if RESPONSE_PREDICATES.key?(type)
|
37
|
+
assert @response.public_send(RESPONSE_PREDICATES[type]), message
|
35
38
|
else
|
36
|
-
assert_equal type, @response.response_code, message
|
39
|
+
assert_equal AssertionResponse.new(type).code, @response.response_code, message
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
40
|
-
#
|
41
|
-
# This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
|
42
|
-
# match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
|
43
|
+
# Asserts that the response is a redirect to a URL matching the given options.
|
43
44
|
#
|
44
|
-
#
|
45
|
-
#
|
45
|
+
# # Asserts that the redirection was to the "index" action on the WeblogController
|
46
|
+
# assert_redirected_to controller: "weblog", action: "index"
|
46
47
|
#
|
47
|
-
#
|
48
|
-
#
|
48
|
+
# # Asserts that the redirection was to the named route login_url
|
49
|
+
# assert_redirected_to login_url
|
49
50
|
#
|
50
|
-
#
|
51
|
-
#
|
51
|
+
# # Asserts that the redirection was to the URL for @customer
|
52
|
+
# assert_redirected_to @customer
|
52
53
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
# # Asserts that the redirection matches the regular expression
|
55
|
+
# assert_redirected_to %r(\Ahttp://example.org)
|
56
|
+
#
|
57
|
+
# # Asserts that the redirection has the HTTP status code 301 (Moved
|
58
|
+
# # Permanently).
|
59
|
+
# assert_redirected_to "/some/path", status: :moved_permanently
|
60
|
+
def assert_redirected_to(url_options = {}, options = {}, message = nil)
|
61
|
+
options, message = {}, options unless options.is_a?(Hash)
|
62
|
+
|
63
|
+
status = options[:status] || :redirect
|
64
|
+
assert_response(status, message)
|
65
|
+
return true if url_options === @response.location
|
58
66
|
|
59
67
|
redirect_is = normalize_argument_to_redirection(@response.location)
|
60
|
-
redirect_expected = normalize_argument_to_redirection(
|
68
|
+
redirect_expected = normalize_argument_to_redirection(url_options)
|
61
69
|
|
62
70
|
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
|
63
71
|
assert_operator redirect_expected, :===, redirect_is, message
|
@@ -77,6 +85,30 @@ module ActionDispatch
|
|
77
85
|
handle._compute_redirect_to_location(@request, fragment)
|
78
86
|
end
|
79
87
|
end
|
88
|
+
|
89
|
+
def generate_response_message(expected, actual = @response.response_code)
|
90
|
+
(+"Expected response to be a <#{code_with_name(expected)}>,"\
|
91
|
+
" but was a <#{code_with_name(actual)}>").concat(location_if_redirected).concat(response_body_if_short)
|
92
|
+
end
|
93
|
+
|
94
|
+
def response_body_if_short
|
95
|
+
return "" if @response.body.size > 500
|
96
|
+
"\nResponse body: #{@response.body}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def location_if_redirected
|
100
|
+
return "" unless @response.redirection? && @response.location.present?
|
101
|
+
location = normalize_argument_to_redirection(@response.location)
|
102
|
+
" redirect to <#{location}>"
|
103
|
+
end
|
104
|
+
|
105
|
+
def code_with_name(code_or_name)
|
106
|
+
if RESPONSE_PREDICATES.value?("#{code_or_name}?".to_sym)
|
107
|
+
code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
|
108
|
+
end
|
109
|
+
|
110
|
+
AssertionResponse.new(code_or_name).code_and_name
|
111
|
+
end
|
80
112
|
end
|
81
113
|
end
|
82
114
|
end
|