rspec-ai-formatter 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +45 -31
- data/lib/rspec/ai_formatter/error_formatter.rb +2 -1
- data/lib/rspec/ai_formatter/formatter.rb +9 -3
- data/lib/rspec/ai_formatter/screenshot_capture.rb +54 -0
- data/lib/rspec/ai_formatter.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc6a7dcbd86749ed1474847222a8ec4ecad876c2f8b2bc2b7db03ede73484b0b
|
|
4
|
+
data.tar.gz: a801f5b55489c3e0b2a101325cf3f91dffb396b067bedfd7a4f68e1e02267800
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02b285fe5fcd39a142700949d96da31c0997f0b60ed37eecc6f115d71bfb3fdd39733f11aff03bae94bd038374790a1412a00bf9fdfe5e2529128dbefdb3acd1
|
|
7
|
+
data.tar.gz: d7b2a3a616155ba2330044f9a39901afb3007863cde7832a31e5797374caa1014e53dff90788d854abc1ece0d73cf3d4e5c018f3a50f1e490d5e7b9770974a26
|
data/README.md
CHANGED
|
@@ -98,32 +98,6 @@ Includes: test names, timestamps, timing for every test.
|
|
|
98
98
|
rspec --format progress --format RSpec::AiFormatter::Formatter --out rspec.jsonl
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
### GitHub Actions
|
|
102
|
-
|
|
103
|
-
The formatter auto-detects `GITHUB_ACTIONS=true` and emits workflow commands for inline PR annotations:
|
|
104
|
-
|
|
105
|
-
```yaml
|
|
106
|
-
- name: Run tests
|
|
107
|
-
env:
|
|
108
|
-
GITHUB_ACTIONS: true # Usually already set by GitHub
|
|
109
|
-
run: bundle exec rspec --format RSpec::AiFormatter::Formatter
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**Annotations appear on:**
|
|
113
|
-
- Failed tests → `::error` with file/line
|
|
114
|
-
- Skipped/pending tests → `::warning`
|
|
115
|
-
|
|
116
|
-
**Example output:**
|
|
117
|
-
```
|
|
118
|
-
::error file=spec/models/user_spec.rb,line=42::expected false, got true
|
|
119
|
-
::warning file=spec/api/client_spec.rb,line=15::Skipped: pending implementation
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
To disable annotations but keep NDJSON:
|
|
123
|
-
```bash
|
|
124
|
-
GITHUB_ACTIONS= bundle exec rspec --format RSpec::AiFormatter::Formatter
|
|
125
|
-
```
|
|
126
|
-
|
|
127
101
|
### Parallel Tests
|
|
128
102
|
|
|
129
103
|
When using `parallel_tests`, each process writes its own output file. Merge them with `rspec-ai-merge`:
|
|
@@ -161,6 +135,46 @@ rspec-ai-merge --no-summary file1.jsonl file2.jsonl
|
|
|
161
135
|
- Combined `done` event with totals from all files
|
|
162
136
|
- Deduplication summaries (if enabled)
|
|
163
137
|
|
|
138
|
+
### Capybara Screenshots
|
|
139
|
+
|
|
140
|
+
For system tests using Capybara, the formatter automatically captures screenshots on failure:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# In your spec_helper.rb or rails_helper.rb
|
|
144
|
+
require 'capybara/rspec'
|
|
145
|
+
|
|
146
|
+
RSpec.configure do |config|
|
|
147
|
+
config.before(:each, type: :system) do
|
|
148
|
+
driven_by :selenium_chrome_headless
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
When a system test fails, the screenshot path is included in the error output:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"t": "test",
|
|
158
|
+
"id": "spec/system/login_spec.rb:15",
|
|
159
|
+
"n": "User logs in successfully",
|
|
160
|
+
"s": "fail",
|
|
161
|
+
"e": {
|
|
162
|
+
"type": "ExpectationNotMet",
|
|
163
|
+
"msg": "expected to find text 'Welcome'",
|
|
164
|
+
"loc": "spec/system/login_spec.rb:20",
|
|
165
|
+
"log": "tmp/rspec_logs/login_spec_15.log",
|
|
166
|
+
"screenshot": "tmp/rspec_logs/login_spec_15.png"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The screenshot is automatically saved to the log directory alongside the failure log.
|
|
172
|
+
|
|
173
|
+
**Requirements:**
|
|
174
|
+
- Capybara gem must be available
|
|
175
|
+
- Test type must be `:feature` or `:system`
|
|
176
|
+
- Browser driver must support screenshots (Selenium, Playwright, etc.)
|
|
177
|
+
|
|
164
178
|
## Output Format
|
|
165
179
|
|
|
166
180
|
### NDJSON Stream
|
|
@@ -233,6 +247,9 @@ spec/models/user_spec.rb:42:in `block (3 levels) in <top (required)>'
|
|
|
233
247
|
| `msg` | Truncated message (200 chars) |
|
|
234
248
|
| `loc` | File:line of failure |
|
|
235
249
|
| `log` | Path to full log file |
|
|
250
|
+
| `screenshot` | Path to Capybara screenshot (system tests only) |
|
|
251
|
+
| `sig` | MD5 signature of error (if dedup/signatures enabled) |
|
|
252
|
+
| `diff` | Expected/actual diff (for expectations) |
|
|
236
253
|
| `sig` | MD5 signature of error (if dedup/signatures enabled) |
|
|
237
254
|
| `diff` | Expected/actual diff (for expectations) |
|
|
238
255
|
|
|
@@ -274,13 +291,10 @@ Useful when a shared setup/teardown failure cascades through many tests.
|
|
|
274
291
|
- ✅ **Diff generation**: Smart expected/actual comparison
|
|
275
292
|
- ✅ **Truncation**: Prevent token explosion on large outputs
|
|
276
293
|
- ✅ **ANSI-free**: Clean text, no escape codes
|
|
277
|
-
- ✅ **
|
|
294
|
+
- ✅ **Screenshot capture**: Auto-capture Capybara screenshots on system test failures
|
|
278
295
|
|
|
279
296
|
## TODO
|
|
280
297
|
|
|
281
|
-
### CI/CD Integration
|
|
282
|
-
- [x] GitHub Actions workflow command integration — Auto-detect `GITHUB_ACTIONS` env and emit `::error` / `::warning` commands for inline PR annotations
|
|
283
|
-
|
|
284
298
|
### Error Intelligence
|
|
285
299
|
- [x] Error pattern deduplication across test runs — Group failures by signature hash, show "and 4 more like this"
|
|
286
300
|
- [ ] Quick-fix hints integration with `did_you_mean` — Suggest typo corrections, similar method names
|
|
@@ -294,7 +308,7 @@ Useful when a shared setup/teardown failure cascades through many tests.
|
|
|
294
308
|
### Compatibility & Tooling
|
|
295
309
|
- [ ] JUnit XML compatibility mode — Dual output for systems requiring XML
|
|
296
310
|
- [x] Parallel test log merging — Clean merge of outputs from `parallel_tests` runs
|
|
297
|
-
- [
|
|
311
|
+
- [x] Screenshot capture for system tests — Auto-capture Capybara screenshot path on failure
|
|
298
312
|
- [ ] Custom output templates — User-defined NDJSON schema / field selection
|
|
299
313
|
|
|
300
314
|
## License
|
|
@@ -4,7 +4,7 @@ module RSpec
|
|
|
4
4
|
module AiFormatter
|
|
5
5
|
# Formats error details for output
|
|
6
6
|
module ErrorFormatter
|
|
7
|
-
def error_details(example, notification, log_path, sig = nil)
|
|
7
|
+
def error_details(example, notification, log_path, sig = nil, screenshot_path = nil)
|
|
8
8
|
exception = notification.exception
|
|
9
9
|
return {} unless exception
|
|
10
10
|
|
|
@@ -15,6 +15,7 @@ module RSpec
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
details[:log] = log_path if log_path && @log_dir != File::NULL
|
|
18
|
+
details[:screenshot] = screenshot_path if screenshot_path
|
|
18
19
|
details[:sig] = sig if sig
|
|
19
20
|
|
|
20
21
|
# Add diff for expectation failures
|
|
@@ -7,6 +7,7 @@ require_relative 'log_writer'
|
|
|
7
7
|
require_relative 'error_formatter'
|
|
8
8
|
require_relative 'location_helper'
|
|
9
9
|
require_relative 'output_helper'
|
|
10
|
+
require_relative 'screenshot_capture'
|
|
10
11
|
|
|
11
12
|
module RSpec
|
|
12
13
|
module AiFormatter
|
|
@@ -17,6 +18,7 @@ module RSpec
|
|
|
17
18
|
include ErrorFormatter
|
|
18
19
|
include LocationHelper
|
|
19
20
|
include OutputHelper
|
|
21
|
+
include ScreenshotCapture
|
|
20
22
|
|
|
21
23
|
RSpec::Core::Formatters.register self,
|
|
22
24
|
:start,
|
|
@@ -75,13 +77,17 @@ module RSpec
|
|
|
75
77
|
exception = notification.exception
|
|
76
78
|
log_path = write_failure_log(ex, notification)
|
|
77
79
|
|
|
80
|
+
# Capture Capybara screenshot if available
|
|
81
|
+
screenshot_path = capture_screenshot(ex)
|
|
82
|
+
screenshot_log_path = copy_screenshot_to_logs(ex, screenshot_path) if screenshot_path
|
|
83
|
+
|
|
78
84
|
# Calculate signature for deduplication
|
|
79
85
|
sig = error_signature(exception) if @deduplicate || ENV['RSPEC_AI_SIGNATURES'] == '1'
|
|
80
86
|
|
|
81
87
|
if @deduplicate && sig && @error_signatures.key?(sig)
|
|
82
88
|
handle_duplicate_error(ex, sig)
|
|
83
89
|
else
|
|
84
|
-
handle_first_error(ex, notification, log_path, sig)
|
|
90
|
+
handle_first_error(ex, notification, log_path, sig, screenshot_log_path)
|
|
85
91
|
end
|
|
86
92
|
end
|
|
87
93
|
|
|
@@ -130,7 +136,7 @@ module RSpec
|
|
|
130
136
|
)
|
|
131
137
|
end
|
|
132
138
|
|
|
133
|
-
def handle_first_error(example, notification, log_path, sig)
|
|
139
|
+
def handle_first_error(example, notification, log_path, sig, screenshot_path = nil)
|
|
134
140
|
# First occurrence of this error
|
|
135
141
|
if @deduplicate && sig
|
|
136
142
|
@error_signatures[sig] = {
|
|
@@ -148,7 +154,7 @@ module RSpec
|
|
|
148
154
|
n: short_name(example),
|
|
149
155
|
s: 'fail',
|
|
150
156
|
time: execution_time_ms(example),
|
|
151
|
-
e: error_details(example, notification, log_path, sig),
|
|
157
|
+
e: error_details(example, notification, log_path, sig, screenshot_path),
|
|
152
158
|
ts: timestamp
|
|
153
159
|
)
|
|
154
160
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSpec
|
|
4
|
+
module AiFormatter
|
|
5
|
+
# Captures Capybara screenshots on test failure
|
|
6
|
+
module ScreenshotCapture
|
|
7
|
+
def capture_screenshot(example)
|
|
8
|
+
return nil unless capybara_available?
|
|
9
|
+
return nil unless example.metadata[:type] == :feature || example.metadata[:type] == :system
|
|
10
|
+
|
|
11
|
+
screenshot_path = take_screenshot
|
|
12
|
+
return nil unless screenshot_path && File.exist?(screenshot_path)
|
|
13
|
+
|
|
14
|
+
screenshot_path
|
|
15
|
+
rescue StandardError
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def copy_screenshot_to_logs(example, screenshot_path)
|
|
20
|
+
return nil unless screenshot_path && @log_dir != File::NULL
|
|
21
|
+
|
|
22
|
+
file = example.metadata[:file_path]
|
|
23
|
+
line = example.metadata[:line_number]
|
|
24
|
+
filename = "#{File.basename(file, '.rb')}_#{line}.png"
|
|
25
|
+
dest_path = File.join(@log_dir, filename)
|
|
26
|
+
|
|
27
|
+
FileUtils.cp(screenshot_path, dest_path)
|
|
28
|
+
dest_path
|
|
29
|
+
rescue StandardError
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def capybara_available?
|
|
36
|
+
defined?(Capybara) && Capybara.respond_to?(:current_session)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def take_screenshot
|
|
40
|
+
# Try different Capybara screenshot methods
|
|
41
|
+
if Capybara.current_session.respond_to?(:save_screenshot)
|
|
42
|
+
# Generate unique temp path
|
|
43
|
+
temp_path = File.join(Dir.tmpdir, "rspec_ai_screenshot_#{Time.now.to_i}_#{rand(1000)}.png")
|
|
44
|
+
Capybara.current_session.save_screenshot(temp_path)
|
|
45
|
+
temp_path
|
|
46
|
+
elsif Capybara.respond_to?(:save_screenshot)
|
|
47
|
+
temp_path = File.join(Dir.tmpdir, "rspec_ai_screenshot_#{Time.now.to_i}_#{rand(1000)}.png")
|
|
48
|
+
Capybara.save_screenshot(temp_path)
|
|
49
|
+
temp_path
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/rspec/ai_formatter.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspec-ai-formatter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SK
|
|
@@ -46,6 +46,7 @@ files:
|
|
|
46
46
|
- lib/rspec/ai_formatter/location_helper.rb
|
|
47
47
|
- lib/rspec/ai_formatter/log_writer.rb
|
|
48
48
|
- lib/rspec/ai_formatter/output_helper.rb
|
|
49
|
+
- lib/rspec/ai_formatter/screenshot_capture.rb
|
|
49
50
|
- lib/rspec_ai_formatter.rb
|
|
50
51
|
homepage: https://github.com/sciencejet/rspec-ai-formatter
|
|
51
52
|
licenses:
|