ruby_raider 3.12.1 → 3.13.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/lib/commands/utility_commands.rb +16 -0
  4. data/lib/generators/cucumber/templates/partials/selenium_env.tt +65 -1
  5. data/lib/generators/generator.rb +10 -1
  6. data/lib/generators/helper_generator.rb +7 -0
  7. data/lib/generators/templates/common/gemfile.tt +1 -1
  8. data/lib/generators/templates/common/partials/web_config.tt +7 -0
  9. data/lib/generators/templates/common/read_me.tt +45 -0
  10. data/lib/generators/templates/helpers/driver_helper.tt +22 -0
  11. data/lib/generators/templates/helpers/partials/selenium_driver.tt +11 -1
  12. data/lib/generators/templates/helpers/raider_element.tt +4 -4
  13. data/lib/generators/templates/helpers/spec_helper.tt +19 -0
  14. data/lib/generators/templates/helpers/trace_helper.tt +1097 -0
  15. data/lib/generators/templates/helpers/wait_helper.tt +145 -4
  16. data/lib/utilities/utilities.rb +7 -0
  17. data/lib/version +1 -1
  18. data/ruby_raider.gemspec +1 -0
  19. data/sig/commands/utility_commands.rbs +2 -0
  20. data/sig/generators/generator.rbs +4 -0
  21. data/sig/generators/helper_generator.rbs +1 -0
  22. data/sig/utilities/utilities.rbs +1 -0
  23. data/spec/generators/fixtures/video_helper_stubs/selenium-webdriver.rb +11 -2
  24. data/spec/generators/trace_helper_spec.rb +945 -0
  25. data/spec/integration/content/config_content_spec.rb +20 -0
  26. data/spec/integration/content/cucumber_reporter_content_spec.rb +51 -0
  27. data/spec/integration/content/gemfile_content_spec.rb +2 -2
  28. data/spec/integration/content/helper_content_spec.rb +60 -1
  29. data/spec/integration/content/readme_content_spec.rb +46 -0
  30. data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
  31. data/spec/integration/end_to_end_spec.rb +392 -29
  32. data/spec/integration/generators/helpers_generator_spec.rb +26 -0
  33. data/spec/integration/helpers/wait_helper_spec.rb +183 -0
  34. data/spec/integration/trace_format_checker.rb +141 -0
  35. data/spec/integration/trace_page_server.rb +80 -0
  36. data/spec/utilities/trace_command_spec.rb +66 -0
  37. metadata +27 -2
@@ -59,6 +59,22 @@ describe 'Config file content' do
59
59
  end
60
60
  end
61
61
 
62
+ shared_examples 'trace config' do |project_name|
63
+ it 'has tracing enabled with 200 steps and 20 MB, with the spec comments' do
64
+ raw = read_generated(project_name, 'config/config.yml')
65
+ config = YAML.safe_load(raw, permitted_classes: [Symbol])
66
+ expect(config['trace']).to eq('enabled' => true, 'max_steps' => 200, 'max_mb' => 20)
67
+ expect(raw.index('trace:')).to be > raw.index('video:')
68
+ expect(raw.index('trace:')).to be < raw.index('login_state:')
69
+ end
70
+ end
71
+
72
+ shared_examples 'no trace config' do |project_name|
73
+ it 'has no trace block' do
74
+ expect(read_generated(project_name, 'config/config.yml')).not_to include('trace')
75
+ end
76
+ end
77
+
62
78
  shared_examples 'login state config' do |project_name|
63
79
  it 'has a login_state max_age of an hour' do
64
80
  config = YAML.safe_load(read_generated(project_name, 'config/config.yml'), permitted_classes: [Symbol])
@@ -73,6 +89,7 @@ describe 'Config file content' do
73
89
  include_examples 'valid web config', name
74
90
  include_examples 'selenium config with timeout', name
75
91
  include_examples 'video config', name
92
+ include_examples 'trace config', name
76
93
  include_examples 'login state config', name
77
94
  end
78
95
 
@@ -80,6 +97,7 @@ describe 'Config file content' do
80
97
  name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::WATIR}"
81
98
  include_examples 'valid web config', name
82
99
  include_examples 'config without driver_options', name
100
+ include_examples 'no trace config', name
83
101
  include_examples 'login state config', name
84
102
  end
85
103
 
@@ -87,6 +105,7 @@ describe 'Config file content' do
87
105
  name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
88
106
  include_examples 'valid web config', name
89
107
  include_examples 'selenium config with timeout', name
108
+ include_examples 'trace config', name
90
109
  include_examples 'login state config', name
91
110
  end
92
111
 
@@ -95,6 +114,7 @@ describe 'Config file content' do
95
114
  # Single-platform Appium projects have no config.yml; VideoHelper falls back to its defaults.
96
115
  context 'with rspec and cross platform' do
97
116
  include_examples 'video config', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
117
+ include_examples 'no trace config', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
98
118
  end
99
119
 
100
120
  context 'with rspec and appium android' do
@@ -68,6 +68,57 @@ describe 'Cucumber raider_reporter wiring' do
68
68
  end
69
69
  end
70
70
 
71
+ if automation == 'selenium'
72
+ context 'with trace recording' do
73
+ it 'requires trace_helper' do
74
+ expect(env).to include("require_relative '../../helpers/trace_helper'")
75
+ end
76
+
77
+ it 'counts attempts per scenario location and starts the trace recorder after the video recorder' do
78
+ before_hook = env[/^Before do.*?^end$/m]
79
+ expect(env).to match(/^TRACE_ATTEMPTS = Hash\.new\(0\) unless defined\?\(TRACE_ATTEMPTS\)$/)
80
+ expect(before_hook).to include('TRACE_ATTEMPTS[scenario.location.to_s] += 1', '@trace_recorder = TraceHelper.recorder_for(driver)')
81
+ expect(before_hook).to match(/^Before do \|scenario\|$/)
82
+ expect(before_hook.index('@video_recorder.start')).to be < before_hook.index('@trace_recorder = TraceHelper')
83
+ expect(before_hook).to match(/if defined\?\(TraceHelper\)\n(.*\n)*.*@trace_recorder = TraceHelper/)
84
+ end
85
+
86
+ it 'stops with keep: TraceHelper.keep_all? || scenario.failed? before the video, attaches it and cleans up before quitting' do
87
+ expect(hook).to include('TraceHelper.keep_all? || scenario.failed?', 'error: scenario.exception', 'attempt: @trace_attempt')
88
+ expect(hook).to include("RaiderReporter.attach('trace', trace_path, type: :trace, attempt: @trace_attempt) if trace_path")
89
+ order = [hook.index('@trace_recorder&.stop'), hook.index('@video_recorder&.stop'), hook.index("RaiderReporter.attach('trace'"),
90
+ hook.index(/^ensure/), hook.index('@trace_recorder&.cleanup'), hook.index('@video_recorder&.cleanup'), hook.index('driver.quit')]
91
+ expect(order).to all(be_an(Integer)).and eq(order.compact.sort)
92
+ end
93
+
94
+ it 'survives a Before that raised before the trace init, and a failing plugin setup or handler' do
95
+ expect(hook).to include('@trace_test&.merge(')
96
+ expect(hook).not_to include('@trace_test.merge(')
97
+ plugin = env[/^InstallPlugin do.*?^end$/m]
98
+ expect(plugin.scan('rescue StandardError => e').size).to eq(3)
99
+ expect(plugin.index('AstLookup.new')).to be > plugin.index("begin\n")
100
+ expect(plugin).to include('TraceHelper.warn_once("Gherkin step groups unavailable')
101
+ end
102
+
103
+ it 'turns tracing off with TraceHelper.disable! when a step group handler raises' do
104
+ plugin = env[/^InstallPlugin do.*?^end$/m]
105
+ expect(plugin.scan(/TraceHelper\.disable!\("step groups failed: \#\{e\.message\}"\) if defined\?\(TraceHelper\)/).size).to eq(2)
106
+ expect(plugin).not_to include('tracing disabled')
107
+ end
108
+
109
+ it 'records each Gherkin step as a group through the InstallPlugin step events, skipping hooks' do
110
+ plugin = env[/^InstallPlugin do.*?^end$/m]
111
+ expect(plugin).to include('config.on_event(:test_step_started)', 'config.on_event(:test_step_finished)', 'next if event.test_step.hook?')
112
+ expect(plugin).to include('TraceHelper.active_recorder&.step_started', 'TraceHelper.active_recorder&.step_finished')
113
+ expect(plugin).to include('defined?(TraceHelper)')
114
+ end
115
+ end
116
+ else
117
+ it 'has no trace wiring' do
118
+ expect(env).not_to match(/trace/i)
119
+ end
120
+ end
121
+
71
122
  it 'does not generate the allure helper' do
72
123
  expect(File).not_to exist(File.join(project_name, 'helpers/allure_helper.rb'))
73
124
  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.10'")
130
+ expect(gemfile).to include("gem 'raider_reporter', '~> 0.12'")
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.10'")
146
+ expect(gemfile).to include("gem 'raider_reporter', '~> 0.12'")
147
147
  end
148
148
 
149
149
  it 'drops allure-cucumber' do
@@ -43,6 +43,21 @@ describe 'Helper file content' do
43
43
  it 'does not reference Capybara' do
44
44
  expect(helper).not_to include('Capybara')
45
45
  end
46
+
47
+ it 'requires trace_helper' do
48
+ expect(helper).to include("require_relative 'trace_helper'")
49
+ end
50
+
51
+ it 'sets web_socket_url only for chrome, edge and firefox and not with TRACE=off' do
52
+ expect(helper).to include('options.web_socket_url = true if defined?(TraceHelper) && TraceHelper.bidi?(load_browser)')
53
+ end
54
+
55
+ it 'fans the video and trace listeners out through one listener, each guarded by defined?' do
56
+ expect(helper).to include('class FanOutListener < Selenium::WebDriver::Support::AbstractEventListener',
57
+ 'listener = FanOutListener.for(listeners)',
58
+ 'listeners << VideoHelper.listener if defined?(VideoHelper)',
59
+ 'listeners << TraceHelper.listener if defined?(TraceHelper)')
60
+ end
46
61
  end
47
62
 
48
63
  # --- Browser helper ---
@@ -168,7 +183,7 @@ describe 'Helper file content' do
168
183
  after_hook.index(/driver\.quit|browser\.quit/)]
169
184
  expect(order).to all(be_an(Integer)).and eq(order.compact.sort)
170
185
  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/)
186
+ expect(after_hook[order[2]..]).to match(/\n (@trace_recorder&\.cleanup\n )?@video_recorder&\.cleanup\n (driver|browser)\.quit(_driver)?\n end\n/)
172
187
  end
173
188
 
174
189
  it 'keeps the screenshot attachment' do
@@ -176,6 +191,41 @@ describe 'Helper file content' do
176
191
  end
177
192
  end
178
193
 
194
+ shared_examples 'trace spec helper wiring' do |project_name|
195
+ subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') }
196
+
197
+ let(:before_hook) { helper[/config\.before\(:each\) do.*?^ end$/m] }
198
+ let(:after_hook) { helper[/config\.after\(:each\).*\z/m] }
199
+
200
+ it 'starts the trace recorder after the video recorder, each guarded by defined?(TraceHelper)' do
201
+ expect(before_hook).to include('@trace_recorder = TraceHelper.recorder_for(driver) if defined?(TraceHelper)', '@trace_recorder&.start')
202
+ expect(before_hook.index('@video_recorder.start')).to be < before_hook.index('@trace_recorder = TraceHelper')
203
+ end
204
+
205
+ it 'keeps the trace for TRACE=on or a final-attempt failure, falling back to keeping failures without a video helper' do
206
+ expect(after_hook).to include('TraceHelper.keep_all? || (example.exception && (defined?(VideoHelper) ? VideoHelper.final_attempt?(example) : true))')
207
+ end
208
+
209
+ it 'counts the attempt from the rspec-retry metadata and hands the recorder the test' do
210
+ expect(after_hook).to include('example.metadata[:retry_attempts].to_i + 1', 'error: example.exception')
211
+ expect(after_hook).to include('@trace_recorder&.stop(keep: keep_trace, test: trace_test)')
212
+ end
213
+
214
+ it 'stops the trace before the video and the screenshot, then attaches it, then cleans up before quitting' do
215
+ order = [after_hook.index('@trace_recorder&.stop'), after_hook.index('@video_recorder&.stop'), after_hook.index('RaiderReporter.attach(\'screenshot\''),
216
+ after_hook.index("RaiderReporter.attach('trace', trace_path, type: :trace, attempt: attempt) if trace_path"),
217
+ after_hook.index("\n ensure\n"), after_hook.index('@trace_recorder&.cleanup'), after_hook.index('@video_recorder&.cleanup'),
218
+ after_hook.index('driver.quit')]
219
+ expect(order).to all(be_an(Integer)).and eq(order.compact.sort)
220
+ end
221
+ end
222
+
223
+ shared_examples 'no trace wiring' do |project_name, file|
224
+ it "leaves #{file} without trace code" do
225
+ expect(read_generated(project_name, file)).not_to match(/trace/i)
226
+ end
227
+ end
228
+
179
229
  shared_examples 'selenium spec helper' do |project_name|
180
230
  subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') }
181
231
 
@@ -313,6 +363,10 @@ describe 'Helper file content' do
313
363
  it 'does not wire a listener into the appium driver helper' do
314
364
  expect(read_generated(name, 'helpers/driver_helper.rb')).not_to include('listener')
315
365
  end
366
+
367
+ it 'does not wire tracing or BiDi into the appium driver helper' do
368
+ expect(read_generated(name, 'helpers/driver_helper.rb')).not_to include('TraceHelper', 'web_socket_url')
369
+ end
316
370
  end
317
371
 
318
372
  # --- RSpec + automation contexts ---
@@ -357,6 +411,11 @@ describe 'Helper file content' do
357
411
  it_behaves_like 'raider reporter spec helper', name, screenshot_call
358
412
  it_behaves_like 'video spec helper wiring', name, automation == 'watir' ? 'browser' : 'driver',
359
413
  %w[selenium watir].include?(automation) ? ':webm' : ':mp4'
414
+ if automation == 'selenium'
415
+ it_behaves_like 'trace spec helper wiring', name
416
+ else
417
+ it_behaves_like 'no trace wiring', name, 'helpers/spec_helper.rb'
418
+ end
360
419
  end
361
420
 
362
421
  context "with cucumber and #{automation}" do
@@ -52,6 +52,52 @@ describe 'Generated Readme content' do
52
52
  end
53
53
  end
54
54
  end
55
+
56
+ context "with #{framework} and #{automation}, the Trace replay section" do
57
+ subject(:section) { readme[/^### Trace replay\n.*?(?=^##)/m]&.gsub(/\s+/, ' ') }
58
+
59
+ let(:readme) { read_generated("#{framework}_#{automation}", 'Readme.md') }
60
+
61
+ if automation == 'selenium'
62
+ it 'covers the defaults, TRACE=on, TRACE=off, the file location and how to open it' do
63
+ expect(section).to include('only for failing tests', 'TRACE=on', 'TRACE=off', 'raider u trace off',
64
+ 'raider u trace on', 'raider-results/attachments/', 'Raider Desktop', 'Hideout',
65
+ 'helpers/trace_helper.rb')
66
+ expect(section).to include('raider-results/attachments/<n>-trace.zip')
67
+ expect(section).to include('`raider u trace on` and `raider u trace off` write `enabled:`')
68
+ expect(section).not_to include('Both write')
69
+ end
70
+
71
+ it 'states the per-step cost and the caps' do
72
+ expect(section).to include('one screenshot and one page HTML', 'max_mb', 'max_steps', 'trace:')
73
+ end
74
+
75
+ it 'allows at most one [trace] notice, plus one line when tracing stops' do
76
+ expect(section).to include('at most one `[trace]` notice', 'one more `[trace]` line', 'tracing disabled for this run')
77
+ end
78
+
79
+ it 'limits network and console to Chrome, Edge or Firefox' do
80
+ expect(section).to include('Chrome, Edge or Firefox', 'Safari', 'steps, screenshots and HTML')
81
+ end
82
+
83
+ it 'says what is hidden, what is kept and what is never captured' do
84
+ expect(section).to include('password', 'RAIDER_SECRET_', 'page text', 'query strings', 'console text',
85
+ 'like the video', 'never captured', 'cookies')
86
+ expect(section).to include('Failure messages can contain typed text too, because an assertion message is kept as it is')
87
+ end
88
+
89
+ it 'says how to remove the trace helper and notes the wait helper link' do
90
+ expect(readme).to include('Removing the trace helper', 'require_relative',
91
+ 'without WaitHelper a trace records only navigation and the failure')
92
+ expect(readme).not_to match(/allure/i)
93
+ expect(readme).not_to include('—')
94
+ end
95
+ else
96
+ it 'does not mention trace replay' do
97
+ expect(readme).not_to include('Trace replay', 'TRACE=', 'trace_helper')
98
+ end
99
+ end
100
+ end
55
101
  end
56
102
 
57
103
  AUTOMATION_TYPES.each do |automation|
@@ -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.10' do
22
- expect(read_generated(project_name, 'Gemfile')).to include("gem 'raider_reporter', '~> 0.10'")
21
+ it 'pins raider_reporter 0.12' do
22
+ expect(read_generated(project_name, 'Gemfile')).to include("gem 'raider_reporter', '~> 0.12'")
23
23
  end
24
24
  end
25
25
  end