omg-actionpack 8.0.0.alpha1

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 (187) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +129 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -0
  5. data/lib/abstract_controller/asset_paths.rb +14 -0
  6. data/lib/abstract_controller/base.rb +299 -0
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +265 -0
  10. data/lib/abstract_controller/collector.rb +44 -0
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +243 -0
  14. data/lib/abstract_controller/logger.rb +16 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +25 -0
  16. data/lib/abstract_controller/rendering.rb +126 -0
  17. data/lib/abstract_controller/translation.rb +42 -0
  18. data/lib/abstract_controller/url_for.rb +37 -0
  19. data/lib/abstract_controller.rb +36 -0
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +155 -0
  22. data/lib/action_controller/base.rb +332 -0
  23. data/lib/action_controller/caching.rb +49 -0
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +96 -0
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +341 -0
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +20 -0
  32. data/lib/action_controller/metal/data_streaming.rb +154 -0
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +59 -0
  36. data/lib/action_controller/metal/exceptions.rb +106 -0
  37. data/lib/action_controller/metal/flash.rb +67 -0
  38. data/lib/action_controller/metal/head.rb +67 -0
  39. data/lib/action_controller/metal/helpers.rb +129 -0
  40. data/lib/action_controller/metal/http_authentication.rb +565 -0
  41. data/lib/action_controller/metal/implicit_render.rb +67 -0
  42. data/lib/action_controller/metal/instrumentation.rb +120 -0
  43. data/lib/action_controller/metal/live.rb +398 -0
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +337 -0
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +312 -0
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +251 -0
  51. data/lib/action_controller/metal/renderers.rb +181 -0
  52. data/lib/action_controller/metal/rendering.rb +260 -0
  53. data/lib/action_controller/metal/request_forgery_protection.rb +667 -0
  54. data/lib/action_controller/metal/rescue.rb +33 -0
  55. data/lib/action_controller/metal/streaming.rb +183 -0
  56. data/lib/action_controller/metal/strong_parameters.rb +1546 -0
  57. data/lib/action_controller/metal/testing.rb +25 -0
  58. data/lib/action_controller/metal/url_for.rb +65 -0
  59. data/lib/action_controller/metal.rb +339 -0
  60. data/lib/action_controller/railtie.rb +149 -0
  61. data/lib/action_controller/railties/helpers.rb +26 -0
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +691 -0
  65. data/lib/action_controller.rb +80 -0
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +249 -0
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +365 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +80 -0
  72. data/lib/action_dispatch/http/filter_redirect.rb +50 -0
  73. data/lib/action_dispatch/http/headers.rb +134 -0
  74. data/lib/action_dispatch/http/mime_negotiation.rb +187 -0
  75. data/lib/action_dispatch/http/mime_type.rb +389 -0
  76. data/lib/action_dispatch/http/mime_types.rb +54 -0
  77. data/lib/action_dispatch/http/parameters.rb +119 -0
  78. data/lib/action_dispatch/http/permissions_policy.rb +189 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +67 -0
  80. data/lib/action_dispatch/http/request.rb +498 -0
  81. data/lib/action_dispatch/http/response.rb +556 -0
  82. data/lib/action_dispatch/http/upload.rb +107 -0
  83. data/lib/action_dispatch/http/url.rb +344 -0
  84. data/lib/action_dispatch/journey/formatter.rb +226 -0
  85. data/lib/action_dispatch/journey/gtg/builder.rb +149 -0
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +50 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +217 -0
  88. data/lib/action_dispatch/journey/nfa/dot.rb +27 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +208 -0
  90. data/lib/action_dispatch/journey/parser.rb +103 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +209 -0
  92. data/lib/action_dispatch/journey/route.rb +189 -0
  93. data/lib/action_dispatch/journey/router/utils.rb +105 -0
  94. data/lib/action_dispatch/journey/router.rb +151 -0
  95. data/lib/action_dispatch/journey/routes.rb +82 -0
  96. data/lib/action_dispatch/journey/scanner.rb +70 -0
  97. data/lib/action_dispatch/journey/visitors.rb +267 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +159 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/journey.rb +7 -0
  102. data/lib/action_dispatch/log_subscriber.rb +25 -0
  103. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  104. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  105. data/lib/action_dispatch/middleware/callbacks.rb +38 -0
  106. data/lib/action_dispatch/middleware/cookies.rb +719 -0
  107. data/lib/action_dispatch/middleware/debug_exceptions.rb +206 -0
  108. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  109. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  110. data/lib/action_dispatch/middleware/exception_wrapper.rb +350 -0
  111. data/lib/action_dispatch/middleware/executor.rb +32 -0
  112. data/lib/action_dispatch/middleware/flash.rb +318 -0
  113. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  114. data/lib/action_dispatch/middleware/public_exceptions.rb +64 -0
  115. data/lib/action_dispatch/middleware/reloader.rb +16 -0
  116. data/lib/action_dispatch/middleware/remote_ip.rb +199 -0
  117. data/lib/action_dispatch/middleware/request_id.rb +50 -0
  118. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  119. data/lib/action_dispatch/middleware/session/abstract_store.rb +112 -0
  120. data/lib/action_dispatch/middleware/session/cache_store.rb +66 -0
  121. data/lib/action_dispatch/middleware/session/cookie_store.rb +129 -0
  122. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +34 -0
  123. data/lib/action_dispatch/middleware/show_exceptions.rb +88 -0
  124. data/lib/action_dispatch/middleware/ssl.rb +180 -0
  125. data/lib/action_dispatch/middleware/stack.rb +194 -0
  126. data/lib/action_dispatch/middleware/static.rb +192 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +35 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +284 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  148. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  149. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  150. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  151. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  152. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  153. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +19 -0
  154. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +232 -0
  155. data/lib/action_dispatch/railtie.rb +77 -0
  156. data/lib/action_dispatch/request/session.rb +283 -0
  157. data/lib/action_dispatch/request/utils.rb +109 -0
  158. data/lib/action_dispatch/routing/endpoint.rb +19 -0
  159. data/lib/action_dispatch/routing/inspector.rb +323 -0
  160. data/lib/action_dispatch/routing/mapper.rb +2372 -0
  161. data/lib/action_dispatch/routing/polymorphic_routes.rb +363 -0
  162. data/lib/action_dispatch/routing/redirection.rb +218 -0
  163. data/lib/action_dispatch/routing/route_set.rb +958 -0
  164. data/lib/action_dispatch/routing/routes_proxy.rb +66 -0
  165. data/lib/action_dispatch/routing/url_for.rb +244 -0
  166. data/lib/action_dispatch/routing.rb +262 -0
  167. data/lib/action_dispatch/system_test_case.rb +206 -0
  168. data/lib/action_dispatch/system_testing/browser.rb +75 -0
  169. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  170. data/lib/action_dispatch/system_testing/server.rb +33 -0
  171. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  172. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  173. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  174. data/lib/action_dispatch/testing/assertions/response.rb +114 -0
  175. data/lib/action_dispatch/testing/assertions/routing.rb +343 -0
  176. data/lib/action_dispatch/testing/assertions.rb +25 -0
  177. data/lib/action_dispatch/testing/integration.rb +694 -0
  178. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  179. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  180. data/lib/action_dispatch/testing/test_process.rb +57 -0
  181. data/lib/action_dispatch/testing/test_request.rb +73 -0
  182. data/lib/action_dispatch/testing/test_response.rb +58 -0
  183. data/lib/action_dispatch.rb +147 -0
  184. data/lib/action_pack/gem_version.rb +19 -0
  185. data/lib/action_pack/version.rb +12 -0
  186. data/lib/action_pack.rb +27 -0
  187. metadata +375 -0
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ gem "capybara", ">= 3.26"
6
+
7
+ require "capybara/dsl"
8
+ require "capybara/minitest"
9
+ require "action_controller"
10
+ require "action_dispatch/system_testing/driver"
11
+ require "action_dispatch/system_testing/browser"
12
+ require "action_dispatch/system_testing/server"
13
+ require "action_dispatch/system_testing/test_helpers/screenshot_helper"
14
+ require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
15
+
16
+ module ActionDispatch
17
+ # # System Testing
18
+ #
19
+ # System tests let you test applications in the browser. Because system tests
20
+ # use a real browser experience, you can test all of your JavaScript easily from
21
+ # your test suite.
22
+ #
23
+ # To create a system test in your application, extend your test class from
24
+ # `ApplicationSystemTestCase`. System tests use Capybara as a base and allow you
25
+ # to configure the settings through your `application_system_test_case.rb` file
26
+ # that is generated with a new application or scaffold.
27
+ #
28
+ # Here is an example system test:
29
+ #
30
+ # require "application_system_test_case"
31
+ #
32
+ # class Users::CreateTest < ApplicationSystemTestCase
33
+ # test "adding a new user" do
34
+ # visit users_path
35
+ # click_on 'New User'
36
+ #
37
+ # fill_in 'Name', with: 'Arya'
38
+ # click_on 'Create User'
39
+ #
40
+ # assert_text 'Arya'
41
+ # end
42
+ # end
43
+ #
44
+ # When generating an application or scaffold, an
45
+ # `application_system_test_case.rb` file will also be generated containing the
46
+ # base class for system testing. This is where you can change the driver, add
47
+ # Capybara settings, and other configuration for your system tests.
48
+ #
49
+ # require "test_helper"
50
+ #
51
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
52
+ # driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
53
+ # end
54
+ #
55
+ # By default, `ActionDispatch::SystemTestCase` is driven by the Selenium driver,
56
+ # with the Chrome browser, and a browser size of 1400x1400.
57
+ #
58
+ # Changing the driver configuration options is easy. Let's say you want to use
59
+ # the Firefox browser instead of Chrome. In your
60
+ # `application_system_test_case.rb` file add the following:
61
+ #
62
+ # require "test_helper"
63
+ #
64
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
65
+ # driven_by :selenium, using: :firefox
66
+ # end
67
+ #
68
+ # `driven_by` has a required argument for the driver name. The keyword arguments
69
+ # are `:using` for the browser and `:screen_size` to change the size of the
70
+ # browser screen. These two options are not applicable for headless drivers and
71
+ # will be silently ignored if passed.
72
+ #
73
+ # Headless browsers such as headless Chrome and headless Firefox are also
74
+ # supported. You can use these browsers by setting the `:using` argument to
75
+ # `:headless_chrome` or `:headless_firefox`.
76
+ #
77
+ # To use a headless driver, like Cuprite, update your Gemfile to use Cuprite
78
+ # instead of Selenium and then declare the driver name in the
79
+ # `application_system_test_case.rb` file. In this case, you would leave out the
80
+ # `:using` option because the driver is headless, but you can still use
81
+ # `:screen_size` to change the size of the browser screen, also you can use
82
+ # `:options` to pass options supported by the driver. Please refer to your
83
+ # driver documentation to learn about supported options.
84
+ #
85
+ # require "test_helper"
86
+ # require "capybara/cuprite"
87
+ #
88
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
89
+ # driven_by :cuprite, screen_size: [1400, 1400], options:
90
+ # { js_errors: true }
91
+ # end
92
+ #
93
+ # Some drivers require browser capabilities to be passed as a block instead of
94
+ # through the `options` hash.
95
+ #
96
+ # As an example, if you want to add mobile emulation on chrome, you'll have to
97
+ # create an instance of selenium's `Chrome::Options` object and add capabilities
98
+ # with a block.
99
+ #
100
+ # The block will be passed an instance of `<Driver>::Options` where you can
101
+ # define the capabilities you want. Please refer to your driver documentation to
102
+ # learn about supported options.
103
+ #
104
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
105
+ # driven_by :selenium, using: :chrome, screen_size: [1024, 768] do |driver_option|
106
+ # driver_option.add_emulation(device_name: 'iPhone 6')
107
+ # driver_option.add_extension('path/to/chrome_extension.crx')
108
+ # end
109
+ # end
110
+ #
111
+ # Because `ActionDispatch::SystemTestCase` is a shim between Capybara and Rails,
112
+ # any driver that is supported by Capybara is supported by system tests as long
113
+ # as you include the required gems and files.
114
+ class SystemTestCase < ActiveSupport::TestCase
115
+ include Capybara::DSL
116
+ include Capybara::Minitest::Assertions
117
+ include SystemTesting::TestHelpers::SetupAndTeardown
118
+ include SystemTesting::TestHelpers::ScreenshotHelper
119
+
120
+ DEFAULT_HOST = "http://127.0.0.1"
121
+
122
+ def initialize(*) # :nodoc:
123
+ super
124
+ self.class.driven_by(:selenium) unless self.class.driver?
125
+ self.class.driver.use
126
+ end
127
+
128
+ def self.start_application # :nodoc:
129
+ Capybara.app = Rack::Builder.new do
130
+ map "/" do
131
+ run Rails.application
132
+ end
133
+ end
134
+
135
+ SystemTesting::Server.new.run
136
+ end
137
+
138
+ class_attribute :driver, instance_accessor: false
139
+
140
+ # System Test configuration options
141
+ #
142
+ # The default settings are Selenium, using Chrome, with a screen size of
143
+ # 1400x1400.
144
+ #
145
+ # Examples:
146
+ #
147
+ # driven_by :cuprite
148
+ #
149
+ # driven_by :selenium, screen_size: [800, 800]
150
+ #
151
+ # driven_by :selenium, using: :chrome
152
+ #
153
+ # driven_by :selenium, using: :headless_chrome
154
+ #
155
+ # driven_by :selenium, using: :firefox
156
+ #
157
+ # driven_by :selenium, using: :headless_firefox
158
+ def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}, &capabilities)
159
+ driver_options = { using: using, screen_size: screen_size, options: options }
160
+
161
+ self.driver = SystemTesting::Driver.new(driver, **driver_options, &capabilities)
162
+ end
163
+
164
+ # Configuration for the System Test application server.
165
+ #
166
+ # By default this is localhost. This method allows the host and port to be specified manually.
167
+ def self.served_by(host:, port:)
168
+ Capybara.server_host = host
169
+ Capybara.server_port = port
170
+ end
171
+
172
+ private
173
+ def url_helpers
174
+ @url_helpers ||=
175
+ if ActionDispatch.test_app
176
+ Class.new do
177
+ include ActionDispatch.test_app.routes.url_helpers
178
+ include ActionDispatch.test_app.routes.mounted_helpers
179
+
180
+ def url_options
181
+ default_url_options.reverse_merge(host: app_host)
182
+ end
183
+
184
+ def app_host
185
+ Capybara.app_host || Capybara.current_session.server_url || DEFAULT_HOST
186
+ end
187
+ end.new
188
+ end
189
+ end
190
+
191
+ def method_missing(name, ...)
192
+ if url_helpers.respond_to?(name)
193
+ url_helpers.public_send(name, ...)
194
+ else
195
+ super
196
+ end
197
+ end
198
+
199
+ def respond_to_missing?(name, include_private = false)
200
+ url_helpers.respond_to?(name)
201
+ end
202
+ end
203
+ end
204
+
205
+ ActiveSupport.run_load_hooks :action_dispatch_system_test_case, ActionDispatch::SystemTestCase
206
+ ActionDispatch::SystemTestCase.start_application
@@ -0,0 +1,75 @@
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
+ end
13
+
14
+ def type
15
+ case name
16
+ when :headless_chrome
17
+ :chrome
18
+ when :headless_firefox
19
+ :firefox
20
+ else
21
+ name
22
+ end
23
+ end
24
+
25
+ def options
26
+ @options ||=
27
+ case type
28
+ when :chrome
29
+ default_chrome_options
30
+ when :firefox
31
+ default_firefox_options
32
+ end
33
+ end
34
+
35
+ def configure
36
+ yield options if block_given?
37
+ end
38
+
39
+ # driver_path is lazily initialized by default. Eagerly set it to avoid race
40
+ # conditions when using parallel tests.
41
+ def preload
42
+ case type
43
+ when :chrome
44
+ resolve_driver_path(::Selenium::WebDriver::Chrome)
45
+ when :firefox
46
+ resolve_driver_path(::Selenium::WebDriver::Firefox)
47
+ end
48
+ end
49
+
50
+ private
51
+ def default_chrome_options
52
+ options = ::Selenium::WebDriver::Chrome::Options.new
53
+ options.add_argument("--disable-search-engine-choice-screen")
54
+ options.add_argument("--headless") if name == :headless_chrome
55
+ options.add_argument("--disable-gpu") if Gem.win_platform?
56
+ options
57
+ end
58
+
59
+ def default_firefox_options
60
+ options = ::Selenium::WebDriver::Firefox::Options.new
61
+ options.add_argument("-headless") if name == :headless_firefox
62
+ options
63
+ end
64
+
65
+ def resolve_driver_path(namespace)
66
+ # The path method has been deprecated in 4.20.0
67
+ if Gem::Version.new(::Selenium::WebDriver::VERSION) >= Gem::Version.new("4.20.0")
68
+ namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.new(options, namespace::Service.new).driver_path
69
+ else
70
+ namespace::Service.driver_path = ::Selenium::WebDriver::DriverFinder.path(options, namespace::Service)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ 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