ruby_raider 3.5.0 → 3.7.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -1
  3. data/RELEASE.md +17 -0
  4. data/lib/generators/common_generator.rb +0 -6
  5. data/lib/generators/generator.rb +9 -0
  6. data/lib/generators/helper_generator.rb +0 -7
  7. data/lib/generators/infrastructure/azure_generator.rb +11 -0
  8. data/lib/generators/infrastructure/github_generator.rb +2 -2
  9. data/lib/generators/infrastructure/templates/azure_pipelines.tt +44 -0
  10. data/lib/generators/infrastructure/templates/github.tt +0 -33
  11. data/lib/generators/invoke_generators.rb +19 -1
  12. data/lib/generators/menu_generator.rb +16 -6
  13. data/lib/generators/templates/common/gemfile.tt +1 -5
  14. data/lib/generators/templates/common/git_ignore.tt +0 -3
  15. data/lib/generators/templates/common/read_me.tt +26 -7
  16. data/lib/generators/templates/helpers/partials/screenshot.tt +3 -3
  17. data/lib/generators/templates/helpers/spec_helper.tt +0 -6
  18. data/lib/ruby_raider.rb +8 -1
  19. data/lib/version +1 -1
  20. data/sig/generators/common_generator.rbs +0 -1
  21. data/sig/generators/generator.rbs +2 -0
  22. data/sig/generators/helper_generator.rbs +0 -2
  23. data/sig/generators/infrastructure/azure_generator.rbs +4 -0
  24. data/sig/generators/invoke_generators.rbs +5 -0
  25. data/sig/generators/menu_generator.rbs +2 -1
  26. data/sig/ruby_raider.rbs +1 -0
  27. data/spec/integration/content/azure_content_spec.rb +109 -0
  28. data/spec/integration/content/ci_content_spec.rb +6 -10
  29. data/spec/integration/content/common_content_spec.rb +2 -2
  30. data/spec/integration/content/gemfile_content_spec.rb +6 -6
  31. data/spec/integration/content/helper_content_spec.rb +3 -42
  32. data/spec/integration/content/readme_content_spec.rb +3 -2
  33. data/spec/integration/content/rspec_reporter_content_spec.rb +26 -0
  34. data/spec/integration/end_to_end_spec.rb +93 -4
  35. data/spec/integration/generators/azure_generator_spec.rb +100 -0
  36. data/spec/integration/generators/helpers_generator_spec.rb +2 -6
  37. data/spec/menus/menu_generator_spec.rb +49 -0
  38. metadata +9 -9
  39. data/lib/generators/templates/helpers/allure_helper.tt +0 -32
  40. data/lib/generators/templates/helpers/partials/allure_imports.tt +0 -7
  41. data/lib/generators/templates/helpers/partials/allure_requirements.tt +0 -8
  42. data/lib/generators/templates/helpers/partials/video_start.tt +0 -9
  43. data/lib/generators/templates/helpers/partials/video_stop.tt +0 -4
  44. data/lib/generators/templates/helpers/test_helper.tt +0 -88
@@ -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
@@ -128,20 +128,16 @@ describe 'CI pipeline content' do
128
128
  end
129
129
  end
130
130
 
131
- shared_examples 'github allure steps' do |project_name|
132
- subject(:pipeline) { read_generated(project_name, '.github/workflows/test_pipeline.yml') }
133
-
134
- it 'keeps the Allure steps' do
135
- expect(pipeline).to include('simple-elf/allure-report-action', 'peaceiris/actions-gh-pages')
136
- end
137
- end
138
-
139
131
  shared_examples 'github without allure steps' do |project_name|
140
132
  subject(:pipeline) { read_generated(project_name, '.github/workflows/test_pipeline.yml') }
141
133
 
142
134
  it 'has no Allure report or gh-pages steps' do
143
135
  expect(pipeline).not_to match(/allure|gh-pages/i)
144
136
  end
137
+
138
+ it 'still uploads raider-results' do
139
+ expect(pipeline).to include('name: raider-results', 'path: raider-results/')
140
+ end
145
141
  end
146
142
 
147
143
  context 'with rspec and selenium' do
@@ -151,7 +147,7 @@ describe 'CI pipeline content' do
151
147
  include_examples 'github triggers', name, 'test_pipeline.yml'
152
148
  include_examples 'github raider wiring', name, 'test_pipeline.yml'
153
149
  include_examples 'github raider-comment job', name, 'test_pipeline.yml', %w[build]
154
- include_examples 'github allure steps', name
150
+ include_examples 'github without allure steps', name
155
151
  end
156
152
 
157
153
  context 'with cucumber and selenium' do
@@ -171,7 +167,7 @@ describe 'CI pipeline content' do
171
167
  include_examples 'github triggers', name, 'test_pipeline.yml'
172
168
  include_examples 'github raider wiring', name, 'test_pipeline.yml'
173
169
  include_examples 'github raider-comment job', name, 'test_pipeline.yml', %w[build]
174
- include_examples 'github allure steps', name
170
+ include_examples 'github without allure steps', name
175
171
  end
176
172
 
177
173
  context 'with cucumber and watir' do
@@ -106,8 +106,8 @@ describe 'Common file content' do
106
106
  shared_examples 'rspec gitignore' do |project_name|
107
107
  subject(:gitignore) { read_generated(project_name, '.gitignore') }
108
108
 
109
- it 'includes allure-results' do
110
- expect(gitignore).to include('allure-results')
109
+ it 'does not include allure-results' do
110
+ expect(gitignore).not_to include('allure-results')
111
111
  end
112
112
 
113
113
  it 'includes spec/examples.txt' do
@@ -34,8 +34,8 @@ describe 'Gemfile content' do
34
34
  expect(gemfile).to include("gem 'rspec'")
35
35
  end
36
36
 
37
- it 'includes allure-rspec' do
38
- expect(gemfile).to include("gem 'allure-rspec'")
37
+ it 'does not include allure gems' do
38
+ expect(gemfile).not_to match(/allure/i)
39
39
  end
40
40
 
41
41
  it 'includes rubocop-rspec' do
@@ -127,11 +127,11 @@ 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.4'")
130
+ expect(gemfile).to include("gem 'raider_reporter', '~> 0.7'")
131
131
  end
132
132
 
133
- it 'keeps allure gems' do
134
- expect(gemfile).to include("gem 'allure-rspec', '2.28.0'", "gem 'allure-ruby-commons', '2.28.0'")
133
+ it 'has no allure gems' do
134
+ expect(gemfile).not_to match(/allure/i)
135
135
  end
136
136
 
137
137
  it 'pins ruby_raider to the generator major.minor' 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.4'")
146
+ expect(gemfile).to include("gem 'raider_reporter', '~> 0.7'")
147
147
  end
148
148
 
149
149
  it 'drops allure-cucumber' do
@@ -103,10 +103,6 @@ describe 'Helper file content' do
103
103
  it 'configures RSpec' do
104
104
  expect(helper).to include('RSpec.configure')
105
105
  end
106
-
107
- it 'requires allure_helper' do
108
- expect(helper).to include("require_relative 'allure_helper'")
109
- end
110
106
  end
111
107
 
112
108
  shared_examples 'raider reporter spec helper' do |project_name, screenshot_call|
@@ -116,10 +112,6 @@ describe 'Helper file content' do
116
112
  expect(helper).to include("require 'raider_reporter/rspec'")
117
113
  end
118
114
 
119
- it 'requires digest, which allure-ruby-commons uses without requiring' do
120
- expect(helper).to include("require 'digest'")
121
- end
122
-
123
115
  it 'adds the raider formatter in spec_helper so a command-line --format cannot drop it' do
124
116
  expect(helper).to include('config.add_formatter(RaiderReporter::RSpec::Formatter)')
125
117
  end
@@ -128,10 +120,6 @@ describe 'Helper file content' do
128
120
  expect(helper).not_to match(/config\.formatter\s*=/)
129
121
  end
130
122
 
131
- it 'adds the allure formatter' do
132
- expect(helper).to include('config.add_formatter(AllureHelper.formatter)')
133
- end
134
-
135
123
  it 'screenshots only on failure' do
136
124
  lines = helper.lines
137
125
  start = lines.index { |line| line.include?('if example.exception') }
@@ -140,8 +128,9 @@ describe 'Helper file content' do
140
128
  body = lines[(start + 1)..].take_while { |line| line.strip.empty? || line[/\A */].size > indent }
141
129
  guarded = body.join
142
130
 
143
- expect(guarded).to include('AllureHelper.add_screenshot', 'RaiderReporter.attach', screenshot_call)
144
- expect(lines.select { |line| line.include?('AllureHelper.add_screenshot') }.size).to eq(1)
131
+ expect(guarded).to include('RaiderReporter.attach', screenshot_call)
132
+ expect(helper).not_to include('AllureHelper')
133
+ expect(helper).not_to match(/screenshot\s*=/)
145
134
  expect(lines.select { |line| line.include?(screenshot_call) }.size).to eq(1)
146
135
  end
147
136
 
@@ -182,20 +171,6 @@ describe 'Helper file content' do
182
171
  end
183
172
  end
184
173
 
185
- # --- Allure helper ---
186
-
187
- shared_examples 'valid allure helper' do |project_name|
188
- subject(:helper) { read_generated(project_name, 'helpers/allure_helper.rb') }
189
-
190
- it 'has valid Ruby syntax' do
191
- expect(helper).to have_valid_ruby_syntax
192
- end
193
-
194
- it 'defines AllureHelper' do
195
- expect(helper).to match(/AllureHelper|module AllureHelper|class AllureHelper/)
196
- end
197
- end
198
-
199
174
  # --- RSpec + automation contexts ---
200
175
 
201
176
  context 'with rspec and selenium' do
@@ -204,7 +179,6 @@ describe 'Helper file content' do
204
179
  it_behaves_like 'selenium driver helper', name
205
180
  it_behaves_like 'valid spec helper', name
206
181
  it_behaves_like 'selenium spec helper', name
207
- it_behaves_like 'valid allure helper', name
208
182
  end
209
183
 
210
184
  context 'with rspec and watir' do
@@ -212,7 +186,6 @@ describe 'Helper file content' do
212
186
  it_behaves_like 'valid browser helper', name
213
187
  it_behaves_like 'valid spec helper', name
214
188
  it_behaves_like 'watir spec helper', name
215
- it_behaves_like 'valid allure helper', name
216
189
  end
217
190
 
218
191
  # --- Cucumber + automation contexts (no spec_helper/test_helper) ---
@@ -228,18 +201,6 @@ describe 'Helper file content' do
228
201
  it_behaves_like 'valid browser helper', name
229
202
  end
230
203
 
231
- # --- Mobile contexts (with AppiumRecorder) ---
232
-
233
- context 'with cucumber and appium android' do
234
- name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::ANDROID}"
235
-
236
- end
237
-
238
- context 'with rspec and appium ios' do
239
- name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
240
-
241
- end
242
-
243
204
  # --- Raider reporter wiring (every rspec automation) ---
244
205
 
245
206
  AUTOMATION_TYPES.each do |automation|
@@ -16,8 +16,9 @@ describe 'Generated Readme content' do
16
16
  'https://github.com/RaiderHQ/raider_reporter')
17
17
  end
18
18
 
19
- it 'says Allure is still produced' do
20
- expect(readme).to match(/Allure/)
19
+ it 'points to the local HTML report' do
20
+ expect(readme).to include('raider-results/index.html')
21
+ expect(readme).not_to match(/allure/i)
21
22
  end
22
23
  end
23
24
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'content_helper'
4
+
5
+ describe 'RSpec projects without Allure' do
6
+ AUTOMATION_TYPES.each do |automation|
7
+ context "with rspec and #{automation}" do
8
+ let(:project_name) { "#{FrameworkIndex::RSPEC}_#{automation}" }
9
+
10
+ it 'does not mention Allure in any generated file' do
11
+ files = Dir.glob("#{project_name}/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) }
12
+ files = files.reject { |f| File.binread(f, 512).include?("\x00") }
13
+ expect(files.select { |f| File.read(f).match?(/allure/i) }).to be_empty
14
+ end
15
+
16
+ it 'does not generate the allure helper or the allure-results folder' do
17
+ expect(File).not_to exist(File.join(project_name, 'helpers/allure_helper.rb'))
18
+ expect(File).not_to exist(File.join(project_name, 'allure-results'))
19
+ end
20
+
21
+ it 'pins raider_reporter 0.7' do
22
+ expect(read_generated(project_name, 'Gemfile')).to include("gem 'raider_reporter', '~> 0.7'")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -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.4 from RubyGems and ships the raider-reporter executable', :aggregate_failures do
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\.4\.\d+\)$/)
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')
@@ -163,11 +163,11 @@ describe 'End-to-End Project Generation and Execution' do
163
163
  "STDERR: #{result[:stderr]}"
164
164
  end
165
165
 
166
- it 'resolves raider_reporter 0.4 from RubyGems and requires its Cucumber plugin', :aggregate_failures do
166
+ it 'resolves raider_reporter 0.5 from RubyGems and requires its Cucumber plugin', :aggregate_failures do
167
167
  skip 'Bundle install failed' unless bundle_install(project)
168
168
 
169
169
  lockfile = File.read(File.join(project, 'Gemfile.lock'))
170
- expect(lockfile).to match(/^ raider_reporter \(0\.4\.\d+\)$/)
170
+ expect(lockfile).to match(/^ raider_reporter \(0\.5\.\d+\)$/)
171
171
  expect(File.read(File.join(project, 'features/support/env.rb'))).to include("require 'raider_reporter/cucumber'")
172
172
  end
173
173
 
@@ -262,6 +262,95 @@ describe 'End-to-End Project Generation and Execution' do
262
262
  end
263
263
  end
264
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
+
265
354
  # Mobile and Visual frameworks require external services, so we only verify structure
266
355
  context 'Mobile Automation Frameworks (structure validation only)' do
267
356
  describe 'iOS + RSpec' do
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require_relative '../../../lib/generators/infrastructure/azure_generator'
6
+ require_relative '../spec_helper'
7
+
8
+ describe AzureGenerator do
9
+ mobile_notice = "Azure Pipelines template for mobile projects is not available yet; see the README.\n"
10
+ web = FRAMEWORKS.product(%w[selenium watir], [false, true])
11
+ mobile = FRAMEWORKS.product(%w[ios android cross_platform])
12
+
13
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
14
+ FileUtils.rm_rf(Dir.glob('azure_*'))
15
+ FileUtils.rm_rf(Dir.glob('plain_*'))
16
+ FileUtils.rm_rf(Dir.glob('ghci_*'))
17
+ end
18
+
19
+ web.each do |framework, automation, axe|
20
+ context "with ci azure, #{framework}, #{automation}#{', axe' if axe}" do
21
+ let(:name) { "azure_#{framework}_#{automation}#{'_axe' if axe}" }
22
+
23
+ before do
24
+ next if File.exist?(name)
25
+
26
+ InvokeGenerators.generate_framework(automation:, framework:, name:, accessibility: axe, ci: 'azure')
27
+ end
28
+
29
+ it 'creates azure-pipelines.yml and no .github directory' do
30
+ expect(File).to exist("#{name}/azure-pipelines.yml")
31
+ expect(File).not_to exist("#{name}/.github")
32
+ end
33
+
34
+ it 'is parseable YAML' do
35
+ expect { YAML.safe_load_file("#{name}/azure-pipelines.yml") }.not_to raise_error
36
+ end
37
+ end
38
+ end
39
+
40
+ mobile.each do |framework, automation|
41
+ context "with ci azure, #{framework}, #{automation}" do
42
+ let(:name) { "azure_#{framework}_#{automation}" }
43
+
44
+ it 'generates no pipeline, no .github and prints the notice' do
45
+ expect do
46
+ InvokeGenerators.generate_framework(automation:, framework:, name:, ci: 'azure')
47
+ end.to output(/#{Regexp.escape(mobile_notice)}/).to_stdout
48
+ expect(File).not_to exist("#{name}/azure-pipelines.yml")
49
+ expect(File).not_to exist("#{name}/.github")
50
+ end
51
+ end
52
+ end
53
+
54
+ context 'with the default ci and ci github' do
55
+ it 'generates GitHub files and no azure-pipelines.yml' do
56
+ InvokeGenerators.generate_framework(automation: 'selenium', framework: 'rspec', name: 'plain_default')
57
+ InvokeGenerators.generate_framework(automation: 'selenium', framework: 'rspec', name: 'ghci_github', ci: 'github')
58
+ %w[plain_default ghci_github].each do |name|
59
+ expect(File).to exist("#{name}/.github/workflows/test_pipeline.yml")
60
+ expect(File).not_to exist("#{name}/azure-pipelines.yml")
61
+ end
62
+ end
63
+ end
64
+
65
+ context 'with an invalid ci' do
66
+ it 'raises an ArgumentError and generates nothing' do
67
+ expect do
68
+ InvokeGenerators.generate_framework(automation: 'selenium', framework: 'rspec', name: 'azure_bad', ci: 'gitlab')
69
+ end.to raise_error(ArgumentError, 'ci must be github or azure')
70
+ expect(File).not_to exist('azure_bad')
71
+ end
72
+ end
73
+
74
+ context 'with raider new and ci:gitlab' do
75
+ it 'prints the error, generates nothing and exits non-zero' do
76
+ root = File.expand_path('../../..', __dir__)
77
+ out = IO.popen(['ruby', "#{root}/bin/raider", 'new', 'azure_cli_bad', '-p', 'framework:rspec',
78
+ 'automation:selenium', 'ci:gitlab', { err: %i[child out] }], &:read)
79
+ expect($CHILD_STATUS.exitstatus).not_to eq(0)
80
+ expect(out).to include('Invalid parameters: ci must be github or azure')
81
+ expect(File).not_to exist('azure_cli_bad')
82
+ end
83
+ end
84
+
85
+ context 'with a project named azure' do
86
+ after { FileUtils.rm_rf(%w[azure azure_named_ci]) }
87
+
88
+ it 'generates the GitHub workflow with the default ci' do
89
+ InvokeGenerators.generate_framework(automation: 'selenium', framework: 'rspec', name: 'azure')
90
+ expect(File).to exist('azure/.github/workflows')
91
+ expect(File).not_to exist('azure/azure-pipelines.yml')
92
+ end
93
+
94
+ it 'generates the Azure pipeline with ci azure' do
95
+ InvokeGenerators.generate_framework(automation: 'selenium', framework: 'rspec', name: 'azure', ci: 'azure')
96
+ expect(File).to exist('azure/azure-pipelines.yml')
97
+ expect(File).not_to exist('azure/.github')
98
+ end
99
+ end
100
+ end
@@ -5,12 +5,8 @@ require_relative '../spec_helper'
5
5
 
6
6
  describe HelpersGenerator do
7
7
  shared_examples 'creates common helpers' do |name|
8
- it 'creates an allure helper file only for rspec projects' do
9
- if name.start_with?(FrameworkIndex::CUCUMBER)
10
- expect(File).not_to exist("#{name}/helpers/allure_helper.rb")
11
- else
12
- expect(File).to exist("#{name}/helpers/allure_helper.rb")
13
- end
8
+ it 'never creates an allure helper file' do
9
+ expect(File).not_to exist("#{name}/helpers/allure_helper.rb")
14
10
  end
15
11
  end
16
12
 
@@ -157,4 +157,53 @@ RSpec.describe MenuGenerator do
157
157
  )
158
158
  end
159
159
  end
160
+
161
+ describe 'CI provider question' do
162
+ let(:menu) { double('menu') } # rubocop:disable RSpec/VerifiedDoubles
163
+
164
+ before do
165
+ allow(menu).to receive(:choice)
166
+ allow(menu_generator).to receive(:generate_framework)
167
+ allow(menu_generator).to receive(:system)
168
+ allow(prompt).to receive(:say)
169
+ allow(prompt).to receive(:yes?).and_return(false)
170
+ end
171
+
172
+ it 'is asked after accessibility for web automations' do
173
+ allow(prompt).to receive(:select).with('Which CI provider?').and_yield(menu)
174
+
175
+ menu_generator.send(:select_ci, 'Rspec', 'selenium', accessibility: false)
176
+
177
+ expect(menu).to have_received(:choice).with('GitHub Actions', anything)
178
+ expect(menu).to have_received(:choice).with('Azure Pipelines', anything)
179
+ expect(menu).to have_received(:choice).with(:Quit, anything)
180
+ end
181
+
182
+ it 'follows the accessibility answer' do
183
+ allow(prompt).to receive(:select).with('Add accessibility testing (axe)?').and_yield(menu)
184
+ allow(menu).to receive(:choice).with(:No, anything) { |_, action| action.call }
185
+ allow(menu_generator).to receive(:select_ci)
186
+
187
+ menu_generator.send(:select_accessibility, 'Rspec', 'selenium')
188
+
189
+ expect(menu_generator).to have_received(:select_ci).with('Rspec', 'selenium', accessibility: false)
190
+ end
191
+
192
+ it 'is skipped for mobile' do
193
+ allow(prompt).to receive(:select)
194
+
195
+ menu_generator.send(:select_accessibility, 'Rspec', 'ios')
196
+
197
+ expect(prompt).not_to have_received(:select)
198
+ expect(menu_generator).to have_received(:generate_framework).with(hash_including(automation: 'ios'))
199
+ end
200
+
201
+ it 'passes ci azure through to generate_framework' do
202
+ menu_generator.send(:create_framework, 'Rspec', 'selenium', ci_provider: 'azure')
203
+
204
+ expect(menu_generator).to have_received(:generate_framework).with(
205
+ hash_including(automation: 'selenium', ci: 'azure')
206
+ )
207
+ end
208
+ end
160
209
  end