snapshot_inspector 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![A list of snapshots](doc/snapshots_index.png)
|
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
|
+
![Response snapshot example: thank you page](doc/response_snapshot_example_thank_you_page.png)
|
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
|
+
![A mail message snapshot](doc/mail_snapshot_example_1.png)
|
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
|