capybara-reloads 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +71 -0
- data/LICENSE.txt +21 -0
- data/README.md +240 -0
- data/Rakefile +8 -0
- data/capybara-reloads.gemspec +38 -0
- data/lib/capybara/reloads/node/matchers.rb +70 -0
- data/lib/capybara/reloads/version.rb +7 -0
- data/lib/capybara/reloads.rb +104 -0
- data/sig/capybara/reloads.rbs +6 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 980ec37f08c81b19646193192a014368ada60f31cc52fc64b4bc03584ed40a77
|
4
|
+
data.tar.gz: abca8c70caad1c2e131b22633e926aa1abd5dcc94c13b2cf587ad9fdfbeae7dd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 96d1a31f3d3a64233fd22e0d7f7bf1a2c6512823225e7fc282443f6f41cc0235c3d6ffaea1ea54dc773b4b876c0c9cc70fd6bc709822415cfe631732b7530eab
|
7
|
+
data.tar.gz: 26b22dac221bf078884da1e5f6649b2d8ebc606875374609f43d99061747a4a9e50b28e1e4b3df207b98e9b4b532db23ad9cffdc23a37e4ac8f4f1baf6009d81
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
capybara-reloads (0.1.0)
|
5
|
+
capybara (~> 3.0)
|
6
|
+
capybara-screenshot (~> 1.0)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
addressable (2.8.1)
|
12
|
+
public_suffix (>= 2.0.2, < 6.0)
|
13
|
+
ast (2.4.2)
|
14
|
+
capybara (3.38.0)
|
15
|
+
addressable
|
16
|
+
matrix
|
17
|
+
mini_mime (>= 0.1.3)
|
18
|
+
nokogiri (~> 1.8)
|
19
|
+
rack (>= 1.6.0)
|
20
|
+
rack-test (>= 0.6.3)
|
21
|
+
regexp_parser (>= 1.5, < 3.0)
|
22
|
+
xpath (~> 3.2)
|
23
|
+
capybara-screenshot (1.0.26)
|
24
|
+
capybara (>= 1.0, < 4)
|
25
|
+
launchy
|
26
|
+
json (2.6.3)
|
27
|
+
launchy (2.5.0)
|
28
|
+
addressable (~> 2.7)
|
29
|
+
matrix (0.4.2)
|
30
|
+
mini_mime (1.1.2)
|
31
|
+
nokogiri (1.13.10-arm64-darwin)
|
32
|
+
racc (~> 1.4)
|
33
|
+
parallel (1.22.1)
|
34
|
+
parser (3.1.3.0)
|
35
|
+
ast (~> 2.4.1)
|
36
|
+
public_suffix (5.0.1)
|
37
|
+
racc (1.6.2)
|
38
|
+
rack (3.0.2)
|
39
|
+
rack-test (2.0.2)
|
40
|
+
rack (>= 1.3)
|
41
|
+
rainbow (3.1.1)
|
42
|
+
rake (13.0.6)
|
43
|
+
regexp_parser (2.6.1)
|
44
|
+
rexml (3.2.5)
|
45
|
+
rubocop (1.41.1)
|
46
|
+
json (~> 2.3)
|
47
|
+
parallel (~> 1.10)
|
48
|
+
parser (>= 3.1.2.1)
|
49
|
+
rainbow (>= 2.2.2, < 4.0)
|
50
|
+
regexp_parser (>= 1.8, < 3.0)
|
51
|
+
rexml (>= 3.2.5, < 4.0)
|
52
|
+
rubocop-ast (>= 1.23.0, < 2.0)
|
53
|
+
ruby-progressbar (~> 1.7)
|
54
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
55
|
+
rubocop-ast (1.24.0)
|
56
|
+
parser (>= 3.1.1.0)
|
57
|
+
ruby-progressbar (1.11.0)
|
58
|
+
unicode-display_width (2.3.0)
|
59
|
+
xpath (3.2.0)
|
60
|
+
nokogiri (~> 1.8)
|
61
|
+
|
62
|
+
PLATFORMS
|
63
|
+
arm64-darwin-21
|
64
|
+
|
65
|
+
DEPENDENCIES
|
66
|
+
capybara-reloads!
|
67
|
+
rake (~> 13.0)
|
68
|
+
rubocop (~> 1.21)
|
69
|
+
|
70
|
+
BUNDLED WITH
|
71
|
+
2.3.23
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
# Capybara::Reloads
|
2
|
+
|
3
|
+
Reload the page when a Capybara selector fails. After reload assert the selector again. Repeat until the number of reloads is equal to allow_max_reloads. Store screenshot and html version of the page to allow for easier debug and understanding of why the spec has failed.
|
4
|
+
|
5
|
+
When used for a randomly failing spec it will store and show the states before every load.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
expect(page).to have_xpath("//..the xpath value",
|
9
|
+
allow_max_reloads: Capybara::Reloads.recommended_allow_max_reloads)
|
10
|
+
```
|
11
|
+
|
12
|
+
If the selector is first not matched and matched after reload capybara-reloads will report it as:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
Failures:
|
16
|
+
|
17
|
+
1) Articles system specs
|
18
|
+
Failure/Error: raise message
|
19
|
+
|
20
|
+
RuntimeError:
|
21
|
+
The example initially failed, but after 1 reloads it was successful.
|
22
|
+
States are shown below in order:
|
23
|
+
|
24
|
+
State 0
|
25
|
+
{
|
26
|
+
"html": ".../tmp/capybara/screenshot_2022-12-25-21-57-53.129.html",
|
27
|
+
"image": ".../tmp/capybara/screenshot_2022-12-25-21-57-53.129.png",
|
28
|
+
"exception": "expected to find xpath \"//a[@class='select2-choice']/span[text()='United States']\" but there were no matches",
|
29
|
+
"reloads_made": 0
|
30
|
+
}
|
31
|
+
State 1
|
32
|
+
{
|
33
|
+
"html": ".../tmp/capybara/screenshot_2022-12-25-21-57-53.906.html",
|
34
|
+
"image": ".../tmp/capybara/screenshot_2022-12-25-21-57-53.906.png",
|
35
|
+
"exception": null,
|
36
|
+
"reloads_made": 1
|
37
|
+
}
|
38
|
+
|
39
|
+
[Screenshot Image]: .../tmp/capybara/failures_r_spec_example_groups_article_807.png
|
40
|
+
|
41
|
+
|
42
|
+
# .../capybara-reloads/lib/capybara/reloads/node/matchers.rb:54:in `after_success'
|
43
|
+
# .../capybara-reloads/lib/capybara/reloads/node/matchers.rb:28:in `block in with_reload_on_fail'
|
44
|
+
# .../capybara-reloads/lib/capybara/reloads/node/matchers.rb:23:in `loop'
|
45
|
+
# .../capybara-reloads/lib/capybara/reloads/node/matchers.rb:23:in `with_reload_on_fail'
|
46
|
+
# .../capybara-reloads/lib/capybara/reloads/node/matchers.rb:7:in `assert_selector'
|
47
|
+
# .../gems/capybara-3.38.0/lib/capybara/session.rb:773:in `assert_selector'
|
48
|
+
# .../gems/capybara-3.38.0/lib/capybara/rspec/matchers/have_selector.rb:18:in `element_matches?'
|
49
|
+
# .../gems/capybara-3.38.0/lib/capybara/rspec/matchers/base.rb:51:in `matches?'
|
50
|
+
# ./spec/system/articles_spec.rb:101:in `block (3 levels) in <top (required)>'
|
51
|
+
# ./spec/system/articles_spec.rb:157:in `block (3 levels) in <top (required)>'
|
52
|
+
# ./spec/system/articles_spec.rb:135:in `upto'
|
53
|
+
# ./spec/system/articles_spec.rb:135:in `block (2 levels) in <top (required)>'
|
54
|
+
# ./spec/spec_helper.rb:664:in `block (2 levels) in <top (required)>'
|
55
|
+
# .../gems/webmock-3.14.0/lib/webmock/rspec.rb:37:in `block (2 levels) in <top (required)>'
|
56
|
+
|
57
|
+
Finished in 13.71 seconds (files took 4.49 seconds to load)
|
58
|
+
```
|
59
|
+
|
60
|
+
## What's the goal and the job to be done
|
61
|
+
|
62
|
+
Sometimes a spec for an application that uses JavaScript fails "randomly". The goal of capybara-reloads is to help identify and resolve specs that are randomly failing due to JavaScript timing, load and reload precularities in non-trivial platform. It helps by saving a screenshot and html version of the page, reloading the page, and trying the selector again. If the second or the third time the selector is matched then the gem reports this.
|
63
|
+
|
64
|
+
An alternative to capybara-reloads is:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
0.upto(2) do
|
68
|
+
begin
|
69
|
+
expect(page).to have_xpath "//..the xpath"
|
70
|
+
rescue Exception=>e
|
71
|
+
save screenshots and html of the page
|
72
|
+
page.refresh
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
but the above has to be done for every call and makes the spec more difficult to read and maintain. capybara-reloads patches Capybara in a way that makes enabling the reload for every or for specific selector very easy.
|
77
|
+
|
78
|
+
## Reasoning
|
79
|
+
|
80
|
+
A non-trivial suite of system specs running over a non-trivial platform often contains specs that fail randomly, generally due to part of the JS not loading properly or not loading at all.
|
81
|
+
|
82
|
+
It is difficult to debug these randomly failing specs.
|
83
|
+
|
84
|
+
Capybara provides a mechanism to wait for an expression to be matched agains a page. Check Capybara.default_max_wait_time and the 'wait: ' options on selectors.
|
85
|
+
|
86
|
+
There are cases where waiting for an expression does not help as the JS has not loaded. One reason might be that the JS is loaded through a network request and is inserted on the page without a proper async attribute on the script tag. Another reason might a a bug in a JS around timing issues. Non-trivial apps often load JS from sprockets, webpack, esbuild, vite, network and others where the source is comming from rails gems, npm packages or as hosted scripts.
|
87
|
+
|
88
|
+
The issue with randomly failing spec is that they reduce the trust that a team has in the system specs. Team members might be reluctant to develop and run a proper system specs when a spec fails from time to time.
|
89
|
+
|
90
|
+
During the development of this gem I stumbled upon such a case where the JS on the page was not loaded at all. I ran a spec 100 times and the results were:
|
91
|
+
|
92
|
+
- 100 times - 0 failed
|
93
|
+
- 100 times - Run 2 and Run 14 failed
|
94
|
+
- 100 times - 0 failed
|
95
|
+
- 1000 times - Run 353, 370, 409, 575, 624, 721, 959 failed
|
96
|
+
|
97
|
+
capybara-reloads is **not designed to hide this issues**. These are real issues that happen for real user requests. It is very possible that 1 in 100-200 requests from the set above loads a page that is broken for the user. Users generally reload the page and move one. If it happens once in a few hundred requests they might not even notice it.
|
98
|
+
|
99
|
+
capybara-reloads is **designed to help identify, track and bring more light to this issue**. In this way teams could have more visibility on the patterns in the suites. They could budget proper time to address these issue when they feel they are important. capybara-reloads also provides a way to track information like images and html versions of the page, to log information and to place brakepoints for when this issues occur.
|
100
|
+
|
101
|
+
## Installation
|
102
|
+
|
103
|
+
Add to gem file
|
104
|
+
|
105
|
+
$ gem install capybara-reloads
|
106
|
+
|
107
|
+
or add to Gemfile
|
108
|
+
|
109
|
+
gem 'capybara-reloads'
|
110
|
+
|
111
|
+
## Usage
|
112
|
+
|
113
|
+
capybara-reloads is non-intrusive. It will extend Capybara, but will not change behavior and will not cause a reload unless enabled. **To enable it the value of 'allow_max_reloads' should be explicitly set.**
|
114
|
+
|
115
|
+
For RSpec add the configuration to spes/spec_helper.rb or spec/rails_helper.rb
|
116
|
+
|
117
|
+
### To enable globally for all specs
|
118
|
+
|
119
|
+
This will enable capybara-reloads for every matcher.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
Capybara::Reloads.allow_max_reloads = 2
|
123
|
+
```
|
124
|
+
|
125
|
+
It is recommended to use the Capybara::Reloads.recommended_allow_max_reloads value like:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
Capybara::Reloads.allow_max_reloads = Capybara::Reloads.recommended_allow_max_reloads
|
129
|
+
```
|
130
|
+
|
131
|
+
### To enable for specific matcher
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
expect(page).to have_xpath "//..the xpath value", allow_max_reloads: 2
|
135
|
+
```
|
136
|
+
|
137
|
+
or the recommended
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
expect(page).to have_xpath("//..the xpath value",
|
141
|
+
allow_max_reloads: Capybara::Reloads.recommended_allow_max_reloads)
|
142
|
+
```
|
143
|
+
|
144
|
+
### Stop at a breakpoint only when reload 'fixed it'
|
145
|
+
|
146
|
+
The example below configures capybara-reloads with a breakpoint where a debugger will stop only after a selector was first not matched, then page was refreshed and then the selector was matched for the second page.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
Capybara::Reloads.allow_max_reloads = 1
|
150
|
+
Capybara::Reloads.reload_fixed_it_callback do |args|
|
151
|
+
debugger
|
152
|
+
Capybara::Reloads.construct_message(args)
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
## Configuration
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# The number of reloads that capybara-reloads will do. It will check if the selector is matched for every returned page.
|
160
|
+
# It is a bad idea to have this set to a large number and 1-2 is the recommended on.
|
161
|
+
#
|
162
|
+
# Default value is 0 which disables any reloads
|
163
|
+
Capybara::Reloads.allow_max_reloads
|
164
|
+
|
165
|
+
# The number of max reloads recommended by capybara-reloads author
|
166
|
+
Capybara::Reloads.recommended_allow_max_reloads
|
167
|
+
|
168
|
+
# By default when capybara-reloads reloads the page and the matcher is resolved on the new page, we will throw an expection even though the new
|
169
|
+
# page makes the specs pass. In this way we clearly inform that this spec fails and should be inspected.
|
170
|
+
# When you are ok with the test continuing you can set 'only_report' to true
|
171
|
+
#
|
172
|
+
# Require.
|
173
|
+
# Default value is false
|
174
|
+
Capybara::Reloads::only_report
|
175
|
+
|
176
|
+
# A Proc called before reload of the page
|
177
|
+
#
|
178
|
+
# It is useful to print a different kind of message, to stop, debug and inspect the state.
|
179
|
+
#
|
180
|
+
# Capybara::Reloads.before_reload_callback = Proc.new do |args|
|
181
|
+
# args[:base] # contains the instance of Capybara::Node::Base where we assert the matcher
|
182
|
+
# args[:expcetion] # contains the Capybara::ElementNotFound exception that occurred
|
183
|
+
# end
|
184
|
+
#
|
185
|
+
# Optional. Can be set to nil
|
186
|
+
# Default value is provided that prints a message that the page will be reloaded
|
187
|
+
Capybara::Reloads.before_reload_callback
|
188
|
+
|
189
|
+
# A Proc called to reload the page
|
190
|
+
#
|
191
|
+
# Capybara::Reloads.reload_callback = Proc.new do |args|
|
192
|
+
# args[:base] # contains the instance of Capybara::Node::Base where we assert the matcher
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# Required.
|
196
|
+
# Default value is provided. Check source code of what it is as of current version
|
197
|
+
Capybara::Reloads.reload_callback
|
198
|
+
|
199
|
+
# A proc called after capybara-reloads has reloaded the page and the new page passes the test.
|
200
|
+
# This means that the reload has 'fixed the test'. By default screenshot and html version
|
201
|
+
# of the page are saved in a 'states' array after every assert of the matcher.
|
202
|
+
#
|
203
|
+
# Capybara::Reloads.reload_callback = Proc.new do |args|
|
204
|
+
# args[:states] # contains the states after every assert of the matcher
|
205
|
+
# # The states object contains
|
206
|
+
# {
|
207
|
+
# :html=>"/path/to/html",
|
208
|
+
# :image=>"/path/to/screenshot",
|
209
|
+
# :exception=>... # The Capybara::ElementNotFound exception that occurred,
|
210
|
+
# :reloads_made=>... # The number of reloads that were made when this state was saved
|
211
|
+
# }
|
212
|
+
# args[:reloads_made] # contains the reloads that were already made before this proc was called
|
213
|
+
#
|
214
|
+
# # This return value should be a message.
|
215
|
+
# # It will be reported or raise based on Capybara::Reloads.only_report
|
216
|
+
# # This gives the chance to add additional things to the report message
|
217
|
+
# # that are important for this spec/suite
|
218
|
+
# # The default message is constructed with Capybara::Reloads.construct_message(args)
|
219
|
+
# message
|
220
|
+
# end
|
221
|
+
#
|
222
|
+
# Required.
|
223
|
+
# Default value is provided. Check source code of what it is as of current version
|
224
|
+
# Should return a message
|
225
|
+
Capybara::Reloads.reload_fixed_it_callback
|
226
|
+
```
|
227
|
+
|
228
|
+
## Development
|
229
|
+
|
230
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
231
|
+
|
232
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
233
|
+
|
234
|
+
## Contributing
|
235
|
+
|
236
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/thebravoman/capybara-reloads.
|
237
|
+
|
238
|
+
## License
|
239
|
+
|
240
|
+
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,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/capybara/reloads/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "capybara-reloads"
|
7
|
+
spec.version = Capybara::Reloads::VERSION
|
8
|
+
spec.authors = ["Kiril Mitov"]
|
9
|
+
spec.email = ["kiril [at] retreaver [dot] com"]
|
10
|
+
|
11
|
+
spec.summary = "Utilities for Capybara to allow us to reload the page and check if examples will then pass."
|
12
|
+
spec.homepage = "https://kmitov.com"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/thebravoman/capybara-reloads"
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/thebravoman/capybara-reloads/CHANGELOG.md"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
# Uncomment to register a new dependency of your gem
|
32
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
33
|
+
spec.add_dependency "capybara", "~> 3.0"
|
34
|
+
spec.add_dependency "capybara-screenshot", "~> 1.0"
|
35
|
+
|
36
|
+
# For more information and examples about making a new gem, check out our
|
37
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
38
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Reloads
|
3
|
+
module Node
|
4
|
+
module Matchers
|
5
|
+
|
6
|
+
def assert_selector(*args, &optional_filter_block)
|
7
|
+
with_reload_on_fail(args.last.delete(:allow_max_reloads)) do
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def with_reload_on_fail allow_max_reloads_arg
|
15
|
+
local_max_reloads = allow_max_reloads_arg || Capybara::Reloads.allow_max_reloads
|
16
|
+
unless local_max_reloads.is_a?(Numeric) || local_max_reloads == nil
|
17
|
+
raise "'allow_max_reloads' must be Numeric or nil"
|
18
|
+
end
|
19
|
+
local_max_reloads == local_max_reloads || 0
|
20
|
+
result = nil
|
21
|
+
reloads_made = 0
|
22
|
+
@previous_states = []
|
23
|
+
loop do
|
24
|
+
begin
|
25
|
+
result = yield
|
26
|
+
# there is a result. We don't have to retry
|
27
|
+
# If previously there were errors get the current state and report
|
28
|
+
after_success reloads_made
|
29
|
+
break
|
30
|
+
rescue Capybara::ElementNotFound => e
|
31
|
+
record_state e, reloads_made
|
32
|
+
if reloads_made == local_max_reloads
|
33
|
+
raise e
|
34
|
+
end
|
35
|
+
reloads_made+=1
|
36
|
+
args = {base: self, exception: e}
|
37
|
+
# Allows us to change the way we report that a refresh should happend
|
38
|
+
if Capybara::Reloads.before_reload_callback != nil
|
39
|
+
Capybara::Reloads.before_reload_callback.call(args)
|
40
|
+
end
|
41
|
+
Capybara::Reloads.reload_callback.call(args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def after_success reloads_made
|
48
|
+
if @previous_states.size > 0
|
49
|
+
record_state nil, reloads_made
|
50
|
+
message = Capybara::Reloads.reload_fixed_it_callback.call(states: @previous_states, reloads_made: reloads_made)
|
51
|
+
if Capybara::Reloads.only_report
|
52
|
+
puts message
|
53
|
+
else
|
54
|
+
raise message
|
55
|
+
end
|
56
|
+
@previous_states = []
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def record_state exception, reloads_made
|
61
|
+
html_and_image = Capybara::Screenshot.screenshot_and_save_page
|
62
|
+
# html_and_image is of the form
|
63
|
+
# {:html=>"...screenshot_2022-12-25-17-42-37.712.html", :image=>"...screenshot_2022-12-25-17-42-37.712.png"}
|
64
|
+
@previous_states << html_and_image.merge({exception: exception, reloads_made: reloads_made})
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "reloads/version"
|
4
|
+
require_relative "reloads/node/matchers"
|
5
|
+
|
6
|
+
module Capybara
|
7
|
+
module Reloads
|
8
|
+
class Error < StandardError; end
|
9
|
+
@@recommended_allow_max_reloads = 2
|
10
|
+
|
11
|
+
# By default Capybara::Reloads should have no impact
|
12
|
+
# It should be explicitly enabled, by setting to the recommended_allow_max_reloads with
|
13
|
+
# Capybara::Reloads.allow_max_reloads = CapybaraReload.recommended_allow_max_reloads
|
14
|
+
@@allow_max_reloads = 0
|
15
|
+
|
16
|
+
# The goal of Capybara::Reloads is to help identify places where the JS does not load
|
17
|
+
# not to hide them. By default Capybara::Reloads will try to reload to see if the error
|
18
|
+
# we can continue, but even if we do it will by default report this as an error.
|
19
|
+
# In this way we have clearly shown that this example depends on reloading the page
|
20
|
+
# and this is the real error here. Not that we can't find an element on the page
|
21
|
+
# but that this element is found sometimes and if we refresh it will be found
|
22
|
+
# more often than not
|
23
|
+
#
|
24
|
+
# To make Capybara::Reloads continue use only_report
|
25
|
+
@@only_report = false
|
26
|
+
|
27
|
+
@@before_reload_callback = Proc.new do |args|
|
28
|
+
puts "Refreshing the page as an exception occurred: #{args[:exception].message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
@@reload_callback = Proc.new do |args|
|
32
|
+
base = args[:base]
|
33
|
+
base.session.refresh
|
34
|
+
end
|
35
|
+
|
36
|
+
@@reload_fixed_it_callback = Proc.new do |args|
|
37
|
+
Capybara::Reloads.construct_message(args)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param args
|
41
|
+
# @params states
|
42
|
+
# @params reloads_made
|
43
|
+
def self.construct_message args
|
44
|
+
message = <<-MESSAGE
|
45
|
+
The example initially failed, but after #{args[:reloads_made]} reloads it was successful.
|
46
|
+
States are shown below in order:
|
47
|
+
MESSAGE
|
48
|
+
args[:states].each_with_index do |state, index|
|
49
|
+
message += "\n"
|
50
|
+
message += "State #{index}\n"
|
51
|
+
message += JSON.pretty_generate(state)
|
52
|
+
end
|
53
|
+
message
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.recommended_allow_max_reloads
|
57
|
+
@@recommended_allow_max_reloads
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.allow_max_reloads
|
61
|
+
@@allow_max_reloads
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.allow_max_reloads=(reloads)
|
65
|
+
if reloads > @@recommended_allow_max_reloads
|
66
|
+
puts "You are setting Capybara::Reloads.allow_max_reloads to be #{reloads}."
|
67
|
+
puts "This is more than the recommended value of #{@@recommended_allow_max_reloads} (Capybara::Reloads.recommended_allow_max_reloads)."
|
68
|
+
puts "Please refer to documentation about why this might be a bad idea."
|
69
|
+
end
|
70
|
+
@@allow_max_reloads = reloads
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.only_report
|
74
|
+
@@only_report
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.only_report=(value)
|
78
|
+
@@only_report = value
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.before_reload_callback
|
82
|
+
@@before_reload_callback
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.before_reload_callback=(the_proc)
|
86
|
+
@@before_reload_callback = the_proc
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.reload_callback
|
90
|
+
@@reload_callback
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.reload_callback=(the_proc)
|
94
|
+
@@reload_callback = the_proc
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.reload_fixed_it_callback
|
98
|
+
@@reload_fixed_it_callback
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Capybara::Node::Base.send(:prepend, Capybara::Reloads::Node::Matchers)
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capybara-reloads
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kiril Mitov
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: capybara
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: capybara-screenshot
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- kiril [at] retreaver [dot] com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".rubocop.yml"
|
49
|
+
- CHANGELOG.md
|
50
|
+
- Gemfile
|
51
|
+
- Gemfile.lock
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- capybara-reloads.gemspec
|
56
|
+
- lib/capybara/reloads.rb
|
57
|
+
- lib/capybara/reloads/node/matchers.rb
|
58
|
+
- lib/capybara/reloads/version.rb
|
59
|
+
- sig/capybara/reloads.rbs
|
60
|
+
homepage: https://kmitov.com
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata:
|
64
|
+
homepage_uri: https://kmitov.com
|
65
|
+
source_code_uri: https://github.com/thebravoman/capybara-reloads
|
66
|
+
changelog_uri: https://github.com/thebravoman/capybara-reloads/CHANGELOG.md
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.6.0
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubygems_version: 3.1.6
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Utilities for Capybara to allow us to reload the page and check if examples
|
86
|
+
will then pass.
|
87
|
+
test_files: []
|