snapshot_inspector 0.1.1
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +227 -0
- data/Rakefile +8 -0
- data/app/assets/config/snapshot_inspector/manifest.js +2 -0
- data/app/assets/javascripts/snapshot_inspector/application.js +1 -0
- data/app/assets/stylesheets/snapshot_inspector/application.css +33 -0
- data/app/assets/stylesheets/snapshot_inspector/snapshots/mail.css +48 -0
- data/app/assets/stylesheets/snapshot_inspector/snapshots/not_found.css +15 -0
- data/app/assets/stylesheets/snapshot_inspector/snapshots/response.css +9 -0
- data/app/assets/stylesheets/snapshot_inspector/snapshots.css +73 -0
- data/app/controllers/snapshot_inspector/application_controller.rb +14 -0
- data/app/controllers/snapshot_inspector/snapshots/mail_controller.rb +57 -0
- data/app/controllers/snapshot_inspector/snapshots/response_controller.rb +15 -0
- data/app/controllers/snapshot_inspector/snapshots_controller.rb +7 -0
- data/app/helpers/snapshot_inspector/application_helper.rb +15 -0
- data/app/helpers/snapshot_inspector/snapshots_helper.rb +37 -0
- data/app/mailers/snapshot_inspector/application_mailer.rb +6 -0
- data/app/models/snapshot_inspector/snapshot/context.rb +49 -0
- data/app/models/snapshot_inspector/snapshot/mail_type.rb +35 -0
- data/app/models/snapshot_inspector/snapshot/response_type.rb +19 -0
- data/app/models/snapshot_inspector/snapshot/rspec_context.rb +52 -0
- data/app/models/snapshot_inspector/snapshot/test_unit_context.rb +44 -0
- data/app/models/snapshot_inspector/snapshot/type.rb +52 -0
- data/app/models/snapshot_inspector/snapshot.rb +86 -0
- data/app/views/layouts/snapshot_inspector/application.html.erb +18 -0
- data/app/views/snapshot_inspector/snapshots/index.html.erb +29 -0
- data/app/views/snapshot_inspector/snapshots/mail/show.html.erb +107 -0
- data/app/views/snapshot_inspector/snapshots/not_found.html.erb +8 -0
- data/app/views/snapshot_inspector/snapshots/response/raw.html.erb +1 -0
- data/app/views/snapshot_inspector/snapshots/response/show.html.erb +1 -0
- data/config/importmap.rb +12 -0
- data/config/routes.rb +9 -0
- data/lib/minitest/snapshot_inspector_plugin.rb +28 -0
- data/lib/snapshot_inspector/engine.rb +67 -0
- data/lib/snapshot_inspector/storage.rb +60 -0
- data/lib/snapshot_inspector/test/action_mailer_headers.rb +18 -0
- data/lib/snapshot_inspector/test/rspec_helpers.rb +45 -0
- data/lib/snapshot_inspector/test/test_unit_helpers.rb +48 -0
- data/lib/snapshot_inspector/version.rb +3 -0
- data/lib/snapshot_inspector.rb +42 -0
- data/lib/tasks/tmp.rake +10 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 571c9cf5b244dabba8c02e82927bef51de92ee803425cdaba8d959f6c326680d
|
4
|
+
data.tar.gz: 568238234e8cfb20cc5cfd2662fb81c303b35a74f6baeb44dfbea4bfe5af6144
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0feb058a79241aff473b9111e6c1ee85c6e1e1806c08614f592f1714ca6eba05040db9b5a93fff255d8217b5a3e399803ca082047abe1b1aaeacb65506a805bc
|
7
|
+
data.tar.gz: 497dff7826caf2b15c86fc904401e27d77c65a85af85bb0a55720ab0ea4929fc3cfbef11d4a30f2e064afb647888939cdc56c03c4424dcef0a00bd906715944f
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2023 Tomaz Zlender
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
# Snapshot Inspector
|
2
|
+
|
3
|
+
Take snapshots of responses and mail messages while testing, and inspect them in a browser. A Ruby on Rails engine. Works with the default Ruby on Rails testing framework and RSpec.
|
4
|
+
|
5
|
+
> **NOTICE**
|
6
|
+
>
|
7
|
+
> The library is actively used during the development of apps running in production, however, treat it as beta software.
|
8
|
+
>
|
9
|
+
> So far it's been tested on the latest version of Ruby on Rails (7.0.x) and Ruby (> 3.1.x).
|
10
|
+
|
11
|
+
A sneak peek into what the library is about:
|
12
|
+
|
13
|
+

|
14
|
+
|
15
|
+
## Rationale
|
16
|
+
|
17
|
+
Imagine that I'm working on a website for a local urban gardening community. It's a server side rendered
|
18
|
+
app, using sprinkles of JavaScript on top.
|
19
|
+
|
20
|
+
While writing view related tests, like integration or mailer tests, I wish to see how a
|
21
|
+
response body (or mail message body) looks like in a browser for a given test setup.
|
22
|
+
|
23
|
+
It's quite easy to do that in some cases. Let's say I'm writing an integration test for a form for signing
|
24
|
+
up gardeners to the garden's waiting list.
|
25
|
+
I can simply start the development server, go to a URL of the page that displays the form
|
26
|
+
and I can see and inspect the rendered HTML that exactly reflects the response body I'm dealing
|
27
|
+
with in the integration test.
|
28
|
+
|
29
|
+
Now imagine I'm writing an integration test for a success page that is displayed after a new gardener fills
|
30
|
+
in the form and submits it. Or imagine, that I'm writing a test for a membership details page of
|
31
|
+
an expired membership that is visible only to a specific registered gardener.
|
32
|
+
|
33
|
+
In such cases, I need to put much more effort into recreating the state in a browser, while I already put effort
|
34
|
+
into creating the test setup in the first place.
|
35
|
+
|
36
|
+
In JavaScript heavy applications it's natural to use system tests (or similar) for such purposes. It's possible
|
37
|
+
to [take screenshots](https://api.rubyonrails.org/classes/ActionDispatch/SystemTesting/TestHelpers/ScreenshotHelper.html#method-i-take_screenshot),
|
38
|
+
and even save the HTML of the page being screenshotted for later inspection.
|
39
|
+
|
40
|
+
However, in the world of server rendered applications with sprinkles of JavaScript on top, the integration tests
|
41
|
+
(where a system under test deals with controller/model/view) can cover a lot of the testing needs that
|
42
|
+
in JavaScript heavy context can only be fulfilled with system tests (javascript/controller/model/view
|
43
|
+
running via a browser).
|
44
|
+
|
45
|
+
It would be convenient to use integration tests for such cases since they are much faster to run. However,
|
46
|
+
writing assertions against HTML can become quickly cumbersome because of the reasons explained above.
|
47
|
+
|
48
|
+
Alternatively, I could use a debugger to inspect the state of a response body through a shell,
|
49
|
+
but that makes me feel like peeking through a peephole compared to inspecting HTML with a Web Inspector.
|
50
|
+
|
51
|
+
Enter **Snapshot Inspector**.
|
52
|
+
|
53
|
+
All that is keeping me away from using Web Inspector for such purposes is one drop of a line
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
take_snapshot response
|
57
|
+
```
|
58
|
+
|
59
|
+
after a request is made in a test and a response object has been populated.
|
60
|
+
|
61
|
+
Take a look at an example of a rendered response snapshot of a thank you page in a browser:
|
62
|
+
|
63
|
+

|
64
|
+
|
65
|
+
### Exploring a new codebase
|
66
|
+
|
67
|
+
Imagine you are new to a codebase (or it's been months since you've looked at it) and you
|
68
|
+
are investigating a reported UI bug. You know to which route
|
69
|
+
the bug is related to, but since you haven't worked in that area of the codebase, it's not clear how
|
70
|
+
to recreate the state of the application in a browser to replicate the bug. Maybe you don't even know
|
71
|
+
what the UI related to the route looks like since you haven't managed to click around
|
72
|
+
through all the permutations of the state of the whole application.
|
73
|
+
|
74
|
+
With Snapshot Inspector, you can simply drop a bunch of `take_snapshot response` in all the tests
|
75
|
+
of the related controller, and you'll get a pretty good picture of how the UI looks like.
|
76
|
+
That might be all you need to do to replicate the bug. Or you can create a new test that
|
77
|
+
will replicate the state of the UI the bug reporter was in, and generate snapshots as you explore.
|
78
|
+
|
79
|
+
### A case for taking snapshots of mail messages
|
80
|
+
|
81
|
+
There is an established practice of using [Action Mailer Previews](https://guides.rubyonrails.org/action_mailer_basics.html#previewing-emails).
|
82
|
+
|
83
|
+
Imagine that I'm working on a mailer that I wish to send to a gardener that has signed up for the
|
84
|
+
waiting list.
|
85
|
+
|
86
|
+
Without Snapshot Inspector I would write the mailer test and the mailer preview. I would need to
|
87
|
+
do the same setup of the mailer state twice to keep the parity between the two.
|
88
|
+
|
89
|
+
With Snapshot Inspector, I can simply drop one line of code into the mailer test and avoid writing
|
90
|
+
the mailer preview altogether.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
take_snapshot mail
|
94
|
+
```
|
95
|
+
|
96
|
+
An example of a rendered mail message snapshot of a thank you email in a browser:
|
97
|
+
|
98
|
+

|
99
|
+
|
100
|
+
The duplicated effort between mailer previews and tests is gone. I can look and inspect all
|
101
|
+
interesting variations of a mailer that I'm already testing anyhow, without putting any effort into
|
102
|
+
creating previews.
|
103
|
+
|
104
|
+
### Summary
|
105
|
+
|
106
|
+
Taking snapshots is to integration and mailer tests what taking screenshots is to system tests.
|
107
|
+
|
108
|
+
### The added bonus: UI library
|
109
|
+
|
110
|
+
While not the primary goal of the Snapshot Inspector, a side effect of taking snapshots is a library
|
111
|
+
of the UI surface area of the application.
|
112
|
+
|
113
|
+
One of the benefits of automated tests with well written test descriptions is to document the
|
114
|
+
behavior of a system under test. Since the output of a system under test in the case of integration
|
115
|
+
and mailer tests is a renderable UI, if we take snapshots of all tested responses and emails, we
|
116
|
+
consequently generate a browsable UI library of the whole application.
|
117
|
+
|
118
|
+
Of course, that only applies to applications that are heavy on the server side rendering and use
|
119
|
+
a tiny bit of JavaScript on top. In the world of [Hotwire](https://hotwired.dev), I imagine there can
|
120
|
+
be quite a few applications like that.
|
121
|
+
|
122
|
+
## Installation
|
123
|
+
|
124
|
+
Add the gem to your application's Gemfile under `:development` and `:test` groups. Snapshots are taken in the test environment and inspected in the development environment.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
group [:development, :test] do
|
128
|
+
gem "snapshot_inspector"
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
Then execute:
|
133
|
+
```bash
|
134
|
+
bundle install
|
135
|
+
```
|
136
|
+
|
137
|
+
## Usage
|
138
|
+
|
139
|
+
Take snapshots by placing a helper method `take_snapshot` in tests that deal with instances of `ActionDispatch::TestResponse` or `ActionMailer::MessageDelivery`.
|
140
|
+
For example, in controller, integration, or mailer tests.
|
141
|
+
|
142
|
+
For the best experience, take snapshots before assertions. That way it is possible to inspect them as part of the investigation of why an assertion failed.
|
143
|
+
|
144
|
+
An example from an integration test:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
test "should get index" do
|
148
|
+
get root_path
|
149
|
+
|
150
|
+
take_snapshot response # <-- takes a snapshot of the response (an instance of ActionDispatch::TestResponse)
|
151
|
+
|
152
|
+
assert_response :success
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
An example of a mailer test:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
test "welcome mail" do
|
160
|
+
mail = NotifierMailer.welcome
|
161
|
+
|
162
|
+
take_snapshot mail # <-- takes a snapshot of the mail (an instance of ActionMailer::MessageDelivery)
|
163
|
+
|
164
|
+
assert_equal "Welcome!", mail.subject
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
When the tests are run, to avoid the performance overhead, the snapshot taking is skipped by default.
|
169
|
+
To enable them, run the tests with a flag `--take-snapshots`. The flag works with the default testing framework only.
|
170
|
+
|
171
|
+
```bash
|
172
|
+
bin/rails test --take-snapshots
|
173
|
+
TAKE_SNAPSHOTS=1 bin/rails test
|
174
|
+
```
|
175
|
+
|
176
|
+
If you are using RSpec, use the environment variable `TAKE_SNAPSHOTS`. The variable also works with the default testing framework.
|
177
|
+
|
178
|
+
```bash
|
179
|
+
TAKE_SNAPSHOTS=1 bin/rspec
|
180
|
+
```
|
181
|
+
|
182
|
+
Then start your local server and visit http://localhost:300/rails/snapshots.
|
183
|
+
|
184
|
+
### Live Reloading
|
185
|
+
|
186
|
+
If you wish for the snapshots in a browser to live reload whenever a test run is completed,
|
187
|
+
Snapshot Inspector works out of the box with [hotwire-livereload](https://github.com/kirillplatonov/hotwire-livereload).
|
188
|
+
Besides the general installation instructions, add the following lines into `development.rb`.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
config.hotwire_livereload.listen_paths << SnapshotInspector::Storage.snapshots_directory
|
192
|
+
config.hotwire_livereload.force_reload_paths << SnapshotInspector::Storage.snapshots_directory
|
193
|
+
```
|
194
|
+
|
195
|
+
## How it works
|
196
|
+
|
197
|
+
- By default, snapshot taking is skipped to preserve the performance of a test suit in CI and similar environments.
|
198
|
+
- Snapshots are stored in `tmp/snapshot_inspector` folder.
|
199
|
+
- For every test run, existing snapshots are cleared and new snapshots are generated.
|
200
|
+
- The testing framework (Rails' default or RSpec) is autodetected.
|
201
|
+
- By default, when a snapshot is rendered all JavaScript related tags (`<script>` and `<link>`) are removed
|
202
|
+
from the HTML. That way we can pretty closely simulate how a page looks like with JavaScript turned off
|
203
|
+
without going into the trouble of turning it off in a browser, and still taking advantage of live reloading.
|
204
|
+
|
205
|
+
## Unresolved Challenges
|
206
|
+
|
207
|
+
Imagine that you are building a page with form. You wish to test drive the development while at the
|
208
|
+
same time use the Snapshot Inspector to see how the page looks after every change.
|
209
|
+
|
210
|
+
It takes more time to make a change in the view, run the test (and generate a snapshot),
|
211
|
+
and reload the browser than to run a development server and use live reloading directly.
|
212
|
+
Even with live reloading turned on and using things like [guard](https://github.com/guard/guard).
|
213
|
+
|
214
|
+
It would be nice to find a way to speed up the tests. Perhaps there is a way to run them
|
215
|
+
interactively from a console and to run something similar to `reload!` after every change? Please reach out
|
216
|
+
via GitHub Issues if you have suggestions.
|
217
|
+
|
218
|
+
## Contributing
|
219
|
+
|
220
|
+
- Fork the repo
|
221
|
+
- Create your feature branch (git checkout -b my-new-feature)
|
222
|
+
- Commit your changes (git commit -am 'Add some feature')
|
223
|
+
- Push to the branch (git push origin my-new-feature)
|
224
|
+
- Create a new Pull Request
|
225
|
+
|
226
|
+
## License
|
227
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
import "controllers"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
11
|
+
* It is generally better to create a new file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
16
|
+
|
17
|
+
html {
|
18
|
+
height: 100%;
|
19
|
+
}
|
20
|
+
|
21
|
+
body {
|
22
|
+
background-color: #fff;
|
23
|
+
color: #333;
|
24
|
+
margin: 0;
|
25
|
+
padding: 0;
|
26
|
+
height: 100%;
|
27
|
+
}
|
28
|
+
|
29
|
+
body, p, ol, ul, td {
|
30
|
+
font-family: helvetica, verdana, arial, sans-serif;
|
31
|
+
font-size: 20px;
|
32
|
+
line-height: 1.7em;
|
33
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
body#mail_show {
|
2
|
+
display: flex;
|
3
|
+
flex-flow: column;
|
4
|
+
height: 100%;
|
5
|
+
}
|
6
|
+
|
7
|
+
body#mail_show header {
|
8
|
+
width: 100%;
|
9
|
+
padding: 10px 0 0 0;
|
10
|
+
margin: 0;
|
11
|
+
background: white;
|
12
|
+
font: 12px "Lucida Grande", sans-serif;
|
13
|
+
border-bottom: 1px solid #dedede;
|
14
|
+
overflow: hidden;
|
15
|
+
}
|
16
|
+
|
17
|
+
body#mail_show dl {
|
18
|
+
margin: 0 0 10px 0;
|
19
|
+
padding: 0;
|
20
|
+
}
|
21
|
+
|
22
|
+
body#mail_show dt {
|
23
|
+
width: 80px;
|
24
|
+
padding: 1px;
|
25
|
+
float: left;
|
26
|
+
clear: left;
|
27
|
+
text-align: right;
|
28
|
+
color: #7f7f7f;
|
29
|
+
}
|
30
|
+
|
31
|
+
body#mail_show dd {
|
32
|
+
margin-left: 90px; /* 80px + 10px */
|
33
|
+
padding: 1px;
|
34
|
+
}
|
35
|
+
|
36
|
+
body#mail_show dd:empty:before {
|
37
|
+
content: "\00a0"; //
|
38
|
+
}
|
39
|
+
|
40
|
+
body#mail_show td {
|
41
|
+
font: 12px "Lucida Grande", sans-serif;
|
42
|
+
}
|
43
|
+
|
44
|
+
body#mail_show iframe {
|
45
|
+
border: 0;
|
46
|
+
width: 100%;
|
47
|
+
flex: 1 1 auto;
|
48
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
body#snapshots_index main {
|
2
|
+
padding: 100px;
|
3
|
+
}
|
4
|
+
|
5
|
+
body#snapshots_index h1 {
|
6
|
+
margin: 0;
|
7
|
+
font-size: 30px;
|
8
|
+
}
|
9
|
+
|
10
|
+
body#snapshots_index h2 {
|
11
|
+
margin: 30px 0 0;
|
12
|
+
font-size: 24px;
|
13
|
+
}
|
14
|
+
|
15
|
+
body#snapshots_index p {
|
16
|
+
margin: 18px 0 0;
|
17
|
+
color: #666666;
|
18
|
+
}
|
19
|
+
|
20
|
+
body#snapshots_index ul {
|
21
|
+
margin: 3px 0 0;
|
22
|
+
}
|
23
|
+
|
24
|
+
body#snapshots_index h1, h2, h3, h4, h5, h6 {
|
25
|
+
font-weight: normal;
|
26
|
+
color: #666666;
|
27
|
+
}
|
28
|
+
|
29
|
+
body#snapshots_index pre {
|
30
|
+
background-color: #eee;
|
31
|
+
padding: 10px;
|
32
|
+
font-size: 11px;
|
33
|
+
white-space: pre-wrap;
|
34
|
+
}
|
35
|
+
|
36
|
+
body#snapshots_index a { color: #000; text-decoration: none; }
|
37
|
+
body#snapshots_index a:hover { text-decoration: underline; }
|
38
|
+
|
39
|
+
body#snapshots_index form.enable_javascript {
|
40
|
+
margin-top: 20px;
|
41
|
+
}
|
42
|
+
|
43
|
+
body#snapshots_index form.enable_javascript input {
|
44
|
+
transform: scale(1.4);
|
45
|
+
cursor: pointer;
|
46
|
+
}
|
47
|
+
|
48
|
+
body#snapshots_index form.enable_javascript label {
|
49
|
+
cursor: pointer;
|
50
|
+
user-select: none;
|
51
|
+
font-size: 19px;
|
52
|
+
display: flex;
|
53
|
+
align-items: center;
|
54
|
+
}
|
55
|
+
|
56
|
+
body#snapshots_index form.enable_javascript label span {
|
57
|
+
margin: 0 0 0 10px;
|
58
|
+
color: #666666;
|
59
|
+
}
|
60
|
+
|
61
|
+
@media (prefers-color-scheme: dark) {
|
62
|
+
body#snapshots_index {
|
63
|
+
background-color: #222;
|
64
|
+
color: #ececec;
|
65
|
+
}
|
66
|
+
|
67
|
+
body#snapshots_index pre {
|
68
|
+
background-color: #333;
|
69
|
+
}
|
70
|
+
|
71
|
+
body#snapshots_index a { color: #fff; }
|
72
|
+
body#snapshots_index a:hover { color: #fff; text-decoration: underline; }
|
73
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SnapshotInspector
|
2
|
+
class ApplicationController < ActionController::Base
|
3
|
+
helper SnapshotInspector::Engine.helpers
|
4
|
+
|
5
|
+
content_security_policy(false)
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def snapshot_not_found(error)
|
10
|
+
@error = error
|
11
|
+
render "snapshot_inspector/snapshots/not_found", status: 404
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module SnapshotInspector
|
2
|
+
class Snapshots::MailController < ApplicationController
|
3
|
+
helper_method :part_query
|
4
|
+
|
5
|
+
rescue_from Snapshot::NotFound, with: :snapshot_not_found
|
6
|
+
|
7
|
+
def show
|
8
|
+
@snapshot = Snapshot.find(params[:slug])
|
9
|
+
@email = @snapshot.message
|
10
|
+
|
11
|
+
if params[:format] == "eml"
|
12
|
+
send_data @email.to_s, filename: "#{@snapshot.mailer_name}##{@snapshot.action_name}.eml"
|
13
|
+
else
|
14
|
+
@part = find_preferred_part(request.format, Mime[:html], Mime[:text])
|
15
|
+
render :show, formats: [:html]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def raw
|
20
|
+
@snapshot = Snapshot.find(params[:slug])
|
21
|
+
@email = @snapshot.message
|
22
|
+
part_type = Mime::Type.lookup(params[:part] || "text/html")
|
23
|
+
|
24
|
+
if (part = find_part(part_type))
|
25
|
+
response.content_type = part_type
|
26
|
+
render plain: part.respond_to?(:decoded) ? part.decoded : part
|
27
|
+
else
|
28
|
+
raise AbstractController::ActionNotFound, "Email part `#{part_type}` not found in a snapshot #{@snapshot.context.test_case_name}##{@snapshot.context.method_name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def find_preferred_part(*formats)
|
35
|
+
formats.each do |format|
|
36
|
+
if (part = @email.find_first_mime_type(format))
|
37
|
+
return part
|
38
|
+
end
|
39
|
+
end
|
40
|
+
if formats.any? { |f| @email.mime_type == f }
|
41
|
+
@email
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_part(format)
|
46
|
+
if (part = @email.find_first_mime_type(format))
|
47
|
+
part
|
48
|
+
elsif @email.mime_type == format
|
49
|
+
@email
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def part_query(mime_type)
|
54
|
+
request.query_parameters.merge(part: mime_type).to_query
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SnapshotInspector
|
2
|
+
class Snapshots::ResponseController < ApplicationController
|
3
|
+
rescue_from Snapshot::NotFound, with: :snapshot_not_found
|
4
|
+
|
5
|
+
layout false, only: [:raw]
|
6
|
+
|
7
|
+
def show
|
8
|
+
@snapshot = Snapshot.find(params[:slug])
|
9
|
+
end
|
10
|
+
|
11
|
+
def raw
|
12
|
+
@snapshot = Snapshot.find(params[:slug])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SnapshotInspector
|
2
|
+
module ApplicationHelper
|
3
|
+
def snapshot_inspector_importmap_tags(entry_point = "application", shim: true)
|
4
|
+
safe_join(
|
5
|
+
[
|
6
|
+
javascript_inline_importmap_tag(SnapshotInspector.configuration.importmap.to_json(resolver: self)),
|
7
|
+
javascript_importmap_module_preload_tags(SnapshotInspector.configuration.importmap),
|
8
|
+
(javascript_importmap_shim_nonce_configuration_tag if shim),
|
9
|
+
(javascript_importmap_shim_tag if shim),
|
10
|
+
javascript_import_module_tag(entry_point)
|
11
|
+
].compact, "\n"
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module SnapshotInspector
|
2
|
+
module SnapshotsHelper
|
3
|
+
def prepare_for_render(body, enable_javascript:)
|
4
|
+
prepared =
|
5
|
+
if enable_javascript == "true"
|
6
|
+
body
|
7
|
+
else
|
8
|
+
remove_traces_of_javascript(body)
|
9
|
+
end
|
10
|
+
|
11
|
+
prepared.html_safe
|
12
|
+
end
|
13
|
+
|
14
|
+
def remove_traces_of_javascript(html)
|
15
|
+
doc = Nokogiri.HTML(html)
|
16
|
+
|
17
|
+
doc.css("script").each do |element|
|
18
|
+
element.replace("")
|
19
|
+
end
|
20
|
+
|
21
|
+
doc.css('link[href$=".js"]').each do |element|
|
22
|
+
element.replace("")
|
23
|
+
end
|
24
|
+
|
25
|
+
doc.to_html
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.snapshot_path(snapshot, enable_javascript:)
|
29
|
+
case snapshot.type
|
30
|
+
when "mail"
|
31
|
+
SnapshotInspector::Engine.routes.url_helpers.mail_snapshot_path(slug: snapshot.slug)
|
32
|
+
when "response"
|
33
|
+
SnapshotInspector::Engine.routes.url_helpers.response_snapshot_path(slug: snapshot.slug, enable_javascript: enable_javascript)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SnapshotInspector
|
2
|
+
class Snapshot
|
3
|
+
class Context
|
4
|
+
class_attribute :registry, default: {}, instance_writer: false, instance_predicate: false
|
5
|
+
|
6
|
+
def self.test_framework(name)
|
7
|
+
registry[name] = self
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.extract(context)
|
11
|
+
record = registry[context[:test_framework]].new
|
12
|
+
record.extract(context)
|
13
|
+
record
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_hash(hash)
|
17
|
+
record = registry[hash[:test_framework].to_sym].new
|
18
|
+
record.from_hash(hash)
|
19
|
+
record
|
20
|
+
end
|
21
|
+
|
22
|
+
# @private
|
23
|
+
def extract(_context)
|
24
|
+
raise "Implement in a child class."
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def from_hash(_hash)
|
29
|
+
raise "Implement in a child class."
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_slug
|
33
|
+
raise "Implement in a child class."
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
raise "Implement in a child class."
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_group
|
41
|
+
raise "Implement in a child class."
|
42
|
+
end
|
43
|
+
|
44
|
+
def order_index
|
45
|
+
raise "Implement in a child class."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|