ruby_raider 3.9.0 → 3.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/.github/workflows/release.yml +18 -29
- data/.github/workflows/security.yml +68 -0
- data/.rubocop.yml +28 -0
- data/README.md +22 -4
- data/docs/superpowers/plans/2026-10-25-failure-video-replay.md +400 -0
- data/docs/superpowers/plans/2026-10-26-parity-cleanup.md +233 -0
- data/docs/superpowers/plans/2026-10-28-failure-card.md +295 -0
- data/docs/superpowers/specs/2026-10-25-failure-video-replay-design.md +214 -0
- data/docs/superpowers/specs/2026-10-28-failure-card-design.md +210 -0
- data/lib/commands/scaffolding_commands.rb +3 -3
- data/lib/commands/utility_commands.rb +36 -6
- data/lib/generators/cucumber/templates/cucumber.tt +1 -1
- data/lib/generators/cucumber/templates/partials/appium_env.tt +6 -0
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +6 -0
- data/lib/generators/cucumber/templates/partials/watir_env.tt +6 -0
- data/lib/generators/helper_generator.rb +5 -1
- data/lib/generators/infrastructure/azure_generator.rb +1 -0
- data/lib/generators/infrastructure/github_generator.rb +1 -0
- data/lib/generators/infrastructure/templates/azure_pipelines.tt +12 -1
- data/lib/generators/infrastructure/templates/github.tt +12 -0
- data/lib/generators/infrastructure/templates/github_appium.tt +23 -0
- data/lib/generators/invoke_generators.rb +20 -2
- data/lib/generators/rspec/rspec_generator.rb +0 -1
- data/lib/generators/templates/common/gemfile.tt +1 -1
- data/lib/generators/templates/common/git_ignore.tt +3 -0
- data/lib/generators/templates/common/partials/mobile_config.tt +5 -0
- data/lib/generators/templates/common/partials/web_config.tt +6 -1
- data/lib/generators/templates/common/read_me.tt +60 -2
- data/lib/generators/templates/helpers/browser_helper.tt +5 -1
- data/lib/generators/templates/helpers/driver_helper.tt +1 -0
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +4 -1
- data/lib/generators/templates/helpers/spec_helper.tt +8 -0
- data/lib/generators/templates/helpers/video_helper.tt +338 -171
- data/lib/ruby_raider/ffmpeg.rb +280 -0
- data/lib/ruby_raider.rb +2 -3
- data/lib/scaffolding/project_detector.rb +1 -3
- data/lib/scaffolding/scaffolding.rb +0 -1
- data/lib/utilities/utilities.rb +15 -2
- data/lib/version +1 -1
- data/ruby_raider.gemspec +3 -1
- data/sig/commands/utility_commands.rbs +4 -1
- data/sig/generators/invoke_generators.rbs +2 -3
- data/sig/ruby_raider/ffmpeg.rbs +23 -0
- data/sig/utilities/utilities.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +56 -1
- data/spec/generators/fixtures/video_helper_stubs/selenium-webdriver.rb +28 -0
- data/spec/generators/video_helper_spec.rb +608 -0
- data/spec/integration/commands/browser_update_after_creation_spec.rb +7 -6
- data/spec/integration/commands/utility_commands_spec.rb +2 -2
- data/spec/integration/content/azure_content_spec.rb +29 -0
- data/spec/integration/content/ci_content_spec.rb +43 -0
- data/spec/integration/content/common_content_spec.rb +8 -0
- data/spec/integration/content/config_content_spec.rb +14 -0
- data/spec/integration/content/cucumber_reporter_content_spec.rb +22 -1
- data/spec/integration/content/gemfile_content_spec.rb +2 -3
- data/spec/integration/content/helper_content_spec.rb +146 -0
- data/spec/integration/content/page_content_spec.rb +0 -1
- data/spec/integration/content/readme_content_spec.rb +51 -0
- data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
- data/spec/integration/content/test_content_spec.rb +2 -3
- data/spec/integration/end_to_end_features_spec.rb +0 -2
- data/spec/integration/end_to_end_spec.rb +252 -26
- data/spec/integration/generators/azure_generator_spec.rb +7 -6
- data/spec/integration/generators/common_generator_spec.rb +1 -1
- data/spec/integration/generators/invoke_generators_spec.rb +52 -0
- data/spec/integration/scaffolding_e2e_spec.rb +13 -24
- data/spec/scaffolding/scaffold_project_detector_spec.rb +0 -1
- data/spec/scaffolding/scaffolding_features_spec.rb +0 -1
- data/spec/system/selenium_spec.rb +0 -1
- data/spec/system/watir_spec.rb +0 -1
- data/spec/utilities/ffmpeg_spec.rb +424 -0
- data/spec/utilities/utilities_spec.rb +19 -2
- data/spec/utilities/video_command_spec.rb +120 -0
- metadata +49 -11
- data/lib/commands/adopt_commands.rb +0 -42
- data/lib/commands/loaded_commands.rb +0 -9
- data/lib/commands/plugin_commands.rb +0 -32
- data/sig/commands/adopt_commands.rbs +0 -8
- data/sig/commands/loaded_commands.rbs +0 -5
- data/sig/commands/plugin_commands.rbs +0 -9
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'yaml'
|
|
4
4
|
require 'fileutils'
|
|
5
5
|
require_relative 'content_helper'
|
|
6
|
+
require_relative '../../../lib/ruby_raider/ffmpeg'
|
|
6
7
|
|
|
7
8
|
# UseRubyVersion@0 versionSpec '3.4' resolves against the hosted tool cache, which lists Ruby 3.4.x
|
|
8
9
|
# (3.2, 3.3, 3.4, 4.0): https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md
|
|
@@ -47,6 +48,33 @@ describe 'Azure Pipelines content' do
|
|
|
47
48
|
)
|
|
48
49
|
end
|
|
49
50
|
|
|
51
|
+
it 'caches the ffmpeg dir keyed on the ffmpeg version and Gemfile.lock, then installs ffmpeg, before the tests' do
|
|
52
|
+
steps = yml['jobs'][0]['steps']
|
|
53
|
+
cache = steps.index { |s| s['task'] == 'Cache@2' }
|
|
54
|
+
install = steps.index { |s| s['script'] == 'bundle exec raider u video install' }
|
|
55
|
+
tests = steps.index { |s| s['script'] == run_command }
|
|
56
|
+
expect([cache, install, tests]).to all(be_an(Integer))
|
|
57
|
+
expect(cache).to be < install
|
|
58
|
+
expect(install).to be < tests
|
|
59
|
+
expect(steps[cache]['inputs']).to eq(
|
|
60
|
+
'key' => "raider-ffmpeg-#{RubyRaider::Ffmpeg::MANIFEST[:version]} | \"$(Agent.OS)\" | Gemfile.lock",
|
|
61
|
+
'path' => '$(RAIDER_CACHE_DIR)'
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'caches RAIDER_CACHE_DIR in the pipeline workspace and keeps the ffmpeg install from failing the build' do
|
|
66
|
+
install = yml['jobs'][0]['steps'].find { |s| s['script'] == 'bundle exec raider u video install' }
|
|
67
|
+
expect(install['continueOnError']).to be(true)
|
|
68
|
+
expect(yml['variables']['RAIDER_CACHE_DIR']).to eq('$(Pipeline.Workspace)/.cache/raider')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'adds no cache or install step to the comment and gate jobs' do
|
|
72
|
+
yml['jobs'][1..].each do |job|
|
|
73
|
+
expect(job['steps'].map { |s| s['task'] || s['script'] })
|
|
74
|
+
.not_to include('Cache@2', 'bundle exec raider u video install')
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
50
78
|
it 'passes exactly the Raider settings to the test step' do
|
|
51
79
|
step = yml['jobs'][0]['steps'].find { |s| s['script'] == run_command }
|
|
52
80
|
expect(step['env']).to eq('RAIDER_TOKEN' => '$(RAIDER_TOKEN)', 'RAIDER_ENDPOINT' => '$(RAIDER_ENDPOINT)',
|
|
@@ -102,6 +130,7 @@ describe 'Azure Pipelines content' do
|
|
|
102
130
|
'pool: { name: Default }', 'Build validation branch policy',
|
|
103
131
|
'same stage', 'raider_gate', 'RAIDER_GATE=1', 'RAIDER_SHARD_TOTAL', 'RAIDER_SHARD_INDEX')
|
|
104
132
|
expect(readme).not_to match(/GitHub Actions/i)
|
|
133
|
+
expect(readme).to include('`RAIDER_CACHE_DIR` to `$(Pipeline.Workspace)/.cache/raider`')
|
|
105
134
|
end
|
|
106
135
|
end
|
|
107
136
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'yaml'
|
|
4
4
|
require_relative 'content_helper'
|
|
5
|
+
require_relative '../../../lib/ruby_raider/ffmpeg'
|
|
5
6
|
|
|
6
7
|
describe 'CI pipeline content' do
|
|
7
8
|
shared_examples 'valid github pipeline' do |project_name|
|
|
@@ -208,8 +209,45 @@ describe 'CI pipeline content' do
|
|
|
208
209
|
end
|
|
209
210
|
end
|
|
210
211
|
|
|
212
|
+
shared_examples 'github ffmpeg cache' do |project_name, file, test_jobs, runner = 'rspec'|
|
|
213
|
+
subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") }
|
|
214
|
+
|
|
215
|
+
let(:jobs) { YAML.safe_load(pipeline)['jobs'] }
|
|
216
|
+
|
|
217
|
+
test_jobs.each do |job_name|
|
|
218
|
+
it "caches ~/.cache/raider keyed on the ffmpeg version and Gemfile.lock, then installs ffmpeg, before the tests in #{job_name}" do
|
|
219
|
+
steps = jobs[job_name]['steps']
|
|
220
|
+
cache = steps.index { |step| step['uses'] == 'actions/cache@v4' }
|
|
221
|
+
install = steps.index { |step| step['run'] == 'bundle exec raider u video install' }
|
|
222
|
+
tests = steps.index do |step|
|
|
223
|
+
step['run'].to_s.include?(runner) || step['with'].to_h['script'].to_s.include?(runner)
|
|
224
|
+
end
|
|
225
|
+
expect([cache, install, tests]).to all(be_an(Integer))
|
|
226
|
+
expect(cache).to be < install
|
|
227
|
+
expect(install).to be < tests
|
|
228
|
+
expect(steps[cache]['with']).to eq(
|
|
229
|
+
'path' => '~/.cache/raider',
|
|
230
|
+
'key' => "raider-ffmpeg-#{RubyRaider::Ffmpeg::MANIFEST[:version]}-${{ runner.os }}-${{ hashFiles('**/Gemfile.lock') }}"
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it "keeps the ffmpeg install step from failing #{job_name}, since video is optional" do
|
|
235
|
+
install = jobs[job_name]['steps'].find { |step| step['run'] == 'bundle exec raider u video install' }
|
|
236
|
+
expect(install['continue-on-error']).to be(true)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it 'adds no cache or install step to the comment and gate jobs' do
|
|
241
|
+
jobs.slice('raider-comment', 'raider-gate').each_value do |job|
|
|
242
|
+
expect(job['steps'].map { |step| step['run'] || step['uses'] })
|
|
243
|
+
.not_to include('actions/cache@v4', 'bundle exec raider u video install')
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
211
248
|
context 'with rspec and selenium' do
|
|
212
249
|
name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
250
|
+
include_examples 'github ffmpeg cache', name, 'test_pipeline.yml', %w[build]
|
|
213
251
|
include_examples 'valid github pipeline', name
|
|
214
252
|
include_examples 'github rspec pipeline', name
|
|
215
253
|
include_examples 'github triggers', name, 'test_pipeline.yml'
|
|
@@ -221,6 +259,7 @@ describe 'CI pipeline content' do
|
|
|
221
259
|
|
|
222
260
|
context 'with cucumber and selenium' do
|
|
223
261
|
name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
262
|
+
include_examples 'github ffmpeg cache', name, 'test_pipeline.yml', %w[build], 'cucumber'
|
|
224
263
|
include_examples 'valid github pipeline', name
|
|
225
264
|
include_examples 'github cucumber pipeline', name
|
|
226
265
|
include_examples 'github triggers', name, 'test_pipeline.yml'
|
|
@@ -232,6 +271,7 @@ describe 'CI pipeline content' do
|
|
|
232
271
|
|
|
233
272
|
context 'with rspec and watir' do
|
|
234
273
|
name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::WATIR}"
|
|
274
|
+
include_examples 'github ffmpeg cache', name, 'test_pipeline.yml', %w[build]
|
|
235
275
|
include_examples 'valid github pipeline', name
|
|
236
276
|
include_examples 'github rspec pipeline', name
|
|
237
277
|
include_examples 'github triggers', name, 'test_pipeline.yml'
|
|
@@ -243,6 +283,7 @@ describe 'CI pipeline content' do
|
|
|
243
283
|
|
|
244
284
|
context 'with cucumber and watir' do
|
|
245
285
|
name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::WATIR}"
|
|
286
|
+
include_examples 'github ffmpeg cache', name, 'test_pipeline.yml', %w[build], 'cucumber'
|
|
246
287
|
include_examples 'valid github pipeline', name
|
|
247
288
|
include_examples 'github cucumber pipeline', name
|
|
248
289
|
include_examples 'github triggers', name, 'test_pipeline.yml'
|
|
@@ -261,6 +302,7 @@ describe 'CI pipeline content' do
|
|
|
261
302
|
context "with rspec and #{automation}" do
|
|
262
303
|
name = "#{FrameworkIndex::RSPEC}_#{automation}"
|
|
263
304
|
include_examples 'github appium shards', name, 'rspec', appium_shards[automation]
|
|
305
|
+
include_examples 'github ffmpeg cache', name, 'appium_pipeline.yml', test_jobs
|
|
264
306
|
include_examples 'github raider-comment job', name, 'appium_pipeline.yml', test_jobs
|
|
265
307
|
include_examples 'github raider-gate job', name, 'appium_pipeline.yml', test_jobs
|
|
266
308
|
include_examples 'github triggers', name, 'appium_pipeline.yml'
|
|
@@ -270,6 +312,7 @@ describe 'CI pipeline content' do
|
|
|
270
312
|
context "with cucumber and #{automation}" do
|
|
271
313
|
name = "#{FrameworkIndex::CUCUMBER}_#{automation}"
|
|
272
314
|
include_examples 'github appium shards', name, 'cucumber', appium_shards[automation]
|
|
315
|
+
include_examples 'github ffmpeg cache', name, 'appium_pipeline.yml', test_jobs, 'cucumber'
|
|
273
316
|
include_examples 'github triggers', name, 'appium_pipeline.yml'
|
|
274
317
|
include_examples 'github raider wiring', name, 'appium_pipeline.yml', 'cucumber'
|
|
275
318
|
include_examples 'github raider-comment job', name, 'appium_pipeline.yml', test_jobs
|
|
@@ -133,6 +133,10 @@ describe 'Common file content' do
|
|
|
133
133
|
it 'includes raider-results' do
|
|
134
134
|
expect(gitignore).to include('raider-results')
|
|
135
135
|
end
|
|
136
|
+
|
|
137
|
+
it 'includes rerun.txt' do
|
|
138
|
+
expect(gitignore).to include('rerun.txt')
|
|
139
|
+
end
|
|
136
140
|
end
|
|
137
141
|
|
|
138
142
|
# --- Spec helper retry config (C4) ---
|
|
@@ -187,6 +191,10 @@ describe 'Common file content' do
|
|
|
187
191
|
it 'includes --retry flag' do
|
|
188
192
|
expect(cucumber_yml).to include('--retry')
|
|
189
193
|
end
|
|
194
|
+
|
|
195
|
+
it 'writes failed scenarios to rerun.txt for Re-run Failed' do
|
|
196
|
+
expect(cucumber_yml).to include('--format rerun --out rerun.txt')
|
|
197
|
+
end
|
|
190
198
|
end
|
|
191
199
|
|
|
192
200
|
# --- RSpec contexts ---
|
|
@@ -51,12 +51,21 @@ describe 'Config file content' do
|
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
shared_examples 'video config' do |project_name|
|
|
55
|
+
subject(:config) { YAML.safe_load(read_generated(project_name, 'config/config.yml'), permitted_classes: [Symbol]) }
|
|
56
|
+
|
|
57
|
+
it 'has video enabled at 5 fps and 1280 px max width' do
|
|
58
|
+
expect(config['video']).to eq('enabled' => true, 'fps' => 5, 'max_width' => 1280)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
54
62
|
# --- Web config per automation ---
|
|
55
63
|
|
|
56
64
|
context 'with rspec and selenium' do
|
|
57
65
|
name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
58
66
|
include_examples 'valid web config', name
|
|
59
67
|
include_examples 'selenium config with timeout', name
|
|
68
|
+
include_examples 'video config', name
|
|
60
69
|
end
|
|
61
70
|
|
|
62
71
|
context 'with rspec and watir' do
|
|
@@ -73,6 +82,11 @@ describe 'Config file content' do
|
|
|
73
82
|
|
|
74
83
|
# --- Mobile config ---
|
|
75
84
|
|
|
85
|
+
# Single-platform Appium projects have no config.yml; VideoHelper falls back to its defaults.
|
|
86
|
+
context 'with rspec and cross platform' do
|
|
87
|
+
include_examples 'video config', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
88
|
+
end
|
|
89
|
+
|
|
76
90
|
context 'with rspec and appium android' do
|
|
77
91
|
subject(:caps) { read_generated("#{FrameworkIndex::RSPEC}_#{AutomationIndex::ANDROID}", 'config/capabilities.yml') }
|
|
78
92
|
|
|
@@ -36,7 +36,7 @@ describe 'Cucumber raider_reporter wiring' do
|
|
|
36
36
|
|
|
37
37
|
it 'quits the driver in an ensure of the After block, without a redundant begin' do
|
|
38
38
|
quit = %w[android ios cross_platform].include?(automation) ? 'driver.quit_driver' : quit_call
|
|
39
|
-
expect(hook).to match(/^\s*After do \|scenario\|\n.*\n^ensure\n\s+#{Regexp.escape(quit)}\nend/m)
|
|
39
|
+
expect(hook).to match(/^\s*After do \|scenario\|\n.*\n^ensure\n.*\n\s+#{Regexp.escape(quit)}\nend/m)
|
|
40
40
|
expect(hook).not_to match(/^\s+begin$/)
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -47,6 +47,27 @@ describe 'Cucumber raider_reporter wiring' do
|
|
|
47
47
|
expect(env.index(attach)).to be < env.index(/driver\.quit|browser\.quit/, env.index(attach))
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
context 'with video recording' do
|
|
51
|
+
target = automation == 'watir' ? 'browser' : 'driver'
|
|
52
|
+
video_type = %w[selenium watir].include?(automation) ? ':webm' : ':mp4'
|
|
53
|
+
|
|
54
|
+
it 'requires video_helper' do
|
|
55
|
+
expect(env).to include("require_relative '../../helpers/video_helper'")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'starts the recorder in Before' do
|
|
59
|
+
expect(env[/^Before do.*?^end$/m]).to include("@video_recorder = VideoHelper.recorder_for(#{target})", '@video_recorder.start')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'stops with keep: scenario.failed? before quitting, attaches the path and cleans up in the ensure' do
|
|
63
|
+
expect(hook).to include('video_path = @video_recorder&.stop(keep: scenario.failed?)')
|
|
64
|
+
expect(hook).to include("RaiderReporter.attach('video', video_path, type: #{video_type}) if video_path")
|
|
65
|
+
expect(hook.index('@video_recorder&.stop')).to be < hook.index("RaiderReporter.attach('video'")
|
|
66
|
+
expect(hook.index("RaiderReporter.attach('video'")).to be < hook.index(/^ensure/)
|
|
67
|
+
expect(hook.index(/^ensure/)).to be < hook.index('@video_recorder&.cleanup')
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
50
71
|
it 'does not generate the allure helper' do
|
|
51
72
|
expect(File).not_to exist(File.join(project_name, 'helpers/allure_helper.rb'))
|
|
52
73
|
end
|
|
@@ -127,7 +127,7 @@ describe 'Gemfile content' do
|
|
|
127
127
|
let(:gemfile) { read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'Gemfile') }
|
|
128
128
|
|
|
129
129
|
it 'includes raider_reporter' do
|
|
130
|
-
expect(gemfile).to include("gem 'raider_reporter', '~> 0.
|
|
130
|
+
expect(gemfile).to include("gem 'raider_reporter', '~> 0.10'")
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
it 'has no allure gems' do
|
|
@@ -143,7 +143,7 @@ describe 'Gemfile content' do
|
|
|
143
143
|
let(:gemfile) { read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'Gemfile') }
|
|
144
144
|
|
|
145
145
|
it 'includes raider_reporter' do
|
|
146
|
-
expect(gemfile).to include("gem 'raider_reporter', '~> 0.
|
|
146
|
+
expect(gemfile).to include("gem 'raider_reporter', '~> 0.10'")
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
it 'drops allure-cucumber' do
|
|
@@ -156,5 +156,4 @@ describe 'Gemfile content' do
|
|
|
156
156
|
end
|
|
157
157
|
end
|
|
158
158
|
end
|
|
159
|
-
|
|
160
159
|
end
|
|
@@ -139,6 +139,43 @@ describe 'Helper file content' do
|
|
|
139
139
|
end
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
+
shared_examples 'video spec helper wiring' do |project_name, recorder_target, video_type|
|
|
143
|
+
subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') }
|
|
144
|
+
|
|
145
|
+
let(:before_hook) { helper[/config\.before\(:each\) do.*?^ end$/m] }
|
|
146
|
+
let(:after_hook) { helper[/config\.after\(:each\).*\z/m] }
|
|
147
|
+
|
|
148
|
+
it 'requires video_helper' do
|
|
149
|
+
expect(helper).to include("require_relative 'video_helper'")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'starts a recorder in before(:each), after the viewport resize or driver start' do
|
|
153
|
+
start = "@video_recorder = VideoHelper.recorder_for(#{recorder_target})"
|
|
154
|
+
expect(before_hook).to include(start, '@video_recorder.start')
|
|
155
|
+
setup = before_hook[/driver\.start_driver|\.window\.maximize/]
|
|
156
|
+
expect(before_hook.index(setup)).to be < before_hook.index(start)
|
|
157
|
+
expect(before_hook.index(start)).to be < before_hook.index('@video_recorder.start')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'attaches "video" only when example.exception && VideoHelper.final_attempt?(example)' do
|
|
161
|
+
expect(after_hook).to match(/example\.exception\s*(\?|&&)\s*VideoHelper\.final_attempt\?\(example\)/)
|
|
162
|
+
expect(after_hook).to include("RaiderReporter.attach('video', video_path, type: #{video_type}) if video_path")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'stops and attaches, then cleans up and quits the driver in the ensure, so the browser closes if an attach raises' do
|
|
166
|
+
order = [after_hook.index('@video_recorder&.stop(keep: keep_video)'), after_hook.index("RaiderReporter.attach('video'"),
|
|
167
|
+
after_hook.index("\n ensure\n"), after_hook.index('@video_recorder&.cleanup'),
|
|
168
|
+
after_hook.index(/driver\.quit|browser\.quit/)]
|
|
169
|
+
expect(order).to all(be_an(Integer)).and eq(order.compact.sort)
|
|
170
|
+
expect(after_hook.scan(/driver\.quit|browser\.quit/).size).to eq(1)
|
|
171
|
+
expect(after_hook[order[2]..]).to match(/\n @video_recorder&\.cleanup\n (driver|browser)\.quit(_driver)?\n end\n/)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'keeps the screenshot attachment' do
|
|
175
|
+
expect(after_hook).to include("RaiderReporter.attach('screenshot', screenshot_path, type: :png)")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
142
179
|
shared_examples 'selenium spec helper' do |project_name|
|
|
143
180
|
subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') }
|
|
144
181
|
|
|
@@ -171,6 +208,113 @@ describe 'Helper file content' do
|
|
|
171
208
|
end
|
|
172
209
|
end
|
|
173
210
|
|
|
211
|
+
# --- Video helper ---
|
|
212
|
+
|
|
213
|
+
shared_examples 'valid video helper' do |project_name|
|
|
214
|
+
subject(:helper) { read_generated(project_name, 'helpers/video_helper.rb') }
|
|
215
|
+
|
|
216
|
+
it 'has frozen_string_literal' do
|
|
217
|
+
expect(helper).to have_frozen_string_literal
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'has valid Ruby syntax' do
|
|
221
|
+
expect(helper).to have_valid_ruby_syntax
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'defines VideoHelper with enabled?, recorder_for and disable!' do
|
|
225
|
+
expect(helper).to match(/module VideoHelper/)
|
|
226
|
+
expect(helper).to match(/def enabled\?/)
|
|
227
|
+
expect(helper).to match(/def recorder_for\(/)
|
|
228
|
+
expect(helper).to match(/def disable!\(/)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
it 'has no ScreenCaptureRecorder and no CdpRecorder' do
|
|
232
|
+
expect(helper).not_to include('ScreenCaptureRecorder')
|
|
233
|
+
expect(helper).not_to include('CdpRecorder')
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
shared_examples 'web video helper' do |project_name|
|
|
238
|
+
subject(:helper) { read_generated(project_name, 'helpers/video_helper.rb') }
|
|
239
|
+
|
|
240
|
+
it 'defines a FrameRecorder and a Selenium FrameListener' do
|
|
241
|
+
expect(helper).to include('class FrameRecorder')
|
|
242
|
+
expect(helper).to include('class FrameListener < Selenium::WebDriver::Support::AbstractEventListener')
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'captures only after navigate, click and change_value_of' do
|
|
246
|
+
hooks = helper.scan(/def ((?:after|before)_\w+)/).flatten
|
|
247
|
+
expect(hooks).to contain_exactly('after_navigate_to', 'after_click', 'after_change_value_of')
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it 'encodes WebM with libvpx' do
|
|
251
|
+
expect(helper).to include("'libvpx'", 'video.webm')
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'loads ffmpeg lazily from the ruby_raider gem' do
|
|
255
|
+
expect(helper).to include("require 'ruby_raider/ffmpeg'")
|
|
256
|
+
expect(helper).not_to match(/^require 'ruby_raider/)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'has no AppiumRecorder' do
|
|
260
|
+
expect(helper).not_to include('AppiumRecorder')
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
shared_examples 'mobile video helper' do |project_name|
|
|
265
|
+
subject(:helper) { read_generated(project_name, 'helpers/video_helper.rb') }
|
|
266
|
+
|
|
267
|
+
it 'defines an AppiumRecorder writing mp4' do
|
|
268
|
+
expect(helper).to include('class AppiumRecorder', 'start_recording_screen', 'video.mp4')
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it 'has no FrameRecorder or listener' do
|
|
272
|
+
expect(helper).not_to include('FrameRecorder')
|
|
273
|
+
expect(helper).not_to include('AbstractEventListener')
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
it 'does not provision ffmpeg on this machine (Appium records on its own host)' do
|
|
277
|
+
expect(helper).not_to include('ruby_raider/ffmpeg', 'Ffmpeg.ensure!', 'ffmpeg_path')
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
shared_examples 'video listener wiring' do |project_name, helper_file|
|
|
282
|
+
subject(:helper) { read_generated(project_name, "helpers/#{helper_file}") }
|
|
283
|
+
|
|
284
|
+
it 'requires video_helper' do
|
|
285
|
+
expect(helper).to include("require_relative 'video_helper'")
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it 'passes VideoHelper.listener only when it is not nil' do
|
|
289
|
+
expect(helper).to include('VideoHelper.listener')
|
|
290
|
+
expect(helper).to match(/\[:listener\] = listener if listener/)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
context 'with rspec and selenium video' do
|
|
295
|
+
name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
296
|
+
it_behaves_like 'valid video helper', name
|
|
297
|
+
it_behaves_like 'web video helper', name
|
|
298
|
+
it_behaves_like 'video listener wiring', name, 'driver_helper.rb'
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
context 'with cucumber and watir video' do
|
|
302
|
+
name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::WATIR}"
|
|
303
|
+
it_behaves_like 'valid video helper', name
|
|
304
|
+
it_behaves_like 'web video helper', name
|
|
305
|
+
it_behaves_like 'video listener wiring', name, 'browser_helper.rb'
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
context 'with rspec and ios video' do
|
|
309
|
+
name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
|
|
310
|
+
it_behaves_like 'valid video helper', name
|
|
311
|
+
it_behaves_like 'mobile video helper', name
|
|
312
|
+
|
|
313
|
+
it 'does not wire a listener into the appium driver helper' do
|
|
314
|
+
expect(read_generated(name, 'helpers/driver_helper.rb')).not_to include('listener')
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
174
318
|
# --- RSpec + automation contexts ---
|
|
175
319
|
|
|
176
320
|
context 'with rspec and selenium' do
|
|
@@ -211,6 +355,8 @@ describe 'Helper file content' do
|
|
|
211
355
|
'watir' => 'browser.screenshot.save(screenshot_path)'
|
|
212
356
|
}.fetch(automation, 'driver.screenshot(screenshot_path)')
|
|
213
357
|
it_behaves_like 'raider reporter spec helper', name, screenshot_call
|
|
358
|
+
it_behaves_like 'video spec helper wiring', name, automation == 'watir' ? 'browser' : 'driver',
|
|
359
|
+
%w[selenium watir].include?(automation) ? ':webm' : ':mp4'
|
|
214
360
|
end
|
|
215
361
|
|
|
216
362
|
context "with cucumber and #{automation}" do
|
|
@@ -3,6 +3,57 @@
|
|
|
3
3
|
require_relative 'content_helper'
|
|
4
4
|
|
|
5
5
|
describe 'Generated Readme content' do
|
|
6
|
+
FRAMEWORKS.product(AUTOMATION_TYPES).each do |framework, automation|
|
|
7
|
+
context "with #{framework} and #{automation}, the Video replay section" do
|
|
8
|
+
subject(:section) { readme[/^### Video replay\n.*?(?=^##)/m]&.gsub(/\s+/, ' ') }
|
|
9
|
+
|
|
10
|
+
let(:readme) { read_generated("#{framework}_#{automation}", 'Readme.md') }
|
|
11
|
+
let(:web) { %w[selenium watir].include?(automation) }
|
|
12
|
+
|
|
13
|
+
it 'exists and covers the defaults, VIDEO=off, the install commands and the cache' do
|
|
14
|
+
expect(section).not_to be_nil
|
|
15
|
+
expect(section).to include('only for failing tests', 'VIDEO=off', 'raider u video install',
|
|
16
|
+
'raider u video status', '~/.cache/raider', 'RAIDER_FFMPEG_PATH',
|
|
17
|
+
'RAIDER_CACHE_DIR', 'raider-results/attachments/')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'says that video needs RAIDER_FFMPEG_PATH until the ffmpeg release is published' do
|
|
21
|
+
expect(section).to include('ffmpeg-7.1', 'not published yet')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'names the right video format and how to turn video off for this project' do
|
|
25
|
+
if web
|
|
26
|
+
expect(section).to include('video.webm', 'raider u video off', 'raider u video on', 'video:')
|
|
27
|
+
elsif %w[android ios].include?(automation)
|
|
28
|
+
expect(section).to include('video.mp4', 'raider u video off', 'creates `config/config.yml`')
|
|
29
|
+
expect(section).not_to include('no `config/config.yml`')
|
|
30
|
+
else
|
|
31
|
+
expect(section).to include('video.mp4', 'raider u video off')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'says where mobile recording needs ffmpeg and how long it records' do
|
|
36
|
+
limits = { 'ios' => ['1800 s'], 'android' => ['180 s'], 'cross_platform' => ['1800 s', '180 s'] }
|
|
37
|
+
if web
|
|
38
|
+
expect(section).not_to include('Appium server host')
|
|
39
|
+
else
|
|
40
|
+
expect(section).to include('`ffmpeg` on the Appium server host', 'iOS', 'longer than 180 s',
|
|
41
|
+
'time_limit', *limits.fetch(automation))
|
|
42
|
+
expect(section).not_to include('do not use ffmpeg')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'lists the known limits of web recording' do
|
|
47
|
+
if web
|
|
48
|
+
expect(section).to include('after each page load, click and text change', 'next frame',
|
|
49
|
+
'one recorded driver per process')
|
|
50
|
+
else
|
|
51
|
+
expect(section).not_to include('next frame')
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
6
57
|
AUTOMATION_TYPES.each do |automation|
|
|
7
58
|
context "with rspec and #{automation}" do
|
|
8
59
|
subject(:readme) { read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'Readme.md') }
|
|
@@ -18,8 +18,8 @@ describe 'RSpec projects without Allure' do
|
|
|
18
18
|
expect(File).not_to exist(File.join(project_name, 'allure-results'))
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
it 'pins raider_reporter 0.
|
|
22
|
-
expect(read_generated(project_name, 'Gemfile')).to include("gem 'raider_reporter', '~> 0.
|
|
21
|
+
it 'pins raider_reporter 0.10' do
|
|
22
|
+
expect(read_generated(project_name, 'Gemfile')).to include("gem 'raider_reporter', '~> 0.10'")
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -111,8 +111,8 @@ describe 'Test file content' do
|
|
|
111
111
|
expect(config).to include('default:')
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
-
it 'uses the pretty
|
|
115
|
-
expect(config.strip).to eq('default: --format pretty --publish-quiet --retry 0')
|
|
114
|
+
it 'uses the pretty and rerun formatters, with the reporter activated from env.rb' do
|
|
115
|
+
expect(config.strip).to eq('default: --format pretty --format rerun --out rerun.txt --publish-quiet --retry 0')
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
|
|
@@ -147,5 +147,4 @@ describe 'Test file content' do
|
|
|
147
147
|
include_examples 'valid cucumber world', name
|
|
148
148
|
include_examples 'valid cucumber config', name
|
|
149
149
|
end
|
|
150
|
-
|
|
151
150
|
end
|
|
@@ -242,7 +242,6 @@ describe 'End-to-End Feature Validation' do
|
|
|
242
242
|
end
|
|
243
243
|
end
|
|
244
244
|
end
|
|
245
|
-
|
|
246
245
|
end
|
|
247
246
|
|
|
248
247
|
# --- Feature: GitHub Actions CI ---
|
|
@@ -264,5 +263,4 @@ describe 'End-to-End Feature Validation' do
|
|
|
264
263
|
expect(content).to include('rspec')
|
|
265
264
|
end
|
|
266
265
|
end
|
|
267
|
-
|
|
268
266
|
end
|