raider_reporter 0.9.0 → 0.10.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/CHANGELOG.md +8 -0
- data/README.md +9 -2
- data/lib/raider_reporter/collector.rb +6 -3
- data/lib/raider_reporter/configuration.rb +3 -1
- data/lib/raider_reporter/cucumber/formatter.rb +19 -2
- data/lib/raider_reporter/cucumber.rb +1 -1
- data/lib/raider_reporter/version.rb +1 -1
- data/lib/raider_reporter/writers/html.rb +5 -1
- data/lib/raider_reporter/writers/uploader.rb +43 -14
- data/lib/raider_reporter.rb +8 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 291269e17dbda3cec36ff4822af7ba9b16b85f5f1d231eed8b96fb9b89f9e338
|
|
4
|
+
data.tar.gz: 16e1c480d70fc174b25f7b91fcd5f9b242b04f1e7b751172e796050a0c405a39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6bc649c77eaa3a22110dd19b9a5eb28f1e60aab2d6a2877985a8254c85ad647f58d83427e298f79d48a5e420893afc12c06c54a05526a0b0488ca43c015a6510
|
|
7
|
+
data.tar.gz: 1590461029cc94956c3e28fffa9003252d8de1f064211dbcc9d3dcdfe0cbe11c4f9125fafb459978f77f082419b80c36a4702d0a7917ba480d4ed6a740a5f8fb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
- Video attachments play inline in the HTML report: `video/*` attachments render as a `<video controls>` player (metadata preloaded only) with a download link, instead of a plain link. Unsafe paths are still dropped
|
|
6
|
+
- Videos get their own size cap, `config.max_video_bytes` (default 50 MB); the 5 MB `max_attachment_bytes` still applies to everything else. A video over its cap is skipped with a warning
|
|
7
|
+
- Uploads keep non-video attachments (screenshots, logs) first; videos then fill whatever room is left in the upload budget. A video that does not fit is skipped and later, smaller ones are still tried
|
|
8
|
+
- The Cucumber formatter works on Cucumber 12, which removed `config.strict`: it no longer logs `undefined method 'strict'` and keeps its failure counts (Cucumber 10 and 11 behave as before)
|
|
9
|
+
- No breaking changes
|
|
10
|
+
|
|
3
11
|
## 0.9.0 — 2026-10-24
|
|
4
12
|
|
|
5
13
|
- `raider-reporter gate` waits for the Raider quality gate verdict of the CI build (`GET /api/v1/builds/gate`, every 10 seconds, up to `RAIDER_GATE_TIMEOUT` seconds, default 600, 1–3600) and exits 0 when the gate passed or the project has no gate rules, 1 when it failed (naming the failed rules), and 2 when it gets no verdict (timeout, Raider unreachable, token rejected, build not found, any other error). 429, 5xx and network errors are retried within the time limit
|
data/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Run `bundle exec rspec` as usual. (For Cucumber, see [Cucumber](#cucumber).) Res
|
|
|
30
30
|
|
|
31
31
|
## Cucumber
|
|
32
32
|
|
|
33
|
-
Requirements: Cucumber 9, 10 or
|
|
33
|
+
Requirements: Cucumber 9, 10, 11 or 12 (12 needs Ruby 3.3+) in your own Gemfile (`gem "cucumber"`). It is not a dependency of this gem; requiring `raider_reporter/cucumber` without Cucumber raises a `LoadError` that says so. Cucumber 9.2.1 on Ruby 4 also needs `gem "logger"` in your Gemfile, because Cucumber requires it without declaring it.
|
|
34
34
|
|
|
35
35
|
### Plugin mode (recommended)
|
|
36
36
|
|
|
@@ -120,7 +120,11 @@ end
|
|
|
120
120
|
|
|
121
121
|
`attach` copies the file right away, so the attachment keeps the bytes the file had at that moment even if the same path is overwritten by a later example. Using a path per example (as above) still makes the files on disk easier to find.
|
|
122
122
|
|
|
123
|
-
`attach` returns `true` on success. It returns `false` and prints a `[raider_reporter]` warning, never raising, if it is called outside a running example (for instance when the formatter is not loaded), if the file does not exist, or if the file is larger than 5 MB.
|
|
123
|
+
`attach` returns `true` on success. It returns `false` and prints a `[raider_reporter]` warning, never raising, if it is called outside a running example (for instance when the formatter is not loaded), if the file does not exist, or if the file is larger than its size cap: 5 MB (`max_attachment_bytes`), or 50 MB for videos (`video/*`, `max_video_bytes`).
|
|
124
|
+
|
|
125
|
+
Videos (`.webm`, `.mp4`) are attached the same way, for example `RaiderReporter.attach("video", path, type: :webm)`. In the local HTML report they play inline, with a download link.
|
|
126
|
+
|
|
127
|
+
When a run is uploaded, screenshots and other non-video attachments are sent first; videos then fill the room left in the upload budget. A video that does not fit is skipped and later, smaller videos are still tried. The local `raider-results/attachments/` folder keeps every attachment that passed its size cap.
|
|
124
128
|
|
|
125
129
|
## Configuration
|
|
126
130
|
|
|
@@ -139,6 +143,9 @@ end
|
|
|
139
143
|
| `clean_output_dir` | `true` | Remove the previous `run.json` and `attachments/` directory (`run-<n>.json` and `attachments-<n>/` when sharded) inside `output_dir` before writing. Nothing else in `output_dir` is touched, except a raider_reporter-written `index.html`. |
|
|
140
144
|
| `html_report` | `true` | Write `index.html`, the local HTML report, into `output_dir` after each run. See [Local report](#local-report). |
|
|
141
145
|
| `quarantine` | `true` when `ENV["RAIDER_QUARANTINE"]` is `1` or `true` | Fetch the quarantine list at run start and report matching failures as `quarantined`. Needs `token` and `endpoint`. See [Quarantine](#quarantine). |
|
|
146
|
+
| `max_attachment_bytes` | `5 MB` | Largest non-video attachment `attach` accepts. |
|
|
147
|
+
| `max_video_bytes` | `50 MB` | Largest `video/*` attachment `attach` accepts. |
|
|
148
|
+
| `exec_summary_path` | `ENV["RAIDER_EXEC_SUMMARY"]` | File where the run writes its failure counts (framework failures, quarantined, errors outside tests) for `raider-reporter exec` to read when it decides the exit code. `exec` sets it for the command it runs; leave it unset otherwise. Nothing is written when it is unset. See [Quarantine](#quarantine). |
|
|
142
149
|
| `environment` | `{}` | Free-form key/values stored with the run (browser, suite name, and so on). Values are JSON values: strings, booleans or numbers. |
|
|
143
150
|
|
|
144
151
|
## Local output
|
|
@@ -8,9 +8,11 @@ require "set"
|
|
|
8
8
|
module RaiderReporter
|
|
9
9
|
# Accumulates one suite run. Framework formatters call it; writers read the Run it returns.
|
|
10
10
|
class Collector
|
|
11
|
-
def initialize(max_attachment_bytes:, warn:,
|
|
11
|
+
def initialize(max_attachment_bytes:, warn:, max_video_bytes: Configuration::DEFAULT_MAX_VIDEO_BYTES,
|
|
12
|
+
clock: -> { Time.now.utc }, uuid: -> { SecureRandom.uuid },
|
|
12
13
|
quarantine: -> { Quarantine::EMPTY })
|
|
13
14
|
@max_attachment_bytes = max_attachment_bytes
|
|
15
|
+
@max_video_bytes = max_video_bytes
|
|
14
16
|
@warn = warn
|
|
15
17
|
@clock = clock
|
|
16
18
|
@uuid = uuid
|
|
@@ -118,8 +120,9 @@ module RaiderReporter
|
|
|
118
120
|
@warn.call("attachment #{path} no longer exists; skipped")
|
|
119
121
|
return false
|
|
120
122
|
end
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
limit = attachment.content_type.to_s.start_with?("video/") ? @max_video_bytes : @max_attachment_bytes
|
|
124
|
+
if File.size(path) > limit
|
|
125
|
+
@warn.call("attachment #{path} is larger than #{limit} bytes; skipped")
|
|
123
126
|
return false
|
|
124
127
|
end
|
|
125
128
|
true
|
|
@@ -5,9 +5,10 @@ require "raider_reporter/azure_env"
|
|
|
5
5
|
module RaiderReporter
|
|
6
6
|
class Configuration
|
|
7
7
|
TRUTHY = %w[1 true].freeze
|
|
8
|
+
DEFAULT_MAX_VIDEO_BYTES = 50 * 1024 * 1024
|
|
8
9
|
|
|
9
10
|
attr_accessor :token, :endpoint, :output_dir, :clean_output_dir, :html_report, :environment, :max_attachment_bytes,
|
|
10
|
-
:quarantine, :exec_summary_path
|
|
11
|
+
:max_video_bytes, :quarantine, :exec_summary_path
|
|
11
12
|
|
|
12
13
|
def initialize(env = ENV)
|
|
13
14
|
@token = AzureEnv.value(env, "RAIDER_TOKEN")
|
|
@@ -17,6 +18,7 @@ module RaiderReporter
|
|
|
17
18
|
@html_report = true
|
|
18
19
|
@environment = {}
|
|
19
20
|
@max_attachment_bytes = 5 * 1024 * 1024
|
|
21
|
+
@max_video_bytes = DEFAULT_MAX_VIDEO_BYTES
|
|
20
22
|
@quarantine = TRUTHY.include?(AzureEnv.value(env, "RAIDER_QUARANTINE").to_s.downcase)
|
|
21
23
|
@exec_summary_path = AzureEnv.value(env, "RAIDER_EXEC_SUMMARY")
|
|
22
24
|
end
|
|
@@ -160,8 +160,8 @@ module RaiderReporter
|
|
|
160
160
|
return if @outcomes_broken
|
|
161
161
|
|
|
162
162
|
key = "#{test_case.location}|#{test_case.name}"
|
|
163
|
-
failed = !
|
|
164
|
-
flaky = !failed && @failed_before[key] &&
|
|
163
|
+
failed = !result_ok?(result)
|
|
164
|
+
flaky = !failed && @failed_before[key] && strict_flaky?
|
|
165
165
|
@failed_before[key] ||= failed
|
|
166
166
|
@outcomes[key] = failed || flaky ? true : false
|
|
167
167
|
rescue StandardError => e
|
|
@@ -169,6 +169,23 @@ module RaiderReporter
|
|
|
169
169
|
RaiderReporter.warn("#{e.class}: #{e.message}")
|
|
170
170
|
end
|
|
171
171
|
|
|
172
|
+
# Cucumber 10 and 11 carry a strict configuration (config.strict) that result.ok?
|
|
173
|
+
# takes as a keyword. Cucumber 12 removed it: result.ok? takes nothing, pending and
|
|
174
|
+
# undefined always fail, and a flaky scenario never does. Detect, never compare versions.
|
|
175
|
+
def strict_configuration
|
|
176
|
+
@config.strict if @config.respond_to?(:strict)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def result_ok?(result)
|
|
180
|
+
strict = strict_configuration
|
|
181
|
+
strict ? result.ok?(strict: strict) : result.ok?
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def strict_flaky?
|
|
185
|
+
strict = strict_configuration
|
|
186
|
+
strict ? strict.strict?(:flaky) : false
|
|
187
|
+
end
|
|
188
|
+
|
|
172
189
|
# Scenarios that make cucumber's run fail; one error outside tests when the run
|
|
173
190
|
# stopped before every scenario finished (--fail-fast) (spec §4.4).
|
|
174
191
|
def record_framework_counts
|
|
@@ -5,7 +5,7 @@ begin
|
|
|
5
5
|
rescue LoadError => e
|
|
6
6
|
raise unless e.path == "cucumber"
|
|
7
7
|
|
|
8
|
-
raise LoadError, "raider_reporter/cucumber needs the cucumber gem (>= 9, <
|
|
8
|
+
raise LoadError, "raider_reporter/cucumber needs the cucumber gem (>= 9, < 13) in your Gemfile"
|
|
9
9
|
end
|
|
10
10
|
require "raider_reporter"
|
|
11
11
|
require_relative "cucumber/formatter"
|
|
@@ -224,8 +224,11 @@ module RaiderReporter
|
|
|
224
224
|
href = safe_href(attachment["path"])
|
|
225
225
|
next unless href
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
content_type = attachment["content_type"].to_s
|
|
228
|
+
if content_type.start_with?("image/")
|
|
228
229
|
%(<a href="#{href}"><img loading="lazy" src="#{href}" alt="#{h(attachment['name'])}"></a>)
|
|
230
|
+
elsif content_type.start_with?("video/")
|
|
231
|
+
%(<video controls preload="metadata" src="#{href}"></video><a href="#{href}" download>#{h(attachment['name'])}</a>)
|
|
229
232
|
else
|
|
230
233
|
%(<a href="#{href}">#{h(attachment['name'])}</a>)
|
|
231
234
|
end
|
|
@@ -280,6 +283,7 @@ module RaiderReporter
|
|
|
280
283
|
.r p{margin:2px 0;overflow-wrap:anywhere}.n{font-weight:500}.x{color:var(--ruby);font-weight:600}
|
|
281
284
|
pre{margin:4px 0;padding:8px;background:var(--card);border-radius:6px;overflow-x:auto;white-space:pre-wrap;font-size:12px}
|
|
282
285
|
.a img{max-width:100%;max-height:240px;border:1px solid var(--bdr);border-radius:4px;margin-top:4px}
|
|
286
|
+
.a video{max-width:100%;max-height:360px;display:block}
|
|
283
287
|
a{color:var(--ruby)}.empty{padding:24px 0;color:var(--mute)}
|
|
284
288
|
CSS
|
|
285
289
|
|
|
@@ -48,30 +48,59 @@ module RaiderReporter
|
|
|
48
48
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
|
-
# Keeps the
|
|
52
|
-
#
|
|
53
|
-
#
|
|
51
|
+
# Keeps the attachments that fit the byte and count budget, non-video
|
|
52
|
+
# attachments first and videos in whatever room is left, and rebuilds the
|
|
53
|
+
# payload without the rest, so part names stay contiguous. Survivors keep
|
|
54
|
+
# their original order. The local run.json is written from the original
|
|
55
|
+
# payload and keeps all.
|
|
54
56
|
def within_budget(payload)
|
|
55
57
|
entries = payload.attachment_entries
|
|
56
|
-
|
|
57
|
-
keep
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end || entries.size
|
|
61
|
-
keep = [keep, MAX_UPLOAD_ATTACHMENTS].min
|
|
62
|
-
return payload if keep == entries.size
|
|
63
|
-
|
|
64
|
-
dropped = entries.size - keep
|
|
58
|
+
keep = budgeted(entries)
|
|
59
|
+
return payload if keep.size == entries.size
|
|
60
|
+
|
|
61
|
+
dropped = entries.size - keep.size
|
|
65
62
|
@warn.call("not uploading #{dropped} #{dropped == 1 ? 'attachment' : 'attachments'}: over the upload " \
|
|
66
63
|
"budget of #{MAX_UPLOAD_ATTACHMENT_BYTES / (1024 * 1024)} MB / " \
|
|
67
64
|
"#{MAX_UPLOAD_ATTACHMENTS} files; they remain in #{@config.output_dir}")
|
|
68
|
-
seen = 0
|
|
69
65
|
run = payload.run
|
|
70
66
|
Payload.new(run.with(results: run.results.map do |result|
|
|
71
|
-
result.with(attachments: result.attachments.select { (
|
|
67
|
+
result.with(attachments: result.attachments.select { |attachment| keep.key?(attachment) })
|
|
72
68
|
end))
|
|
73
69
|
end
|
|
74
70
|
|
|
71
|
+
# Identity-keyed set of the attachments to upload. Non-videos come first
|
|
72
|
+
# and stop at the first byte overflow; videos then fill the room left,
|
|
73
|
+
# a video that does not fit is skipped and later ones are still tried.
|
|
74
|
+
def budgeted(entries)
|
|
75
|
+
videos, others = entries.partition { |entry| video?(entry[:attachment]) }
|
|
76
|
+
keep = {}.compare_by_identity
|
|
77
|
+
bytes = 0
|
|
78
|
+
others.each do |entry|
|
|
79
|
+
bytes += size_of(entry)
|
|
80
|
+
break if bytes > MAX_UPLOAD_ATTACHMENT_BYTES || keep.size >= MAX_UPLOAD_ATTACHMENTS
|
|
81
|
+
|
|
82
|
+
keep[entry[:attachment]] = true
|
|
83
|
+
end
|
|
84
|
+
videos.each do |entry|
|
|
85
|
+
break if keep.size >= MAX_UPLOAD_ATTACHMENTS
|
|
86
|
+
|
|
87
|
+
size = size_of(entry)
|
|
88
|
+
next if bytes + size > MAX_UPLOAD_ATTACHMENT_BYTES
|
|
89
|
+
|
|
90
|
+
bytes += size
|
|
91
|
+
keep[entry[:attachment]] = true
|
|
92
|
+
end
|
|
93
|
+
keep
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def video?(attachment)
|
|
97
|
+
attachment.content_type.to_s.start_with?("video/")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def size_of(entry)
|
|
101
|
+
File.size?(entry[:attachment].source_path).to_i
|
|
102
|
+
end
|
|
103
|
+
|
|
75
104
|
def add_field(body, boundary, name, value)
|
|
76
105
|
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"#{name}\"\r\n\r\n".b
|
|
77
106
|
body << value.to_s.b << "\r\n".b
|
data/lib/raider_reporter.rb
CHANGED
|
@@ -50,6 +50,7 @@ module RaiderReporter
|
|
|
50
50
|
@project_root = Dir.pwd
|
|
51
51
|
@collector = Collector.new(
|
|
52
52
|
max_attachment_bytes: configuration.max_attachment_bytes,
|
|
53
|
+
max_video_bytes: configuration.max_video_bytes,
|
|
53
54
|
warn: ->(message) { warn(message) },
|
|
54
55
|
quarantine: -> { quarantined_keys }
|
|
55
56
|
)
|
|
@@ -70,14 +71,15 @@ module RaiderReporter
|
|
|
70
71
|
return false
|
|
71
72
|
end
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
content_type = ContentTypes.resolve(type, path)
|
|
75
|
+
limit = attachment_limit(content_type)
|
|
74
76
|
if File.size(path.to_s) > limit
|
|
75
77
|
warn("attach(#{name.inspect}): file #{path} is larger than #{limit} bytes; ignored")
|
|
76
78
|
return false
|
|
77
79
|
end
|
|
78
80
|
|
|
79
81
|
collector.attach(Attachment.new(name: name.to_s, source_path: collector.stage(path.to_s),
|
|
80
|
-
content_type:
|
|
82
|
+
content_type: content_type))
|
|
81
83
|
rescue StandardError => e
|
|
82
84
|
warn("attach(#{name.inspect}) failed: #{e.class}: #{e.message}")
|
|
83
85
|
false
|
|
@@ -119,6 +121,10 @@ module RaiderReporter
|
|
|
119
121
|
|
|
120
122
|
private
|
|
121
123
|
|
|
124
|
+
def attachment_limit(content_type)
|
|
125
|
+
content_type.start_with?("video/") ? configuration.max_video_bytes : configuration.max_attachment_bytes
|
|
126
|
+
end
|
|
127
|
+
|
|
122
128
|
# First, so a later reporting problem cannot lose it. A failure here only means
|
|
123
129
|
# no summary, and exec then keeps the command's exit code.
|
|
124
130
|
def write_exec_summary
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: raider_reporter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Augustin Gottlieb
|
|
@@ -74,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
74
74
|
- !ruby/object:Gem::Version
|
|
75
75
|
version: '0'
|
|
76
76
|
requirements: []
|
|
77
|
-
rubygems_version: 4.0.
|
|
77
|
+
rubygems_version: 4.0.20
|
|
78
78
|
specification_version: 4
|
|
79
79
|
summary: RSpec and Cucumber formatters that write Raider reports locally and upload
|
|
80
80
|
them to Raider
|