ruby_raider 3.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11203a49dfd79c493cfa08d13576ecd10933eea755664e75f569c4860d74294b
4
- data.tar.gz: 7338d98246ce0e765cb805fbfdb64b1feff88d215aeaecc5cb91184901ea0906
3
+ metadata.gz: ddfb958e45c6bc2214a0d9b80fd9085d8c8bd061199988ba5d0717becfa8e4ba
4
+ data.tar.gz: bc6baf9bc40c2cc60c7dbdbf68386b49ede6c014a80e76bef90746841db7933d
5
5
  SHA512:
6
- metadata.gz: 6e4c33e6293efb67fbe0207e62b434a422337df81d8936ff579a776d23ed89cf3d47bb3d22637a6d4ab28082003db155cb2e75c553aa8dba8966cf494ab0e90f
7
- data.tar.gz: 3eff51512305847c73c9dfd8c83582772fe03fdf232047073428583edd25be07f644e8c133862ae633027e6efae9353255d803b430b0ed44fb9f1384df45dd76
6
+ metadata.gz: 2357fb714baf101f7bb2891fd0f0e2560acb6c5b0009b3188da7714f0579361519d5f00aace5bbaa725e8e6fee3bc9e5d38ad887ff71eed218b0db7154ad3077
7
+ data.tar.gz: c27785ba31ac441a2c8f76cc15e3266881ee4b69a8d3dde8b4de10eabfdc52e81fa3b3ae8be1247afa6256ea03389ee76fe0812d7228ea94934ccdfd5604b9f7
data/README.md CHANGED
@@ -50,6 +50,7 @@ Ruby Raider is a CLI gem and API backend for scaffolding and generating UI test
50
50
  | Add-on | Flag | Description |
51
51
  |--------|------|-------------|
52
52
  | Accessibility | `--accessibility` | Adds axe gem + example accessibility tests |
53
+ | CI provider | `-p ci:azure` | Generates `azure-pipelines.yml` instead of the GitHub Actions workflow (default `ci:github`) |
53
54
 
54
55
  This works on all platforms (Mac OS, Linux and Windows).
55
56
 
@@ -101,6 +102,15 @@ Add accessibility testing:
101
102
  raider new my_project -p framework:rspec automation:selenium --accessibility
102
103
  ```
103
104
 
105
+ Generate an Azure Pipelines file instead of GitHub Actions (web projects only):
106
+
107
+ ```bash
108
+ raider new my_project -p framework:rspec automation:selenium ci:azure
109
+ ```
110
+
111
+ Azure Pipelines for mobile (Appium) projects is not available yet: with `ci:azure` no pipeline file is generated and a notice is printed.
112
+ Azure Repos ignores `pr:` triggers, so add the pipeline as a Build validation branch policy to get pull request builds.
113
+
104
114
  ## Commands
105
115
 
106
116
  ###### Anything between square brackets ([...]) is where your input goes
data/RELEASE.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## Release Notes
4
4
 
5
+ ### 3.6.0
6
+
7
+ - New `ci` parameter: `raider new my_project -p framework:rspec automation:selenium ci:azure` generates an `azure-pipelines.yml` for web projects instead of the GitHub Actions workflow. `ci:github` (the default) is unchanged; any other value fails with `Invalid parameters: ci must be github or azure`.
8
+ - The pipeline runs the suite on `ubuntu-latest`, publishes `raider-results/` as a pipeline artifact and includes a `raider_comment` job that comments Raider results on pull requests via `raider-reporter comment`. It needs the `RAIDER_TOKEN` (secret) and `RAIDER_ENDPOINT` pipeline variables and the "Contribute to pull requests" permission for the build service.
9
+ - Azure Repos ignores `pr:` triggers: add the pipeline as a Build validation branch policy so pull request builds and the Raider comment run.
10
+ - The interactive menu asks "Which CI provider?" (GitHub Actions / Azure Pipelines) for web automations.
11
+ - The generated Readme gets an "Azure Pipelines" section when `ci:azure` is used.
12
+ - Mobile projects with `ci:azure` get no pipeline file yet; `raider new` prints a notice.
13
+ - Generated Gemfiles now require `raider_reporter` `~> 0.5`.
14
+
5
15
  ### 3.5.0
6
16
 
7
17
  - Generated Cucumber projects now report through `raider_reporter` `~> 0.4` instead of Allure: results are written to `raider-results/` and uploaded when `RAIDER_TOKEN` and `RAIDER_ENDPOINT` are set. The reporter is activated by `require 'raider_reporter/cucumber'` in `features/support/env.rb`.
@@ -51,6 +51,15 @@ class Generator < Thor::Group
51
51
  args.include?('rspec')
52
52
  end
53
53
 
54
+ # Only the options after automation, framework and name, so a project named "azure" is not Azure CI
55
+ def azure?
56
+ args.drop(3).include?('azure')
57
+ end
58
+
59
+ def github_ci?
60
+ !azure?
61
+ end
62
+
54
63
  def selenium?
55
64
  args.include?('selenium')
56
65
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../generator'
4
+
5
+ class AzureGenerator < Generator
6
+ def generate_azure_pipeline
7
+ return unless azure? && web?
8
+
9
+ template('azure_pipelines.tt', "#{name}/azure-pipelines.yml")
10
+ end
11
+ end
@@ -4,13 +4,13 @@ require_relative '../generator'
4
4
 
5
5
  class GithubGenerator < Generator
6
6
  def generate_actions_file
7
- return unless web?
7
+ return unless github_ci? && web?
8
8
 
9
9
  template('github.tt', "#{name}/.github/workflows/test_pipeline.yml")
10
10
  end
11
11
 
12
12
  def generate_appium_pipeline
13
- return unless mobile?
13
+ return unless github_ci? && mobile?
14
14
 
15
15
  template('github_appium.tt', "#{name}/.github/workflows/appium_pipeline.yml")
16
16
  end
@@ -0,0 +1,44 @@
1
+ trigger:
2
+ branches:
3
+ include: [main, master]
4
+ # Azure Repos ignores pr: — add this pipeline as a Build validation branch policy so pull request builds (and the Raider comment) run.
5
+ pr:
6
+ branches:
7
+ include: ['*']
8
+
9
+ # Self-hosted agent? Replace vmImage with: pool: { name: Default }
10
+ pool:
11
+ vmImage: ubuntu-latest
12
+
13
+ variables:
14
+ HEADLESS: 'true'
15
+ # Add RAIDER_TOKEN (secret) and RAIDER_ENDPOINT as pipeline variables.
16
+
17
+ jobs:
18
+ - job: tests
19
+ steps:
20
+ - task: UseRubyVersion@0
21
+ inputs: { versionSpec: '3.4' }
22
+ - script: bundle install
23
+ displayName: Install gems
24
+ - script: <%- if cucumber? -%>bundle exec cucumber features/ --format pretty<%- else -%>bundle exec rspec spec/ --format documentation<%- end %>
25
+ displayName: Run tests
26
+ env:
27
+ RAIDER_TOKEN: $(RAIDER_TOKEN)
28
+ RAIDER_ENDPOINT: $(RAIDER_ENDPOINT)
29
+ - task: PublishPipelineArtifact@1
30
+ condition: always()
31
+ inputs: { targetPath: raider-results, artifact: raider-results-$(System.JobPositionInPhase)-$(System.JobAttempt) }
32
+ - job: raider_comment
33
+ dependsOn: tests
34
+ condition: and(always(), eq(variables['Build.Reason'], 'PullRequest'))
35
+ steps:
36
+ - task: UseRubyVersion@0
37
+ inputs: { versionSpec: '3.4' }
38
+ - script: bundle install
39
+ - script: bundle exec raider-reporter comment
40
+ displayName: Comment Raider results on the pull request
41
+ env:
42
+ RAIDER_TOKEN: $(RAIDER_TOKEN)
43
+ RAIDER_ENDPOINT: $(RAIDER_ENDPOINT)
44
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
@@ -1,4 +1,5 @@
1
1
  require_relative 'infrastructure/github_generator'
2
+ require_relative 'infrastructure/azure_generator'
2
3
  require_relative 'automation/automation_generator'
3
4
  require_relative 'common_generator'
4
5
  require_relative 'cucumber/cucumber_generator'
@@ -6,13 +7,16 @@ require_relative 'helper_generator'
6
7
  require_relative 'rspec/rspec_generator'
7
8
 
8
9
  module InvokeGenerators
10
+ class InvalidParameters < ArgumentError; end
11
+
9
12
  module_function
10
13
 
11
14
  def generate_framework(structure = {})
15
+ validate_ci(structure[:ci])
12
16
  generators = %w[Automation Common Helpers]
13
17
  framework = structure[:framework]
14
18
  add_generator(generators, framework.capitalize)
15
- add_generator(generators, 'Github')
19
+ add_generator(generators, 'Github', 'Azure')
16
20
  extra_args = collect_flags(structure)
17
21
  generators.each do |generator|
18
22
  invoke_generator({
@@ -23,6 +27,19 @@ module InvokeGenerators
23
27
  extra_args:
24
28
  })
25
29
  end
30
+ print_mobile_azure_notice(structure)
31
+ end
32
+
33
+ def validate_ci(provider)
34
+ return if [nil, '', 'github', 'azure'].include?(provider&.to_s)
35
+
36
+ raise InvalidParameters, 'ci must be github or azure'
37
+ end
38
+
39
+ def print_mobile_azure_notice(structure)
40
+ return unless structure[:ci].to_s == 'azure' && mobile_automation?(structure[:automation])
41
+
42
+ puts 'Azure Pipelines template for mobile projects is not available yet; see the README.'
26
43
  end
27
44
 
28
45
  def add_generator(generators, *gens)
@@ -45,6 +62,7 @@ module InvokeGenerators
45
62
  def collect_flags(structure)
46
63
  flags = []
47
64
  flags << 'axe_addon' if structure[:accessibility] && !mobile_automation?(structure[:automation])
65
+ flags << 'azure' if structure[:ci].to_s == 'azure'
48
66
  flags
49
67
  end
50
68
 
@@ -30,6 +30,7 @@ class MenuGenerator
30
30
  automation: options[:automation],
31
31
  framework: options[:framework],
32
32
  accessibility: options[:accessibility],
33
+ ci: options[:ci],
33
34
  name: @name
34
35
  }
35
36
  generate_framework(structure)
@@ -75,18 +76,26 @@ class MenuGenerator
75
76
  end
76
77
  end
77
78
 
78
- FrameworkOptions = Struct.new(:automation, :framework, :accessibility)
79
+ FrameworkOptions = Struct.new(:automation, :framework, :accessibility, :ci)
79
80
 
80
81
  def create_framework_options(params)
81
- FrameworkOptions.new(params[:automation], params[:framework], params[:accessibility])
82
+ FrameworkOptions.new(params[:automation], params[:framework], params[:accessibility], params[:ci])
82
83
  end
83
84
 
84
85
  def select_accessibility(framework, automation_type)
85
86
  return create_framework(framework, automation_type) if mobile_automation?(automation_type)
86
87
 
87
88
  prompt.select('Add accessibility testing (axe)?') do |menu|
88
- menu.choice :Yes, -> { create_framework(framework, automation_type, accessibility: true) }
89
- menu.choice :No, -> { create_framework(framework, automation_type) }
89
+ menu.choice :Yes, -> { select_ci(framework, automation_type, accessibility: true) }
90
+ menu.choice :No, -> { select_ci(framework, automation_type, accessibility: false) }
91
+ menu.choice :Quit, -> { exit }
92
+ end
93
+ end
94
+
95
+ def select_ci(framework, automation_type, accessibility:)
96
+ prompt.select('Which CI provider?') do |menu|
97
+ menu.choice 'GitHub Actions', -> { create_framework(framework, automation_type, accessibility:) }
98
+ menu.choice 'Azure Pipelines', -> { create_framework(framework, automation_type, accessibility:, ci_provider: 'azure') }
90
99
  menu.choice :Quit, -> { exit }
91
100
  end
92
101
  end
@@ -95,15 +104,16 @@ class MenuGenerator
95
104
  %w[ios android cross_platform].include?(automation_type)
96
105
  end
97
106
 
98
- def create_framework(framework, automation_type, accessibility: false)
107
+ def create_framework(framework, automation_type, accessibility: false, ci_provider: nil)
99
108
  options = create_framework_options(automation: automation_type,
100
109
  framework: framework.downcase,
101
- accessibility:)
110
+ accessibility:, ci: ci_provider)
102
111
 
103
112
  puts 'Chosen Options:'
104
113
  puts " Automation Type: #{options[:automation]}"
105
114
  puts " Framework: #{options[:framework]}"
106
115
  puts ' Accessibility: enabled' if options[:accessibility]
116
+ puts ' CI: Azure Pipelines' if options[:ci] == 'azure'
107
117
 
108
118
  set_up_framework(options)
109
119
  prompt.say("You have chosen to use #{framework} with #{automation_type}")
@@ -17,7 +17,7 @@ gem 'parallel_split_test'
17
17
  gem 'parallel_tests'
18
18
  gem 'rake'
19
19
  gem 'reek'
20
- gem 'raider_reporter', '~> 0.4'
20
+ gem 'raider_reporter', '~> 0.5'
21
21
  <%- if rspec? -%>
22
22
  gem 'rspec-retry'
23
23
  <%- end -%>
@@ -107,6 +107,24 @@ Without `RAIDER_TOKEN`, nothing is uploaded and the suite output is unchanged. F
107
107
  Raider. See the [raider_reporter README](https://github.com/RaiderHQ/raider_reporter) for details.
108
108
  <%- end -%>
109
109
 
110
+ <%- if azure? && mobile? -%>
111
+ ## CI
112
+
113
+ Azure Pipelines template for mobile projects is not available yet. Use GitHub Actions (`ci:github`) or write your own pipeline; see the raider_reporter README for the Azure setup.
114
+
115
+ <%- elsif azure? -%>
116
+ ## Azure Pipelines
117
+
118
+ `azure-pipelines.yml` runs the suite on `ubuntu-latest` and, on pull requests, comments the Raider results.
119
+
120
+ - Create a pipeline from `azure-pipelines.yml`, then add `RAIDER_TOKEN` (mark it secret) and `RAIDER_ENDPOINT` as pipeline variables.
121
+ - Contribute to pull requests: grant the pipeline's build service identity (`<Project> Build Service (<Organization>)`) the "Contribute to pull requests" permission on the repository, so `raider-reporter comment` can post the comment and status.
122
+ - Azure Repos ignores `pr:` triggers. Add this pipeline as a Build validation branch policy so pull request builds (and the Raider comment) run.
123
+ - Keep the `raider_comment` job in the same stage as the `tests` job.
124
+ - Pull request comments work for Azure Repos only; a GitHub-hosted repo built by Azure Pipelines gets results uploaded but no PR comment.
125
+ - Self-hosted agent? Replace `vmImage: ubuntu-latest` with `pool: { name: Default }`.
126
+
127
+ <%- end -%>
110
128
  ### Sponsors
111
129
 
112
130
  This project is tested with BrowserStack.
data/lib/ruby_raider.rb CHANGED
@@ -25,7 +25,7 @@ module RubyRaider
25
25
  params[:name] = project_name
26
26
  parsed_hash = params.transform_keys(&:to_sym)
27
27
  merge_flags(parsed_hash)
28
- return InvokeGenerators.generate_framework(parsed_hash)
28
+ return generate_from_parameters(parsed_hash)
29
29
  end
30
30
 
31
31
  MenuGenerator.new(project_name).generate_choice_menu
@@ -50,6 +50,13 @@ module RubyRaider
50
50
  map 'u' => 'utility'
51
51
 
52
52
  no_tasks do
53
+ def generate_from_parameters(params)
54
+ InvokeGenerators.generate_framework(params)
55
+ rescue InvokeGenerators::InvalidParameters => e
56
+ puts "Invalid parameters: #{e.message}"
57
+ exit 1
58
+ end
59
+
53
60
  def merge_flags(params)
54
61
  params[:accessibility] = true if options[:accessibility]
55
62
  end
data/lib/version CHANGED
@@ -1 +1 @@
1
- 3.5.0
1
+ 3.6.0
@@ -15,6 +15,8 @@ class Generator < Thor::Group
15
15
  def ios?: () -> bool
16
16
  def android?: () -> bool
17
17
  def rspec?: () -> bool
18
+ def azure?: () -> bool
19
+ def github_ci?: () -> bool
18
20
  def selenium?: () -> bool
19
21
  def watir?: () -> bool
20
22
  def web?: () -> bool
@@ -0,0 +1,4 @@
1
+ # Type definitions for AzureGenerator
2
+ class AzureGenerator < Generator
3
+ def generate_azure_pipeline: () -> void
4
+ end
@@ -1,6 +1,11 @@
1
1
  # Type definitions for InvokeGenerators module
2
2
  module InvokeGenerators
3
+ class InvalidParameters < ArgumentError
4
+ end
5
+
3
6
  def self?.generate_framework: (?Hash[Symbol, untyped] structure) -> void
7
+ def self?.validate_ci: (untyped provider) -> void
8
+ def self?.print_mobile_azure_notice: (Hash[Symbol, untyped] structure) -> void
4
9
  def self?.add_generator: (Array[String] generators, *String gens) -> void
5
10
  def self?.invoke_generator: (?Hash[Symbol, untyped] structure) -> void
6
11
  def self?.collect_flags: (Hash[Symbol, untyped] structure) -> Array[String]
@@ -18,7 +18,8 @@ class MenuGenerator
18
18
  def create_framework_options: (Hash[Symbol, untyped] params) -> untyped
19
19
  def select_accessibility: (String framework, String automation_type) -> void
20
20
  def mobile_automation?: (String automation_type) -> bool
21
- def create_framework: (String framework, String automation_type, ?accessibility: bool) -> void
21
+ def select_ci: (String framework, String automation_type, accessibility: bool) -> void
22
+ def create_framework: (String framework, String automation_type, ?accessibility: bool, ?ci_provider: String?) -> void
22
23
  def yes_no_menu_choices: () -> Hash[Symbol, Proc]
23
24
  def select_automation_framework: (untyped menu) -> void
24
25
  def automation_options: (untyped menu) -> void
data/sig/ruby_raider.rbs CHANGED
@@ -8,6 +8,7 @@ module RubyRaider
8
8
 
9
9
  private
10
10
 
11
+ def generate_from_parameters: (Hash[Symbol, untyped] params) -> void
11
12
  def merge_flags: (Hash[Symbol, untyped] params) -> void
12
13
  end
13
14
  end
@@ -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
@@ -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.4'")
130
+ expect(gemfile).to include("gem 'raider_reporter', '~> 0.5'")
131
131
  end
132
132
 
133
133
  it 'keeps 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.4'")
146
+ expect(gemfile).to include("gem 'raider_reporter', '~> 0.5'")
147
147
  end
148
148
 
149
149
  it 'drops allure-cucumber' do
@@ -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
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_raider
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Agustin Pequeno
@@ -208,7 +208,9 @@ files:
208
208
  - lib/generators/cucumber/templates/world.tt
209
209
  - lib/generators/generator.rb
210
210
  - lib/generators/helper_generator.rb
211
+ - lib/generators/infrastructure/azure_generator.rb
211
212
  - lib/generators/infrastructure/github_generator.rb
213
+ - lib/generators/infrastructure/templates/azure_pipelines.tt
212
214
  - lib/generators/infrastructure/templates/github.tt
213
215
  - lib/generators/infrastructure/templates/github_appium.tt
214
216
  - lib/generators/invoke_generators.rb
@@ -276,6 +278,7 @@ files:
276
278
  - sig/generators/cucumber/cucumber_generator.rbs
277
279
  - sig/generators/generator.rbs
278
280
  - sig/generators/helper_generator.rbs
281
+ - sig/generators/infrastructure/azure_generator.rbs
279
282
  - sig/generators/infrastructure/github_generator.rbs
280
283
  - sig/generators/invoke_generators.rbs
281
284
  - sig/generators/menu_generator.rbs
@@ -302,6 +305,7 @@ files:
302
305
  - spec/integration/commands/browser_update_after_creation_spec.rb
303
306
  - spec/integration/commands/scaffolding_commands_spec.rb
304
307
  - spec/integration/commands/utility_commands_spec.rb
308
+ - spec/integration/content/azure_content_spec.rb
305
309
  - spec/integration/content/ci_content_spec.rb
306
310
  - spec/integration/content/common_content_spec.rb
307
311
  - spec/integration/content/config_content_spec.rb
@@ -317,6 +321,7 @@ files:
317
321
  - spec/integration/end_to_end_spec.rb
318
322
  - spec/integration/generators/automation_generator_spec.rb
319
323
  - spec/integration/generators/axe_addon_spec.rb
324
+ - spec/integration/generators/azure_generator_spec.rb
320
325
  - spec/integration/generators/common_generator_spec.rb
321
326
  - spec/integration/generators/config_features_spec.rb
322
327
  - spec/integration/generators/cucumber_generator_spec.rb