ruby_raider 3.12.0 → 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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +2 -0
- data/lib/commands/utility_commands.rb +16 -0
- data/lib/generators/automation/templates/page.tt +8 -3
- data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -3
- data/lib/generators/automation/templates/partials/url_methods.tt +3 -2
- data/lib/generators/automation/templates/partials/visit_method.tt +5 -5
- data/lib/generators/cucumber/cucumber_generator.rb +1 -1
- data/lib/generators/cucumber/templates/env.tt +3 -3
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +65 -1
- data/lib/generators/cucumber/templates/steps.tt +2 -2
- data/lib/generators/cucumber/templates/user_factory.tt +2 -0
- data/lib/generators/cucumber/templates/world.tt +5 -3
- data/lib/generators/generator.rb +25 -12
- data/lib/generators/helper_generator.rb +7 -0
- data/lib/generators/rspec/templates/accessibility_spec.tt +10 -10
- data/lib/generators/rspec/templates/spec.tt +2 -1
- data/lib/generators/templates/common/gemfile.tt +9 -9
- data/lib/generators/templates/common/partials/web_config.tt +7 -0
- data/lib/generators/templates/common/read_me.tt +45 -0
- data/lib/generators/templates/helpers/appium_helper.tt +17 -15
- data/lib/generators/templates/helpers/browser_helper.tt +1 -2
- data/lib/generators/templates/helpers/driver_helper.tt +22 -0
- data/lib/generators/templates/helpers/partials/appium_driver.tt +1 -1
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +11 -1
- data/lib/generators/templates/helpers/raider_element.tt +4 -4
- data/lib/generators/templates/helpers/session_state.tt +1 -1
- data/lib/generators/templates/helpers/spec_helper.tt +20 -1
- data/lib/generators/templates/helpers/trace_helper.tt +1097 -0
- data/lib/generators/templates/helpers/wait_helper.tt +145 -4
- data/lib/ruby_raider.rb +9 -2
- data/lib/utilities/utilities.rb +7 -0
- data/lib/version +1 -1
- data/ruby_raider.gemspec +1 -0
- data/sig/commands/utility_commands.rbs +2 -0
- data/sig/generators/generator.rbs +6 -0
- data/sig/generators/helper_generator.rbs +1 -0
- data/sig/ruby_raider.rbs +1 -0
- data/sig/utilities/utilities.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +25 -1
- data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +1 -1
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +2 -4
- data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +2 -4
- data/spec/generators/fixtures/video_helper_stubs/selenium-webdriver.rb +11 -2
- data/spec/generators/trace_helper_spec.rb +945 -0
- data/spec/integration/content/config_content_spec.rb +20 -0
- data/spec/integration/content/content_helper.rb +1 -5
- data/spec/integration/content/cucumber_reporter_content_spec.rb +51 -0
- data/spec/integration/content/gemfile_content_spec.rb +2 -2
- data/spec/integration/content/helper_content_spec.rb +60 -1
- data/spec/integration/content/readme_content_spec.rb +46 -0
- data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
- data/spec/integration/content/rubocop_content_spec.rb +87 -0
- data/spec/integration/content/syntax_validation_spec.rb +31 -51
- data/spec/integration/content/test_content_spec.rb +5 -2
- data/spec/integration/end_to_end_spec.rb +415 -29
- data/spec/integration/frozen_string_literal_matcher.rb +13 -0
- data/spec/integration/generators/helpers_generator_spec.rb +26 -0
- data/spec/integration/generators/invoke_generators_spec.rb +60 -0
- data/spec/integration/helpers/wait_helper_spec.rb +183 -0
- data/spec/integration/scaffolding_e2e_spec.rb +1 -5
- data/spec/integration/trace_format_checker.rb +141 -0
- data/spec/integration/trace_page_server.rb +80 -0
- data/spec/utilities/trace_command_spec.rb +66 -0
- metadata +29 -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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../spec_helper'
|
|
4
|
+
require_relative '../frozen_string_literal_matcher'
|
|
4
5
|
|
|
5
6
|
module ContentHelper
|
|
6
7
|
def read_generated(project, relative_path)
|
|
@@ -12,11 +13,6 @@ module ContentHelper
|
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
|
|
15
|
-
RSpec::Matchers.define :have_frozen_string_literal do
|
|
16
|
-
match { |content| content.include?('# frozen_string_literal: true') }
|
|
17
|
-
failure_message { 'expected file to contain frozen_string_literal magic comment' }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
16
|
RSpec::Matchers.define :have_valid_ruby_syntax do
|
|
21
17
|
match do |content|
|
|
22
18
|
RubyVM::InstructionSequence.compile(content)
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
22
|
-
expect(read_generated(project_name, 'Gemfile')).to include("gem 'raider_reporter', '~> 0.
|
|
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
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'rubocop'
|
|
5
|
+
require 'stringio'
|
|
6
|
+
require 'tmpdir'
|
|
7
|
+
require_relative 'content_helper'
|
|
8
|
+
|
|
9
|
+
RUBOCOP_WEB_AUTOMATIONS = %w[selenium watir].freeze
|
|
10
|
+
|
|
11
|
+
# Every generated project must pass the RuboCop it ships: its own .rubocop.yml, run in-process with
|
|
12
|
+
# this gem's bundle. The project is linted from a copy outside this repo, so RuboCop resolves the
|
|
13
|
+
# target Ruby the way a user's fresh project does (no TargetRubyVersion, so its 2.7 default) instead
|
|
14
|
+
# of picking up this repo's .ruby-version or gemspec. The end-to-end spec repeats the check with the
|
|
15
|
+
# project's own resolved gems.
|
|
16
|
+
describe 'Generated projects pass their own RuboCop' do
|
|
17
|
+
def rubocop_report(project_name)
|
|
18
|
+
Dir.mktmpdir('raider_rubocop') do |tmp|
|
|
19
|
+
dir = File.join(tmp, File.basename(project_name))
|
|
20
|
+
FileUtils.cp_r(project_name, dir)
|
|
21
|
+
with_project_root(dir) { yield lint(dir), target_ruby(dir) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with_project_root(dir)
|
|
26
|
+
RuboCop::ConfigFinder.project_root = dir
|
|
27
|
+
yield
|
|
28
|
+
ensure
|
|
29
|
+
RuboCop::ConfigFinder.project_root = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def target_ruby(dir)
|
|
33
|
+
RuboCop::TargetRuby.new(RuboCop::ConfigLoader.configuration_from_file(File.join(dir, '.rubocop.yml'))).version
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def lint(dir)
|
|
37
|
+
report, exit_code = capture_rubocop_json(['--config', File.join(dir, '.rubocop.yml'), '--cache', 'false',
|
|
38
|
+
'--format', 'json', dir])
|
|
39
|
+
offenses = report.fetch('files').flat_map do |file|
|
|
40
|
+
path = file.fetch('path').delete_prefix("#{dir}/")
|
|
41
|
+
file.fetch('offenses').map do |offense|
|
|
42
|
+
"#{path}:#{offense.dig('location', 'line')}: #{offense.fetch('cop_name')}: #{offense.fetch('message')}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
{ offenses: offenses, inspected: report.fetch('summary').fetch('inspected_file_count'), exit_code: exit_code }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def capture_rubocop_json(args)
|
|
49
|
+
original = $stdout
|
|
50
|
+
$stdout = StringIO.new
|
|
51
|
+
exit_code = RuboCop::CLI.new.run(args)
|
|
52
|
+
[JSON.parse($stdout.string), exit_code]
|
|
53
|
+
ensure
|
|
54
|
+
$stdout = original
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def expect_no_offenses(project_name)
|
|
58
|
+
expect(File).to exist(File.join(project_name, '.rubocop.yml'))
|
|
59
|
+
rubocop_report(project_name) do |summary, target|
|
|
60
|
+
offenses = summary.fetch(:offenses)
|
|
61
|
+
expect(target).to eq(RuboCop::TargetRuby::DEFAULT_VERSION)
|
|
62
|
+
expect(summary.fetch(:inspected)).to be_positive, "#{project_name}: RuboCop inspected no files"
|
|
63
|
+
expect(offenses).to be_empty, "#{project_name} has #{offenses.size} RuboCop offenses:\n#{offenses.join("\n")}"
|
|
64
|
+
expect(summary.fetch(:exit_code)).to eq(0)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
FRAMEWORKS.each do |framework|
|
|
69
|
+
AUTOMATION_TYPES.each do |automation|
|
|
70
|
+
{ 'with examples' => true, 'without examples' => false }.each do |label, examples|
|
|
71
|
+
it "has no offenses for #{framework} and #{automation} (#{label})" do
|
|
72
|
+
expect_no_offenses(ensure_project(framework:, automation:, examples:))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
RUBOCOP_WEB_AUTOMATIONS.each do |automation|
|
|
78
|
+
it "has no offenses for #{framework} and #{automation} with accessibility" do
|
|
79
|
+
name = "rubocop_axe_#{framework}_#{automation}"
|
|
80
|
+
InvokeGenerators.generate_framework(automation:, framework:, name:, accessibility: true)
|
|
81
|
+
expect_no_offenses(name)
|
|
82
|
+
ensure
|
|
83
|
+
FileUtils.rm_rf(name)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -2,66 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'content_helper'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
SYNTAX_ALL_FRAMEWORKS = %w[cucumber rspec].freeze
|
|
5
|
+
SYNTAX_MOBILE_AUTOMATIONS = %w[android ios cross_platform].freeze
|
|
7
6
|
|
|
7
|
+
# Projects are generated by spec_helper's before(:all), so the file list is read inside the
|
|
8
|
+
# examples (after generation), never when this file loads.
|
|
8
9
|
describe 'Ruby syntax validation' do
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
project_name = "#{framework}_#{automation}"
|
|
13
|
-
|
|
14
|
-
Dir.glob("#{project_name}/**/*.rb").each do |file|
|
|
15
|
-
relative = file.sub("#{project_name}/", '')
|
|
16
|
-
|
|
17
|
-
it "#{relative} has valid Ruby syntax" do
|
|
18
|
-
content = File.read(file)
|
|
19
|
-
expect(content).to have_valid_ruby_syntax
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it "#{relative} has frozen_string_literal" do
|
|
23
|
-
content = File.read(file)
|
|
24
|
-
expect(content).to have_frozen_string_literal
|
|
25
|
-
end
|
|
26
|
-
end
|
|
10
|
+
def generated_ruby_files(project_name)
|
|
11
|
+
Dir.glob('**/*.rb', base: project_name).sort
|
|
12
|
+
end
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# The glob above runs at load time, before the projects exist, so new helpers are listed here.
|
|
35
|
-
login_reuse_files += %w[helpers/wait_helper.rb helpers/raider_element.rb] if automation == 'selenium'
|
|
14
|
+
def syntax_error_for(content)
|
|
15
|
+
RubyVM::InstructionSequence.compile(content)
|
|
16
|
+
nil
|
|
17
|
+
rescue SyntaxError => e
|
|
18
|
+
e.message
|
|
19
|
+
end
|
|
36
20
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
FRAMEWORKS.each do |framework|
|
|
22
|
+
AUTOMATION_TYPES.each do |automation|
|
|
23
|
+
{ 'with examples' => true, 'without examples' => false }.each do |label, examples|
|
|
24
|
+
it "generates only valid Ruby with frozen_string_literal for #{framework} and #{automation} (#{label})" do
|
|
25
|
+
project_name = ensure_project(framework:, automation:, examples:)
|
|
26
|
+
files = generated_ruby_files(project_name)
|
|
27
|
+
|
|
28
|
+
expect(files).not_to be_empty
|
|
29
|
+
aggregate_failures do
|
|
30
|
+
files.each do |relative|
|
|
31
|
+
content = File.read(File.join(project_name, relative))
|
|
32
|
+
expect(syntax_error_for(content)).to be_nil, "#{project_name}/#{relative} has a syntax error"
|
|
33
|
+
expect(content).to have_frozen_string_literal, "#{project_name}/#{relative} lacks frozen_string_literal"
|
|
34
|
+
end
|
|
42
35
|
end
|
|
43
36
|
end
|
|
44
37
|
end
|
|
45
38
|
end
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
%w[helpers/wait_helper.rb helpers/raider_element.rb].each do |relative|
|
|
53
|
-
it "#{relative} is generated as valid Ruby with frozen_string_literal" do
|
|
54
|
-
content = File.read(File.join(project_name, relative))
|
|
55
|
-
expect(content).to have_valid_ruby_syntax
|
|
56
|
-
expect(content).to have_frozen_string_literal
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
it 'helpers/wait_helper.rb has the mobile gestures and no web-only methods' do
|
|
61
|
-
content = File.read(File.join(project_name, 'helpers/wait_helper.rb'))
|
|
62
|
-
expect(content).to include("'mobile: swipeGesture'").and include('def scroll_to(')
|
|
63
|
-
expect(content).not_to include('def drag_and_drop(')
|
|
64
|
-
end
|
|
40
|
+
SYNTAX_MOBILE_AUTOMATIONS.each do |automation|
|
|
41
|
+
it "#{framework}_#{automation} wait_helper has the mobile gestures and no web-only methods" do
|
|
42
|
+
content = File.read(File.join(ensure_project(framework:, automation:), 'helpers/wait_helper.rb'))
|
|
43
|
+
expect(content).to include("'mobile: swipeGesture'").and include('def scroll_to(')
|
|
44
|
+
expect(content).not_to include('def drag_and_drop(')
|
|
65
45
|
end
|
|
66
46
|
end
|
|
67
47
|
end
|
|
@@ -212,10 +212,13 @@ describe 'Example tests on the wait helper' do
|
|
|
212
212
|
end
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
-
# The Watir output must stay as master generated it (1274a66); the snapshots in
|
|
215
|
+
# The Watir output must stay as master generated it (1274a66), plus the 3.12.1 lint fixes; the snapshots in
|
|
216
216
|
# spec/fixtures/watir_master were generated from that commit's lib.
|
|
217
217
|
# To regenerate: `git archive 1274a66 lib` into a scratch dir, generate rspec and cucumber Watir projects there,
|
|
218
|
-
# then apply only the Readme deprecation-line delta (the `> **Deprecated:**` line under the heading block)
|
|
218
|
+
# then apply only the Readme deprecation-line delta (the `> **Deprecated:**` line under the heading block) and
|
|
219
|
+
# the 3.12.1 changes: RuboCop lint fixes (page.rb loses its extra blank lines, login_steps.rb ends with a newline)
|
|
220
|
+
# plus the frozen_string_literal comments the syntax spec requires (Style/FrozenStringLiteralComment is disabled
|
|
221
|
+
# in the generated .rubocop.yml, so RuboCop does not ask for them) on the Watir page, world and user_factory files.
|
|
219
222
|
describe 'Watir example output' do
|
|
220
223
|
snapshots = File.expand_path('../../fixtures/watir_master', __dir__)
|
|
221
224
|
|