ruby_raider 3.4.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +12 -2
- data/RELEASE.md +18 -0
- data/lib/generators/common_generator.rb +2 -0
- data/lib/generators/cucumber/templates/cucumber.tt +1 -1
- data/lib/generators/cucumber/templates/partials/appium_env.tt +10 -5
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +9 -5
- data/lib/generators/cucumber/templates/partials/watir_env.tt +9 -5
- data/lib/generators/generator.rb +9 -0
- data/lib/generators/helper_generator.rb +2 -0
- data/lib/generators/infrastructure/azure_generator.rb +11 -0
- data/lib/generators/infrastructure/github_generator.rb +2 -2
- data/lib/generators/infrastructure/templates/azure_pipelines.tt +44 -0
- data/lib/generators/infrastructure/templates/github.tt +4 -7
- data/lib/generators/infrastructure/templates/github_appium.tt +0 -6
- data/lib/generators/invoke_generators.rb +19 -1
- data/lib/generators/menu_generator.rb +16 -6
- data/lib/generators/templates/common/gemfile.tt +2 -4
- data/lib/generators/templates/common/git_ignore.tt +3 -1
- data/lib/generators/templates/common/read_me.tt +27 -1
- data/lib/ruby_raider.rb +8 -1
- data/lib/version +1 -1
- data/sig/generators/generator.rbs +2 -0
- data/sig/generators/infrastructure/azure_generator.rbs +4 -0
- data/sig/generators/invoke_generators.rbs +5 -0
- data/sig/generators/menu_generator.rbs +2 -1
- data/sig/ruby_raider.rbs +1 -0
- data/spec/integration/content/azure_content_spec.rb +109 -0
- data/spec/integration/content/ci_content_spec.rb +24 -24
- data/spec/integration/content/common_content_spec.rb +4 -4
- data/spec/integration/content/cucumber_reporter_content_spec.rb +64 -0
- data/spec/integration/content/gemfile_content_spec.rb +7 -7
- data/spec/integration/content/helper_content_spec.rb +2 -6
- data/spec/integration/content/readme_content_spec.rb +13 -3
- data/spec/integration/content/test_content_spec.rb +4 -0
- data/spec/integration/end_to_end_spec.rb +171 -3
- data/spec/integration/generators/azure_generator_spec.rb +100 -0
- data/spec/integration/generators/helpers_generator_spec.rb +6 -3
- data/spec/menus/menu_generator_spec.rb +49 -0
- metadata +8 -2
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require_relative 'content_helper'
|
|
6
|
+
|
|
7
|
+
# UseRubyVersion@0 versionSpec '3.4' resolves against the hosted tool cache, which lists Ruby 3.4.x
|
|
8
|
+
# (3.2, 3.3, 3.4, 4.0): https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md
|
|
9
|
+
describe 'Azure Pipelines content' do
|
|
10
|
+
combos = FRAMEWORKS.product(%w[selenium watir], [false, true])
|
|
11
|
+
|
|
12
|
+
before(:all) do # rubocop:disable RSpec/BeforeAfterAll
|
|
13
|
+
combos.each do |framework, automation, axe|
|
|
14
|
+
name = "azurec_#{framework}_#{automation}#{'_axe' if axe}"
|
|
15
|
+
next if File.exist?(name)
|
|
16
|
+
|
|
17
|
+
InvokeGenerators.generate_framework(automation:, framework:, name:, accessibility: axe, ci: 'azure')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
after(:all) { FileUtils.rm_rf(Dir.glob('azurec_*')) } # rubocop:disable RSpec/BeforeAfterAll
|
|
22
|
+
|
|
23
|
+
combos.each do |framework, automation, axe|
|
|
24
|
+
context "with #{framework}, #{automation}#{', axe' if axe}" do
|
|
25
|
+
let(:name) { "azurec_#{framework}_#{automation}#{'_axe' if axe}" }
|
|
26
|
+
let(:raw) { read_generated(name, 'azure-pipelines.yml') }
|
|
27
|
+
let(:yml) { YAML.safe_load(raw) }
|
|
28
|
+
let(:run_command) do
|
|
29
|
+
if framework == 'cucumber'
|
|
30
|
+
'bundle exec cucumber features/ --format pretty'
|
|
31
|
+
else
|
|
32
|
+
'bundle exec rspec spec/ --format documentation'
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'has a tests job with ruby, bundle install, run command and an always-published artifact' do
|
|
37
|
+
tests = yml['jobs'][0]
|
|
38
|
+
expect(tests['job']).to eq('tests')
|
|
39
|
+
steps = tests['steps']
|
|
40
|
+
expect(steps.first).to eq('task' => 'UseRubyVersion@0', 'inputs' => { 'versionSpec' => '3.4' })
|
|
41
|
+
expect(steps.map { |s| s['script'] }).to include('bundle install', run_command)
|
|
42
|
+
publish = steps.find { |s| s['task'] == 'PublishPipelineArtifact@1' }
|
|
43
|
+
expect(publish['condition']).to eq('always()')
|
|
44
|
+
expect(publish['inputs']).to eq(
|
|
45
|
+
'targetPath' => 'raider-results',
|
|
46
|
+
'artifact' => 'raider-results-$(System.JobPositionInPhase)-$(System.JobAttempt)'
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'passes exactly the Raider settings to the test step' do
|
|
51
|
+
step = yml['jobs'][0]['steps'].find { |s| s['script'] == run_command }
|
|
52
|
+
expect(step['env']).to eq('RAIDER_TOKEN' => '$(RAIDER_TOKEN)', 'RAIDER_ENDPOINT' => '$(RAIDER_ENDPOINT)')
|
|
53
|
+
expect(yml['variables']['HEADLESS']).to eq('true')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'has a raider_comment job for pull requests' do
|
|
57
|
+
comment = yml['jobs'][1]
|
|
58
|
+
expect(comment['job']).to eq('raider_comment')
|
|
59
|
+
expect(comment['dependsOn']).to eq('tests')
|
|
60
|
+
expect(comment['condition']).to eq("and(always(), eq(variables['Build.Reason'], 'PullRequest'))")
|
|
61
|
+
last = comment['steps'].last
|
|
62
|
+
expect(last['script']).to eq('bundle exec raider-reporter comment')
|
|
63
|
+
expect(last['env']).to include('SYSTEM_ACCESSTOKEN' => '$(System.AccessToken)')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'has triggers, pool and the self-hosted hint' do
|
|
67
|
+
expect(yml['trigger']['branches']['include']).to include('main', 'master')
|
|
68
|
+
expect(yml['pr']['branches']['include']).to include('*')
|
|
69
|
+
expect(yml['pool']).to eq('vmImage' => 'ubuntu-latest')
|
|
70
|
+
expect(raw).to include('# Self-hosted agent? Replace vmImage with: pool: { name: Default }')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'explains that Azure Repos ignores pr: directly above the pr block' do
|
|
74
|
+
expect(raw).to include('# Azure Repos ignores pr: — add this pipeline as a Build validation branch policy ' \
|
|
75
|
+
"so pull request builds (and the Raider comment) run.\npr:")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'has no Allure' do
|
|
79
|
+
expect(raw).not_to match(/allure/i)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'documents Azure Pipelines in the README and no GitHub Actions setup' do
|
|
83
|
+
readme = read_generated(name, 'Readme.md')
|
|
84
|
+
expect(readme).to include('## Azure Pipelines', 'RAIDER_TOKEN', 'Contribute to pull requests',
|
|
85
|
+
'pool: { name: Default }', 'Build validation branch policy',
|
|
86
|
+
'same stage')
|
|
87
|
+
expect(readme).not_to match(/GitHub Actions/i)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
context 'with a mobile project and ci azure' do
|
|
93
|
+
let(:readme) { read_generated('azurec_mobile', 'Readme.md') }
|
|
94
|
+
|
|
95
|
+
before(:all) do # rubocop:disable RSpec/BeforeAfterAll
|
|
96
|
+
InvokeGenerators.generate_framework(automation: 'ios', framework: 'rspec', name: 'azurec_mobile', ci: 'azure')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'says the template is not available and does not mention azure-pipelines.yml' do
|
|
100
|
+
expect(readme).to include('## CI', 'Azure Pipelines template for mobile projects is not available yet. ' \
|
|
101
|
+
'Use GitHub Actions (`ci:github`)')
|
|
102
|
+
expect(readme).not_to include('azure-pipelines.yml', '## Azure Pipelines')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'does not generate azure-pipelines.yml for default projects' do
|
|
107
|
+
expect(File).not_to exist("#{project('rspec', 'selenium')}/azure-pipelines.yml")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -62,13 +62,13 @@ describe 'CI pipeline content' do
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
shared_examples 'github raider wiring' do |project_name, file|
|
|
65
|
+
shared_examples 'github raider wiring' do |project_name, file, runner = 'rspec'|
|
|
66
66
|
subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") }
|
|
67
67
|
|
|
68
68
|
let(:steps) { YAML.safe_load(pipeline)['jobs'].values.flat_map { |job| job['steps'] } }
|
|
69
69
|
|
|
70
70
|
let(:test_steps) do
|
|
71
|
-
steps.select { |step| step['run'].to_s.include?(
|
|
71
|
+
steps.select { |step| step['run'].to_s.include?(runner) || step['with'].to_h['script'].to_s.include?(runner) }
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
it 'passes Raider settings to every test step' do
|
|
@@ -128,29 +128,19 @@ describe 'CI pipeline content' do
|
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
-
shared_examples 'github
|
|
132
|
-
subject(:pipeline) { read_generated(project_name,
|
|
133
|
-
|
|
134
|
-
it 'has no raider-comment job' do
|
|
135
|
-
expect(pipeline).not_to include('raider-comment')
|
|
136
|
-
expect(pipeline).not_to include('raider-reporter')
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
shared_examples 'github without raider wiring' do |project_name, file|
|
|
141
|
-
subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") }
|
|
131
|
+
shared_examples 'github allure steps' do |project_name|
|
|
132
|
+
subject(:pipeline) { read_generated(project_name, '.github/workflows/test_pipeline.yml') }
|
|
142
133
|
|
|
143
|
-
it '
|
|
144
|
-
expect(pipeline).
|
|
145
|
-
expect(pipeline).not_to include('raider-results')
|
|
134
|
+
it 'keeps the Allure steps' do
|
|
135
|
+
expect(pipeline).to include('simple-elf/allure-report-action', 'peaceiris/actions-gh-pages')
|
|
146
136
|
end
|
|
147
137
|
end
|
|
148
138
|
|
|
149
|
-
shared_examples 'github allure steps' do |project_name|
|
|
139
|
+
shared_examples 'github without allure steps' do |project_name|
|
|
150
140
|
subject(:pipeline) { read_generated(project_name, '.github/workflows/test_pipeline.yml') }
|
|
151
141
|
|
|
152
|
-
it '
|
|
153
|
-
expect(pipeline).
|
|
142
|
+
it 'has no Allure report or gh-pages steps' do
|
|
143
|
+
expect(pipeline).not_to match(/allure|gh-pages/i)
|
|
154
144
|
end
|
|
155
145
|
end
|
|
156
146
|
|
|
@@ -169,9 +159,9 @@ describe 'CI pipeline content' do
|
|
|
169
159
|
include_examples 'valid github pipeline', name
|
|
170
160
|
include_examples 'github cucumber pipeline', name
|
|
171
161
|
include_examples 'github triggers', name, 'test_pipeline.yml'
|
|
172
|
-
include_examples 'github
|
|
173
|
-
include_examples 'github
|
|
174
|
-
include_examples 'github allure steps', name
|
|
162
|
+
include_examples 'github raider wiring', name, 'test_pipeline.yml', 'cucumber'
|
|
163
|
+
include_examples 'github raider-comment job', name, 'test_pipeline.yml', %w[build]
|
|
164
|
+
include_examples 'github without allure steps', name
|
|
175
165
|
end
|
|
176
166
|
|
|
177
167
|
context 'with rspec and watir' do
|
|
@@ -184,6 +174,16 @@ describe 'CI pipeline content' do
|
|
|
184
174
|
include_examples 'github allure steps', name
|
|
185
175
|
end
|
|
186
176
|
|
|
177
|
+
context 'with cucumber and watir' do
|
|
178
|
+
name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::WATIR}"
|
|
179
|
+
include_examples 'valid github pipeline', name
|
|
180
|
+
include_examples 'github cucumber pipeline', name
|
|
181
|
+
include_examples 'github triggers', name, 'test_pipeline.yml'
|
|
182
|
+
include_examples 'github raider wiring', name, 'test_pipeline.yml', 'cucumber'
|
|
183
|
+
include_examples 'github raider-comment job', name, 'test_pipeline.yml', %w[build]
|
|
184
|
+
include_examples 'github without allure steps', name
|
|
185
|
+
end
|
|
186
|
+
|
|
187
187
|
appium_jobs = { 'android' => %w[android-tests], 'ios' => %w[ios-tests],
|
|
188
188
|
'cross_platform' => %w[ios-tests android-tests] }
|
|
189
189
|
|
|
@@ -198,8 +198,8 @@ describe 'CI pipeline content' do
|
|
|
198
198
|
context "with cucumber and #{automation}" do
|
|
199
199
|
name = "#{FrameworkIndex::CUCUMBER}_#{automation}"
|
|
200
200
|
include_examples 'github triggers', name, 'appium_pipeline.yml'
|
|
201
|
-
include_examples 'github
|
|
202
|
-
include_examples 'github
|
|
201
|
+
include_examples 'github raider wiring', name, 'appium_pipeline.yml', 'cucumber'
|
|
202
|
+
include_examples 'github raider-comment job', name, 'appium_pipeline.yml', test_jobs
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
205
|
end
|
|
@@ -122,16 +122,16 @@ describe 'Common file content' do
|
|
|
122
122
|
shared_examples 'non-rspec gitignore' do |project_name|
|
|
123
123
|
subject(:gitignore) { read_generated(project_name, '.gitignore') }
|
|
124
124
|
|
|
125
|
-
it '
|
|
126
|
-
expect(gitignore).
|
|
125
|
+
it 'does not include allure-results' do
|
|
126
|
+
expect(gitignore).not_to include('allure-results')
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
it 'does not include spec/examples.txt' do
|
|
130
130
|
expect(gitignore).not_to include('spec/examples.txt')
|
|
131
131
|
end
|
|
132
132
|
|
|
133
|
-
it '
|
|
134
|
-
expect(gitignore).
|
|
133
|
+
it 'includes raider-results' do
|
|
134
|
+
expect(gitignore).to include('raider-results')
|
|
135
135
|
end
|
|
136
136
|
end
|
|
137
137
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'content_helper'
|
|
4
|
+
|
|
5
|
+
describe 'Cucumber raider_reporter wiring' do
|
|
6
|
+
screenshot_calls = {
|
|
7
|
+
'selenium' => 'driver.save_screenshot(screenshot_path)',
|
|
8
|
+
'watir' => 'browser.screenshot.save(screenshot_path)'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
AUTOMATION_TYPES.each do |automation|
|
|
12
|
+
context "with cucumber and #{automation}" do
|
|
13
|
+
let(:project_name) { "#{FrameworkIndex::CUCUMBER}_#{automation}" }
|
|
14
|
+
let(:env) { read_generated(project_name, 'features/support/env.rb') }
|
|
15
|
+
let(:screenshot_call) { screenshot_calls.fetch(automation, 'driver.screenshot(screenshot_path)') }
|
|
16
|
+
let(:hook) { env[/^\s*After do.*\z/m] }
|
|
17
|
+
let(:quit_call) { automation == 'watir' ? 'browser.quit' : 'driver.quit' }
|
|
18
|
+
|
|
19
|
+
it 'activates the reporter by requiring raider_reporter/cucumber' do
|
|
20
|
+
expect(env).to include("require 'raider_reporter/cucumber'")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'does not reference the allure helper' do
|
|
24
|
+
expect(env).not_to match(/AllureHelper|allure_helper/)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'takes the screenshot only for failed scenarios, inside the guard block' do
|
|
28
|
+
guarded = /if scenario\.failed\?\n\s+Dir\.mktmpdir do \|temp_folder\|\n(?<body>.*?)\n\s+end\n\s+end\nensure/m
|
|
29
|
+
expect(hook.match(guarded)[:body]).to include(screenshot_call)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'uses a fixed file name in the tmpdir, never the scenario name' do
|
|
33
|
+
expect(env).to include("screenshot_path = File.join(temp_folder, 'screenshot.png')")
|
|
34
|
+
expect(hook).not_to include('scenario.name')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'quits the driver in an ensure of the After block, without a redundant begin' do
|
|
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)
|
|
40
|
+
expect(hook).not_to match(/^\s+begin$/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'attaches the screenshot path before quitting the driver' do
|
|
44
|
+
attach = "RaiderReporter.attach('screenshot', screenshot_path, type: :png)"
|
|
45
|
+
expect(env).to include(attach)
|
|
46
|
+
expect(env.index(screenshot_call)).to be < env.index(attach)
|
|
47
|
+
expect(env.index(attach)).to be < env.index(/driver\.quit|browser\.quit/, env.index(attach))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'does not generate the allure helper' do
|
|
51
|
+
expect(File).not_to exist(File.join(project_name, 'helpers/allure_helper.rb'))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'does not mention Allure in any generated file' do
|
|
55
|
+
files = Dir.glob("#{project_name}/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) }
|
|
56
|
+
expect(files.select { |f| File.read(f).match?(/allure/i) }).to be_empty
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'does not create an allure-results folder' do
|
|
60
|
+
expect(File).not_to exist(File.join(project_name, 'allure-results'))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -58,8 +58,8 @@ describe 'Gemfile content' do
|
|
|
58
58
|
expect(gemfile).to include("gem 'cucumber'")
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
it '
|
|
62
|
-
expect(gemfile).
|
|
61
|
+
it 'does not include allure-cucumber' do
|
|
62
|
+
expect(gemfile).not_to include('allure')
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
it 'includes rspec as cucumber dependency' do
|
|
@@ -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.5'")
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
it 'keeps allure gems' do
|
|
@@ -142,12 +142,12 @@ describe 'Gemfile content' do
|
|
|
142
142
|
context "with cucumber and #{automation}" do
|
|
143
143
|
let(:gemfile) { read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'Gemfile') }
|
|
144
144
|
|
|
145
|
-
it '
|
|
146
|
-
expect(gemfile).
|
|
145
|
+
it 'includes raider_reporter' do
|
|
146
|
+
expect(gemfile).to include("gem 'raider_reporter', '~> 0.5'")
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
-
it '
|
|
150
|
-
expect(gemfile).
|
|
149
|
+
it 'drops allure-cucumber' do
|
|
150
|
+
expect(gemfile).not_to include('allure')
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
it 'pins ruby_raider to the generator major.minor' do
|
|
@@ -221,13 +221,11 @@ describe 'Helper file content' do
|
|
|
221
221
|
name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
222
222
|
it_behaves_like 'valid driver helper', name
|
|
223
223
|
it_behaves_like 'selenium driver helper', name
|
|
224
|
-
it_behaves_like 'valid allure helper', name
|
|
225
224
|
end
|
|
226
225
|
|
|
227
226
|
context 'with cucumber and watir' do
|
|
228
227
|
name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::WATIR}"
|
|
229
228
|
it_behaves_like 'valid browser helper', name
|
|
230
|
-
it_behaves_like 'valid allure helper', name
|
|
231
229
|
end
|
|
232
230
|
|
|
233
231
|
# --- Mobile contexts (with AppiumRecorder) ---
|
|
@@ -255,10 +253,8 @@ describe 'Helper file content' do
|
|
|
255
253
|
end
|
|
256
254
|
|
|
257
255
|
context "with cucumber and #{automation}" do
|
|
258
|
-
it '
|
|
259
|
-
|
|
260
|
-
files = Dir.glob("#{project}/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) }
|
|
261
|
-
expect(files.select { |f| File.read(f).match?(/raider_reporter|RaiderReporter/) }).to be_empty
|
|
256
|
+
it 'does not generate the allure helper' do
|
|
257
|
+
expect(File).not_to exist("#{FrameworkIndex::CUCUMBER}_#{automation}/helpers/allure_helper.rb")
|
|
262
258
|
end
|
|
263
259
|
end
|
|
264
260
|
end
|
|
@@ -22,9 +22,19 @@ describe 'Generated Readme content' do
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
context "with cucumber and #{automation}" do
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
subject(:readme) { read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'Readme.md') }
|
|
26
|
+
|
|
27
|
+
it 'has a Reporting section' do
|
|
28
|
+
expect(readme).to include('## Reporting')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'documents local results, upload settings, plugin mode and the raider_reporter README' do
|
|
32
|
+
expect(readme).to include('raider-results/', 'RAIDER_TOKEN', 'RAIDER_ENDPOINT', 'features/support/env.rb',
|
|
33
|
+
'https://github.com/RaiderHQ/raider_reporter')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'does not mention Allure' do
|
|
37
|
+
expect(readme).not_to match(/allure/i)
|
|
28
38
|
end
|
|
29
39
|
end
|
|
30
40
|
end
|
|
@@ -110,6 +110,10 @@ describe 'Test file content' do
|
|
|
110
110
|
it 'has default profile' do
|
|
111
111
|
expect(config).to include('default:')
|
|
112
112
|
end
|
|
113
|
+
|
|
114
|
+
it 'uses the pretty formatter, with the reporter activated from env.rb' do
|
|
115
|
+
expect(config.strip).to eq('default: --format pretty --publish-quiet --retry 0')
|
|
116
|
+
end
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
# --- RSpec contexts ---
|
|
@@ -84,7 +84,7 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
84
84
|
# Helper to run Cucumber tests
|
|
85
85
|
def run_cucumber(project_name)
|
|
86
86
|
puts "\n🥒 Running Cucumber tests in #{project_name}..."
|
|
87
|
-
result = run_in_project(project_name, 'bundle exec cucumber features/ --format pretty', timeout: 120)
|
|
87
|
+
result = run_in_project(project_name, 'HEADLESS=true bundle exec cucumber features/ --format pretty', timeout: 120)
|
|
88
88
|
|
|
89
89
|
puts result[:stdout] if result[:stdout].length.positive?
|
|
90
90
|
|
|
@@ -104,10 +104,10 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
104
104
|
expect(bundle_install(project)).to be true
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
it 'resolves raider_reporter 0.
|
|
107
|
+
it 'resolves raider_reporter 0.5 from RubyGems and ships the raider-reporter executable', :aggregate_failures do
|
|
108
108
|
skip 'Bundle install failed' unless bundle_install(project)
|
|
109
109
|
|
|
110
|
-
expect(File.read(File.join(project, 'Gemfile.lock'))).to match(/^ raider_reporter \(0\.
|
|
110
|
+
expect(File.read(File.join(project, 'Gemfile.lock'))).to match(/^ raider_reporter \(0\.5\.\d+\)$/)
|
|
111
111
|
result = run_in_project(project, 'bundle exec raider-reporter --help', timeout: 60)
|
|
112
112
|
expect(result[:exit_code]).to eq(0), "STDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
113
113
|
expect(result[:stdout] + result[:stderr]).to include('raider-reporter comment')
|
|
@@ -162,6 +162,85 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
162
162
|
"STDOUT: #{result[:stdout]}\n" \
|
|
163
163
|
"STDERR: #{result[:stderr]}"
|
|
164
164
|
end
|
|
165
|
+
|
|
166
|
+
it 'resolves raider_reporter 0.5 from RubyGems and requires its Cucumber plugin', :aggregate_failures do
|
|
167
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
168
|
+
|
|
169
|
+
lockfile = File.read(File.join(project, 'Gemfile.lock'))
|
|
170
|
+
expect(lockfile).to match(/^ raider_reporter \(0\.5\.\d+\)$/)
|
|
171
|
+
expect(File.read(File.join(project, 'features/support/env.rb'))).to include("require 'raider_reporter/cucumber'")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'prints pretty output and keeps the exit status of a failing Cucumber run', :aggregate_failures, :slow do
|
|
175
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
176
|
+
|
|
177
|
+
result = run_cucumber_with_failure(project)
|
|
178
|
+
|
|
179
|
+
expect(result[:stdout]).to include('Feature: Reporter failure')
|
|
180
|
+
expect(result[:stdout]).to include('Scenario: A deliberately failing scenario')
|
|
181
|
+
expect(result[:stdout]).to match(/\d+ scenarios? \(\d+ failed/)
|
|
182
|
+
expect(result[:exit_code]).to eq(1)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'writes raider-results/run.json listing every scenario with a failing scenario screenshot',
|
|
186
|
+
:aggregate_failures, :slow do
|
|
187
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
188
|
+
|
|
189
|
+
result = run_cucumber_with_failure(project)
|
|
190
|
+
summary = result[:stdout].match(/(\d+) scenarios? \((?:(\d+) failed)?/)
|
|
191
|
+
expect(summary).not_to be_nil, "No Cucumber summary line.\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
192
|
+
|
|
193
|
+
run = JSON.parse(File.read(File.join(project, 'raider-results', 'run.json')))
|
|
194
|
+
expect(run['schema_version']).to eq(1)
|
|
195
|
+
expect(run['results'].size).to eq(summary[1].to_i)
|
|
196
|
+
expect(run['results'].count { |r| r['status'] == 'failed' }).to eq(summary[2].to_i)
|
|
197
|
+
|
|
198
|
+
failing = run['results'].find { |r| r['full_name'] == 'Reporter failure: A deliberately failing scenario' }
|
|
199
|
+
expect(failing).not_to be_nil, "Scenario missing from run.json: #{run['results'].map { |r| r['full_name'] }}"
|
|
200
|
+
expect(failing['status']).to eq('failed')
|
|
201
|
+
expect(failing['file_path']).to eq('features/reporter_failure.feature')
|
|
202
|
+
expect(failing['failure']['message']).to start_with(
|
|
203
|
+
'Then the reporter records this failure (features/reporter_failure.feature:5)'
|
|
204
|
+
)
|
|
205
|
+
screenshot = failing['attachments'].find { |a| a['name'] == 'screenshot' }
|
|
206
|
+
expect(screenshot).not_to be_nil, "No screenshot attachment: #{failing['attachments']}"
|
|
207
|
+
expect(screenshot['content_type']).to eq('image/png')
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Runs the project's features plus a deliberately failing scenario, then removes that scenario again
|
|
212
|
+
def run_cucumber_with_failure(project)
|
|
213
|
+
add_failing_cucumber_scenario(project)
|
|
214
|
+
FileUtils.rm_rf(File.join(project, 'raider-results'))
|
|
215
|
+
run_cucumber(project)
|
|
216
|
+
ensure
|
|
217
|
+
remove_failing_cucumber_scenario(project)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Adds a scenario that always fails at its last step, after the browser has been started
|
|
221
|
+
def add_failing_cucumber_scenario(project)
|
|
222
|
+
File.write(File.join(project, 'features/reporter_failure.feature'), <<~FEATURE)
|
|
223
|
+
Feature: Reporter failure
|
|
224
|
+
|
|
225
|
+
Scenario: A deliberately failing scenario
|
|
226
|
+
Given the reporter has a started scenario
|
|
227
|
+
Then the reporter records this failure
|
|
228
|
+
FEATURE
|
|
229
|
+
File.write(File.join(project, 'features/step_definitions/reporter_failure_steps.rb'), <<~STEPS)
|
|
230
|
+
# frozen_string_literal: true
|
|
231
|
+
|
|
232
|
+
Given('the reporter has a started scenario') {}
|
|
233
|
+
|
|
234
|
+
Then('the reporter records this failure') do
|
|
235
|
+
raise 'deliberate failure for raider_reporter'
|
|
236
|
+
end
|
|
237
|
+
STEPS
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Keeps the deliberate failure from leaking into the other examples that share the project
|
|
241
|
+
def remove_failing_cucumber_scenario(project)
|
|
242
|
+
FileUtils.rm_f(File.join(project, 'features/reporter_failure.feature'))
|
|
243
|
+
FileUtils.rm_f(File.join(project, 'features/step_definitions/reporter_failure_steps.rb'))
|
|
165
244
|
end
|
|
166
245
|
|
|
167
246
|
# Test Web Frameworks (these can run without external services)
|
|
@@ -183,6 +262,95 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
183
262
|
end
|
|
184
263
|
end
|
|
185
264
|
|
|
265
|
+
# Recorded Azure Pipelines env of a push (IndividualCI) build, see raider_reporter's CiDetector specs
|
|
266
|
+
AZURE_PUSH_ENV = {
|
|
267
|
+
'TF_BUILD' => 'True',
|
|
268
|
+
'SYSTEM_COLLECTIONURI' => 'https://dev.azure.com/raider-test/',
|
|
269
|
+
'SYSTEM_TEAMPROJECT' => 'My Project',
|
|
270
|
+
'SYSTEM_TEAMPROJECTID' => '6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c',
|
|
271
|
+
'BUILD_REPOSITORY_NAME' => 'web tests',
|
|
272
|
+
'BUILD_REPOSITORY_ID' => '1c2bd1a0-3f63-4d93-9c3c-2b4f2b1d8f11',
|
|
273
|
+
'BUILD_REPOSITORY_PROVIDER' => 'TfsGit',
|
|
274
|
+
'BUILD_BUILDID' => '42',
|
|
275
|
+
'SYSTEM_JOBDISPLAYNAME' => 'tests',
|
|
276
|
+
'BUILD_REASON' => 'IndividualCI',
|
|
277
|
+
'BUILD_SOURCEBRANCH' => 'refs/heads/release/1.2',
|
|
278
|
+
'BUILD_SOURCEVERSION' => 'abc'
|
|
279
|
+
}.freeze
|
|
280
|
+
|
|
281
|
+
# Generates a ci: azure web project (the shared before(:all) only builds the default github ones)
|
|
282
|
+
def generate_azure_project(framework)
|
|
283
|
+
name = "azure_#{framework}_selenium"
|
|
284
|
+
unless LazyProjectGenerator::GENERATED.include?(name)
|
|
285
|
+
generate_framework(framework:, automation: 'selenium', name:, ci: 'azure')
|
|
286
|
+
LazyProjectGenerator::GENERATED.add(name)
|
|
287
|
+
end
|
|
288
|
+
name
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def azure_env_prefix
|
|
292
|
+
"GITHUB_ACTIONS= HEADLESS=true #{AZURE_PUSH_ENV.map { |k, v| "#{k}='#{v}'" }.join(' ')}"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def expect_azure_ci_block(project)
|
|
296
|
+
run = JSON.parse(File.read(File.join(project, 'raider-results', 'run.json')))
|
|
297
|
+
ci = run.dig('run', 'ci') || run['ci']
|
|
298
|
+
expect(ci).not_to be_nil, "No ci block in run.json: #{run.keys}"
|
|
299
|
+
expect(ci['provider']).to eq('azure_pipelines')
|
|
300
|
+
expect(ci['build_key']).to start_with('azure_pipelines:')
|
|
301
|
+
expect(ci['build_key']).to eq(
|
|
302
|
+
'azure_pipelines:dev.azure.com/raider-test/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c/' \
|
|
303
|
+
'1c2bd1a0-3f63-4d93-9c3c-2b4f2b1d8f11:42:1'
|
|
304
|
+
)
|
|
305
|
+
expect(ci['build_url']).to eq('https://dev.azure.com/raider-test/My%20Project/_build/results?buildId=42')
|
|
306
|
+
puts "\nrun.json ci block: #{JSON.pretty_generate(ci)}"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
context 'Azure Pipelines projects (ci: azure)' do
|
|
310
|
+
describe 'Selenium + RSpec' do
|
|
311
|
+
let(:project) { generate_azure_project('rspec') }
|
|
312
|
+
|
|
313
|
+
it 'ships azure-pipelines.yml and resolves raider_reporter 0.5', :aggregate_failures do
|
|
314
|
+
expect(File).to exist(File.join(project, 'azure-pipelines.yml'))
|
|
315
|
+
expect(File).not_to exist(File.join(project, '.github/workflows'))
|
|
316
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
317
|
+
|
|
318
|
+
expect(File.read(File.join(project, 'Gemfile.lock'))).to match(/^ raider_reporter \(0\.5\.\d+\)$/)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
it 'records provider azure_pipelines and an azure_pipelines: build key in run.json', :aggregate_failures, :slow do
|
|
322
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
323
|
+
|
|
324
|
+
FileUtils.rm_rf(File.join(project, 'raider-results'))
|
|
325
|
+
result = run_in_project(project, "#{azure_env_prefix} bundle exec rspec spec/ --format documentation",
|
|
326
|
+
timeout: 120)
|
|
327
|
+
expect(result[:success]).to be(true), "STDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
328
|
+
expect_azure_ci_block(project)
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
describe 'Selenium + Cucumber' do
|
|
333
|
+
let(:project) { generate_azure_project('cucumber') }
|
|
334
|
+
|
|
335
|
+
it 'ships azure-pipelines.yml and resolves raider_reporter 0.5', :aggregate_failures do
|
|
336
|
+
expect(File).to exist(File.join(project, 'azure-pipelines.yml'))
|
|
337
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
338
|
+
|
|
339
|
+
expect(File.read(File.join(project, 'Gemfile.lock'))).to match(/^ raider_reporter \(0\.5\.\d+\)$/)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
it 'records provider azure_pipelines and an azure_pipelines: build key in run.json', :aggregate_failures, :slow do
|
|
343
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
344
|
+
|
|
345
|
+
FileUtils.rm_rf(File.join(project, 'raider-results'))
|
|
346
|
+
result = run_in_project(project, "#{azure_env_prefix} bundle exec cucumber features/ --format pretty",
|
|
347
|
+
timeout: 120)
|
|
348
|
+
expect(result[:success]).to be(true), "STDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
349
|
+
expect_azure_ci_block(project)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
186
354
|
# Mobile and Visual frameworks require external services, so we only verify structure
|
|
187
355
|
context 'Mobile Automation Frameworks (structure validation only)' do
|
|
188
356
|
describe 'iOS + RSpec' do
|