From 8204dbe5a45da31bd08b48c28ce633ead33b33a3 Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:01:15 +0200 Subject: [PATCH 1/7] Add raider_reporter to generated RSpec projects and pin ruby_raider to its own version --- lib/generators/generator.rb | 5 +++ lib/generators/templates/common/gemfile.tt | 3 +- .../content/gemfile_content_spec.rb | 38 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/generators/generator.rb b/lib/generators/generator.rb index b27a697e..99b3e546 100644 --- a/lib/generators/generator.rb +++ b/lib/generators/generator.rb @@ -71,6 +71,11 @@ def selenium_based? args.include?('selenium') end + def ruby_raider_version_requirement + major, minor = File.read(File.expand_path('../version', __dir__)).strip.split('.') + "~> #{major}.#{minor}" + end + private def _initializer diff --git a/lib/generators/templates/common/gemfile.tt b/lib/generators/templates/common/gemfile.tt index 3483ca63..1aef2105 100644 --- a/lib/generators/templates/common/gemfile.tt +++ b/lib/generators/templates/common/gemfile.tt @@ -20,6 +20,7 @@ gem 'parallel_tests' gem 'rake' gem 'reek' <%- if rspec? -%> +gem 'raider_reporter', '~> 0.2' gem 'rspec-retry' <%- end -%> gem '<%= framework %>' @@ -30,7 +31,7 @@ gem 'rubocop' <%- if rspec? -%> gem 'rubocop-rspec' <%- end -%> -gem 'ruby_raider', '~> 1.1.0' +gem 'ruby_raider', '<%= ruby_raider_version_requirement %>' <%- if selenium_based? %> gem 'selenium-webdriver' <%- end -%> diff --git a/spec/integration/content/gemfile_content_spec.rb b/spec/integration/content/gemfile_content_spec.rb index f937eb33..79007c19 100644 --- a/spec/integration/content/gemfile_content_spec.rb +++ b/spec/integration/content/gemfile_content_spec.rb @@ -119,4 +119,42 @@ include_examples 'contains watir gems', name end + describe 'raider_reporter and ruby_raider pin' do + major_minor = File.read('lib/version').strip.split('.').first(2).join('.') + + AUTOMATION_TYPES.each do |automation| + context "with rspec and #{automation}" do + let(:gemfile) { read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'Gemfile') } + + it 'includes raider_reporter' do + expect(gemfile).to include("gem 'raider_reporter', '~> 0.2'") + end + + it 'keeps allure gems' do + expect(gemfile).to include("gem 'allure-rspec', '2.28.0'", "gem 'allure-ruby-commons', '2.28.0'") + end + + it 'pins ruby_raider to the generator major.minor' do + expect(gemfile).to include("gem 'ruby_raider', '~> #{major_minor}'") + end + end + + context "with cucumber and #{automation}" do + let(:gemfile) { read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'Gemfile') } + + it 'does not include raider_reporter' do + expect(gemfile).not_to include('raider_reporter') + end + + it 'keeps allure-cucumber' do + expect(gemfile).to include("gem 'allure-cucumber', '2.28.0'") + end + + it 'pins ruby_raider to the generator major.minor' do + expect(gemfile).to include("gem 'ruby_raider', '~> #{major_minor}'") + end + end + end + end + end From 2e17658539c7ca116e594d0cdfc8ab0d609376f7 Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:02:48 +0200 Subject: [PATCH 2/7] Report generated RSpec runs through raider_reporter --- lib/generators/templates/common/git_ignore.tt | 1 + lib/generators/templates/common/rspec.tt | 1 + .../templates/helpers/partials/screenshot.tt | 6 +- .../templates/helpers/spec_helper.tt | 13 +++-- .../templates/helpers/test_helper.tt | 1 + .../content/common_content_spec.rb | 16 ++++++ .../content/helper_content_spec.rb | 55 +++++++++++++++++++ 7 files changed, 85 insertions(+), 8 deletions(-) diff --git a/lib/generators/templates/common/git_ignore.tt b/lib/generators/templates/common/git_ignore.tt index 7d4fcf99..a1b3ab8f 100644 --- a/lib/generators/templates/common/git_ignore.tt +++ b/lib/generators/templates/common/git_ignore.tt @@ -1,4 +1,5 @@ allure-results <%- if rspec? -%> +raider-results spec/examples.txt <%- end -%> \ No newline at end of file diff --git a/lib/generators/templates/common/rspec.tt b/lib/generators/templates/common/rspec.tt index 88fcecc8..557e8e01 100644 --- a/lib/generators/templates/common/rspec.tt +++ b/lib/generators/templates/common/rspec.tt @@ -1,3 +1,4 @@ -I . --require helpers/spec_helper --format documentation +--format RaiderReporter::RSpec::Formatter diff --git a/lib/generators/templates/helpers/partials/screenshot.tt b/lib/generators/templates/helpers/partials/screenshot.tt index 456ca517..7e0b56d9 100644 --- a/lib/generators/templates/helpers/partials/screenshot.tt +++ b/lib/generators/templates/helpers/partials/screenshot.tt @@ -1,7 +1,7 @@ <% if selenium_based? %> - screenshot = driver.save_screenshot("#{temp_folder}/#{example_name}.png") + screenshot = driver.save_screenshot(screenshot_path) <% elsif watir? %> - screenshot = browser.screenshot.save("#{temp_folder}/#{example_name}.png") + screenshot = browser.screenshot.save(screenshot_path) <% else %> - screenshot = driver.screenshot("#{temp_folder}/#{example_name}.png") + screenshot = driver.screenshot(screenshot_path) <% end %> \ No newline at end of file diff --git a/lib/generators/templates/helpers/spec_helper.tt b/lib/generators/templates/helpers/spec_helper.tt index 04949cbc..53d13ecd 100644 --- a/lib/generators/templates/helpers/spec_helper.tt +++ b/lib/generators/templates/helpers/spec_helper.tt @@ -5,6 +5,7 @@ require 'axe-rspec' <%- end -%> require 'rspec' require 'rspec/retry' +require 'raider_reporter/rspec' require 'tmpdir' require_relative 'allure_helper' <%- if watir? -%> @@ -21,7 +22,7 @@ module SpecHelper config.verbose_retry = true config.display_try_failure_messages = true config.default_retry_count = ENV.fetch('RETRY_COUNT', 0).to_i - config.formatter = AllureHelper.formatter + config.add_formatter(AllureHelper.formatter) <%- if watir? -%> config.include(BrowserHelper) <%- else -%> @@ -53,10 +54,12 @@ module SpecHelper example_name = example.description if example.exception @_exception = example.exception - end - Dir.mktmpdir do |temp_folder| - <%= partial('screenshot', strip: true) %> - AllureHelper.add_screenshot(example_name, screenshot) + Dir.mktmpdir do |temp_folder| + screenshot_path = "#{temp_folder}/#{example_name}.png" + <%= partial('screenshot', strip: true) %> + AllureHelper.add_screenshot(example_name, screenshot) + RaiderReporter.attach('screenshot', screenshot_path, type: :png) + end end <%= partial('quit_driver', strip: true) %> end diff --git a/lib/generators/templates/helpers/test_helper.tt b/lib/generators/templates/helpers/test_helper.tt index d4a794b1..6f76217d 100644 --- a/lib/generators/templates/helpers/test_helper.tt +++ b/lib/generators/templates/helpers/test_helper.tt @@ -74,6 +74,7 @@ module TestHelper <%- end -%> <%- end -%> Dir.mktmpdir do |temp_folder| + screenshot_path = "#{temp_folder}/#{example_name}.png" <%= partial('screenshot', strip: true) %> <%- if allure_reporter? -%> AllureHelper.add_screenshot(example_name, screenshot) diff --git a/spec/integration/content/common_content_spec.rb b/spec/integration/content/common_content_spec.rb index c3696822..cb08b4f7 100644 --- a/spec/integration/content/common_content_spec.rb +++ b/spec/integration/content/common_content_spec.rb @@ -89,6 +89,14 @@ it 'uses documentation format' do expect(rspec_file).to include('--format documentation') end + + it 'adds the raider formatter' do + expect(rspec_file).to include('--format RaiderReporter::RSpec::Formatter') + end + + it 'requires spec_helper before the formatters' do + expect(rspec_file.index('--require helpers/spec_helper')).to be < rspec_file.index('--format') + end end shared_examples 'no rspec dotfile' do |project_name| @@ -109,6 +117,10 @@ it 'includes spec/examples.txt' do expect(gitignore).to include('spec/examples.txt') end + + it 'includes raider-results' do + expect(gitignore).to include('raider-results') + end end shared_examples 'non-rspec gitignore' do |project_name| @@ -121,6 +133,10 @@ it 'does not include spec/examples.txt' do expect(gitignore).not_to include('spec/examples.txt') end + + it 'does not include raider-results' do + expect(gitignore).not_to include('raider-results') + end end # --- Spec helper retry config (C4) --- diff --git a/spec/integration/content/helper_content_spec.rb b/spec/integration/content/helper_content_spec.rb index 023d20fc..baae19bb 100644 --- a/spec/integration/content/helper_content_spec.rb +++ b/spec/integration/content/helper_content_spec.rb @@ -109,6 +109,32 @@ end end + shared_examples 'raider reporter spec helper' do |project_name| + subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') } + + it 'requires raider_reporter/rspec' do + expect(helper).to include("require 'raider_reporter/rspec'") + end + + it 'does not replace the formatter' do + expect(helper).not_to match(/config\.formatter\s*=/) + end + + it 'adds the allure formatter' do + expect(helper).to include('config.add_formatter(AllureHelper.formatter)') + end + + it 'screenshots only on failure' do + expect(helper.scan('AllureHelper.add_screenshot').size).to eq(1) + expect(helper).to match(/if example\.exception\n(?:(?!\bend\b).*\n)*?.*AllureHelper\.add_screenshot/) + expect(helper).not_to match(/^ {6}Dir\.mktmpdir/) + end + + it 'attaches the screenshot to raider by path' do + expect(helper).to include("RaiderReporter.attach('screenshot', screenshot_path, type: :png)") + end + end + shared_examples 'selenium spec helper' do |project_name| subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') } @@ -200,4 +226,33 @@ name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}" end + + # --- Raider reporter wiring (every rspec automation) --- + + SCREENSHOT_CALLS = { + 'selenium' => 'driver.save_screenshot(screenshot_path)', + 'watir' => 'browser.screenshot.save(screenshot_path)', + 'android' => 'driver.screenshot(screenshot_path)', + 'ios' => 'driver.screenshot(screenshot_path)', + 'cross_platform' => 'driver.screenshot(screenshot_path)' + }.freeze + + AUTOMATION_TYPES.each do |automation| + context "with rspec and #{automation} raider reporter wiring" do + name = "#{FrameworkIndex::RSPEC}_#{automation}" + it_behaves_like 'raider reporter spec helper', name + + it 'takes the screenshot with the automation call into a path' do + expect(read_generated(name, 'helpers/spec_helper.rb')).to include(SCREENSHOT_CALLS.fetch(automation)) + end + end + + context "with cucumber and #{automation}" do + it 'has no raider reporter wiring' do + project = "#{FrameworkIndex::CUCUMBER}_#{automation}" + files = Dir.glob("#{project}/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) } + expect(files.select { |f| File.read(f).match?(/raider_reporter|RaiderReporter/) }).to be_empty + end + end + end end From 0cf424b8887f8c0db4da1069971793b901b8110f Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:04:03 +0200 Subject: [PATCH 3/7] Run generated CI on push and PRs and pass Raider settings --- .../infrastructure/templates/github.tt | 18 +++++ .../infrastructure/templates/github_appium.tt | 32 +++++++- spec/integration/content/ci_content_spec.rb | 75 +++++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/lib/generators/infrastructure/templates/github.tt b/lib/generators/infrastructure/templates/github.tt index cf126715..f076928e 100644 --- a/lib/generators/infrastructure/templates/github.tt +++ b/lib/generators/infrastructure/templates/github.tt @@ -1,6 +1,9 @@ name: <%- if framework == 'cucumber' -%>Cucumber Tests<%- else -%>Rspec Tests<%- end -%> on: + push: + branches: [ main, master ] + pull_request: workflow_dispatch: inputs: browser: @@ -37,6 +40,21 @@ jobs: run: <%- if framework == 'cucumber' -%>bundle exec cucumber features --format pretty<%- else -%>bundle exec rspec spec --format documentation<%- end %> env: HEADLESS: true +<%- unless framework == 'cucumber' -%> + RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }} + RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }} +<%- end -%> + +<%- unless framework == 'cucumber' -%> + - name: Upload Raider results + uses: actions/upload-artifact@v4 + if: always() + with: + name: raider-results + path: raider-results/ + if-no-files-found: ignore + +<%- end -%> - name: Get Allure history uses: actions/checkout@v2 diff --git a/lib/generators/infrastructure/templates/github_appium.tt b/lib/generators/infrastructure/templates/github_appium.tt index 73b661ed..b769d4e5 100644 --- a/lib/generators/infrastructure/templates/github_appium.tt +++ b/lib/generators/infrastructure/templates/github_appium.tt @@ -2,9 +2,9 @@ name: Appium Tests on: push: - branches: [ main ] + branches: [ main, master ] pull_request: - branches: [ main ] + workflow_dispatch: jobs: <%- if ios? || cross_platform? -%> @@ -52,6 +52,20 @@ jobs: <%- end -%> env: APP_PATH: ${{ github.workspace }}/apps/RaiderTestApp.app +<%- unless cucumber? -%> + RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }} + RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }} +<%- end -%> +<%- unless cucumber? -%> + + - name: Upload Raider results + uses: actions/upload-artifact@v4 + if: always() + with: + name: raider-results-ios + path: raider-results/ + if-no-files-found: ignore +<%- end -%> <%- end -%> <%- if android? || cross_platform? -%> @@ -101,4 +115,18 @@ jobs: <%- end -%> env: APP_PATH: ${{ github.workspace }}/apps/app-release.apk +<%- unless cucumber? -%> + RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }} + RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }} +<%- end -%> +<%- unless cucumber? -%> + + - name: Upload Raider results + uses: actions/upload-artifact@v4 + if: always() + with: + name: raider-results-android + path: raider-results/ + if-no-files-found: ignore +<%- end -%> <%- end -%> diff --git a/spec/integration/content/ci_content_spec.rb b/spec/integration/content/ci_content_spec.rb index 0ec7a7f6..dd641f4b 100644 --- a/spec/integration/content/ci_content_spec.rb +++ b/spec/integration/content/ci_content_spec.rb @@ -44,21 +44,96 @@ end end + shared_examples 'github triggers' do |project_name, file| + subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") } + + let(:triggers) { YAML.safe_load(pipeline)[true] } + + it 'triggers on push to main and master' do + expect(triggers['push']['branches']).to include('main', 'master') + end + + it 'triggers on pull_request' do + expect(triggers).to have_key('pull_request') + end + + it 'triggers on workflow_dispatch' do + expect(triggers).to have_key('workflow_dispatch') + end + end + + shared_examples 'github raider wiring' do |project_name, file| + subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") } + + let(:steps) { YAML.safe_load(pipeline)['jobs'].values.flat_map { |job| job['steps'] } } + + it 'passes Raider settings to the test step' do + expect(pipeline).to include('RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}') + expect(pipeline).to include('RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}') + end + + it 'uploads raider-results as an artifact even on failure' do + upload = steps.find { |step| step['uses'] == 'actions/upload-artifact@v4' } + expect(upload['with']['path']).to eq('raider-results/') + expect(upload['if']).to eq('always()') + end + end + + shared_examples 'github without raider wiring' do |project_name, file| + subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") } + + it 'has no Raider settings or artifact' do + expect(pipeline).not_to include('RAIDER_') + expect(pipeline).not_to include('raider-results') + end + end + + shared_examples 'github allure steps' do |project_name| + subject(:pipeline) { read_generated(project_name, '.github/workflows/test_pipeline.yml') } + + it 'keeps the Allure steps' do + expect(pipeline).to include('simple-elf/allure-report-action', 'peaceiris/actions-gh-pages') + end + end + context 'with rspec and selenium' do name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}" include_examples 'valid github pipeline', name include_examples 'github rspec pipeline', name + include_examples 'github triggers', name, 'test_pipeline.yml' + include_examples 'github raider wiring', name, 'test_pipeline.yml' + include_examples 'github allure steps', name end context 'with cucumber and selenium' do name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}" include_examples 'valid github pipeline', name include_examples 'github cucumber pipeline', name + include_examples 'github triggers', name, 'test_pipeline.yml' + include_examples 'github without raider wiring', name, 'test_pipeline.yml' + include_examples 'github allure steps', name end context 'with rspec and watir' do name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::WATIR}" include_examples 'valid github pipeline', name include_examples 'github rspec pipeline', name + include_examples 'github triggers', name, 'test_pipeline.yml' + include_examples 'github raider wiring', name, 'test_pipeline.yml' + include_examples 'github allure steps', name + end + + %w[android ios cross_platform].each do |automation| + context "with rspec and #{automation}" do + name = "#{FrameworkIndex::RSPEC}_#{automation}" + include_examples 'github triggers', name, 'appium_pipeline.yml' + include_examples 'github raider wiring', name, 'appium_pipeline.yml' + end + + context "with cucumber and #{automation}" do + name = "#{FrameworkIndex::CUCUMBER}_#{automation}" + include_examples 'github triggers', name, 'appium_pipeline.yml' + include_examples 'github without raider wiring', name, 'appium_pipeline.yml' + end end end From 7b825c25865541132c54566a39ccf27c50536c14 Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:06:10 +0200 Subject: [PATCH 4/7] Document raider_reporter and release 3.3.0 --- README.md | 1 + RELEASE.md | 11 +++++++ lib/generators/templates/common/read_me.tt | 17 ++++++++++ lib/version | 2 +- .../content/readme_content_spec.rb | 31 +++++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 spec/integration/content/readme_content_spec.rb diff --git a/README.md b/README.md index 5b2482ff..ca424e59 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Every generated project comes with: - **Page Object Model** — Base page and component classes with example pages - **Test examples** — Working login tests against the [Raider Test Store](https://raider-test-site.onrender.com/) - **Allure reporting** — Pre-configured with screenshot capture on failure +- **Raider reporting** — RSpec projects include [raider_reporter](https://github.com/RaiderHQ/raider_reporter), which writes results to `raider-results/` and uploads runs when `RAIDER_TOKEN` and `RAIDER_ENDPOINT` are set - **GitHub Actions CI** — Ready-to-use test pipeline - **Rake tasks** — Default, smoke, and regression tasks with tag-based filtering - **Retry support** — RSpec retry with configurable retry count via `RETRY_COUNT` env var diff --git a/RELEASE.md b/RELEASE.md index 4da154df..d09644fe 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,16 @@ # Release Process Documentation +## Release Notes + +### 3.3.0 + +- Generated RSpec projects now include `raider_reporter`: results are written to `raider-results/` and uploaded when `RAIDER_TOKEN` and `RAIDER_ENDPOINT` are set. Allure is still produced. Cucumber projects are unchanged. +- Screenshots (Allure too) are now taken only when an example fails, and are attached to both Allure and Raider. +- Generated GitHub CI now runs on push (main, master), pull requests and manual dispatch, passes the Raider settings to RSpec runs and uploads `raider-results/` as an artifact. +- Fixed the generated Gemfile `ruby_raider` pin so it follows the gem's own major.minor version. + +--- + ## Overview Ruby Raider uses a **fully automated release process**. You only need to run a single command, and everything else happens automatically via GitHub Actions. diff --git a/lib/generators/templates/common/read_me.tt b/lib/generators/templates/common/read_me.tt index bdd1d29b..78c23f86 100644 --- a/lib/generators/templates/common/read_me.tt +++ b/lib/generators/templates/common/read_me.tt @@ -82,6 +82,23 @@ raider g steps [NAME] # Create step definitions raider g scaffold [NAME] # Create page + test + steps ``` +<%- if rspec? -%> +## Reporting + +Every run writes machine-readable results to `raider-results/` (including `run.json`), next to the usual Allure results. +Allure reports are still produced. + +To upload runs to Raider, set these environment variables before running the suite: + +```bash +export RAIDER_TOKEN=your-token +export RAIDER_ENDPOINT=https://your-raider-endpoint +``` + +Without `RAIDER_TOKEN`, nothing is uploaded and the suite output is unchanged. Failure screenshots are attached to both +Allure and Raider. See the [raider_reporter README](https://github.com/RaiderHQ/raider_reporter) for details. + +<%- end -%> ### Sponsors This project is tested with BrowserStack. diff --git a/lib/version b/lib/version index e4604e3a..15a27998 100644 --- a/lib/version +++ b/lib/version @@ -1 +1 @@ -3.2.1 +3.3.0 diff --git a/spec/integration/content/readme_content_spec.rb b/spec/integration/content/readme_content_spec.rb new file mode 100644 index 00000000..7253693f --- /dev/null +++ b/spec/integration/content/readme_content_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative 'content_helper' + +describe 'Generated Readme content' do + AUTOMATION_TYPES.each do |automation| + context "with rspec and #{automation}" do + subject(:readme) { read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'Readme.md') } + + it 'has a Reporting section' do + expect(readme).to include('## Reporting') + end + + it 'documents local results, upload settings and the raider_reporter README' do + expect(readme).to include('raider-results/', 'RAIDER_TOKEN', 'RAIDER_ENDPOINT', + 'https://github.com/RaiderHQ/raider_reporter') + end + + it 'says Allure is still produced' do + expect(readme).to match(/Allure/) + end + end + + context "with cucumber and #{automation}" do + it 'has no Reporting section' do + readme = read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'Readme.md') + expect(readme).not_to include('RAIDER_TOKEN') + end + end + end +end From b5ce9fd97f46daeaa48a07115b81d0b7fdbeca37 Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:06:10 +0200 Subject: [PATCH 5/7] Tighten generated reporting specs --- .../infrastructure/templates/github_appium.tt | 4 --- spec/integration/content/ci_content_spec.rb | 24 ++++++++++---- .../content/helper_content_spec.rb | 33 +++++++++---------- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/lib/generators/infrastructure/templates/github_appium.tt b/lib/generators/infrastructure/templates/github_appium.tt index b769d4e5..b5a6c2e1 100644 --- a/lib/generators/infrastructure/templates/github_appium.tt +++ b/lib/generators/infrastructure/templates/github_appium.tt @@ -55,8 +55,6 @@ jobs: <%- unless cucumber? -%> RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }} RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }} -<%- end -%> -<%- unless cucumber? -%> - name: Upload Raider results uses: actions/upload-artifact@v4 @@ -118,8 +116,6 @@ jobs: <%- unless cucumber? -%> RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }} RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }} -<%- end -%> -<%- unless cucumber? -%> - name: Upload Raider results uses: actions/upload-artifact@v4 diff --git a/spec/integration/content/ci_content_spec.rb b/spec/integration/content/ci_content_spec.rb index dd641f4b..93a493f4 100644 --- a/spec/integration/content/ci_content_spec.rb +++ b/spec/integration/content/ci_content_spec.rb @@ -67,15 +67,25 @@ let(:steps) { YAML.safe_load(pipeline)['jobs'].values.flat_map { |job| job['steps'] } } - it 'passes Raider settings to the test step' do - expect(pipeline).to include('RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}') - expect(pipeline).to include('RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}') + let(:test_steps) do + steps.select { |step| step['run'].to_s.include?('rspec') || step['with'].to_h['script'].to_s.include?('rspec') } end - it 'uploads raider-results as an artifact even on failure' do - upload = steps.find { |step| step['uses'] == 'actions/upload-artifact@v4' } - expect(upload['with']['path']).to eq('raider-results/') - expect(upload['if']).to eq('always()') + it 'passes Raider settings to every test step' do + expect(test_steps).not_to be_empty + test_steps.each do |step| + expect(step['env']).to include('RAIDER_TOKEN' => '${{ secrets.RAIDER_TOKEN }}', + 'RAIDER_ENDPOINT' => '${{ vars.RAIDER_ENDPOINT }}') + end + end + + it 'uploads raider-results as an artifact even on failure in every job' do + uploads = steps.select { |step| step['uses'] == 'actions/upload-artifact@v4' } + expect(uploads.size).to eq(YAML.safe_load(pipeline)['jobs'].size) + uploads.each do |upload| + expect(upload['with']['path']).to eq('raider-results/') + expect(upload['if']).to eq('always()') + end end end diff --git a/spec/integration/content/helper_content_spec.rb b/spec/integration/content/helper_content_spec.rb index baae19bb..a5cad313 100644 --- a/spec/integration/content/helper_content_spec.rb +++ b/spec/integration/content/helper_content_spec.rb @@ -109,7 +109,7 @@ end end - shared_examples 'raider reporter spec helper' do |project_name| + shared_examples 'raider reporter spec helper' do |project_name, screenshot_call| subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') } it 'requires raider_reporter/rspec' do @@ -125,9 +125,16 @@ end it 'screenshots only on failure' do - expect(helper.scan('AllureHelper.add_screenshot').size).to eq(1) - expect(helper).to match(/if example\.exception\n(?:(?!\bend\b).*\n)*?.*AllureHelper\.add_screenshot/) - expect(helper).not_to match(/^ {6}Dir\.mktmpdir/) + lines = helper.lines + start = lines.index { |line| line.include?('if example.exception') } + expect(start).not_to be_nil + indent = lines[start][/\A */].size + body = lines[(start + 1)..].take_while { |line| line.strip.empty? || line[/\A */].size > indent } + guarded = body.join + + expect(guarded).to include('AllureHelper.add_screenshot', 'RaiderReporter.attach', screenshot_call) + expect(lines.select { |line| line.include?('AllureHelper.add_screenshot') }.size).to eq(1) + expect(lines.select { |line| line.include?(screenshot_call) }.size).to eq(1) end it 'attaches the screenshot to raider by path' do @@ -229,22 +236,14 @@ # --- Raider reporter wiring (every rspec automation) --- - SCREENSHOT_CALLS = { - 'selenium' => 'driver.save_screenshot(screenshot_path)', - 'watir' => 'browser.screenshot.save(screenshot_path)', - 'android' => 'driver.screenshot(screenshot_path)', - 'ios' => 'driver.screenshot(screenshot_path)', - 'cross_platform' => 'driver.screenshot(screenshot_path)' - }.freeze - AUTOMATION_TYPES.each do |automation| context "with rspec and #{automation} raider reporter wiring" do name = "#{FrameworkIndex::RSPEC}_#{automation}" - it_behaves_like 'raider reporter spec helper', name - - it 'takes the screenshot with the automation call into a path' do - expect(read_generated(name, 'helpers/spec_helper.rb')).to include(SCREENSHOT_CALLS.fetch(automation)) - end + screenshot_call = { + 'selenium' => 'driver.save_screenshot(screenshot_path)', + 'watir' => 'browser.screenshot.save(screenshot_path)' + }.fetch(automation, 'driver.screenshot(screenshot_path)') + it_behaves_like 'raider reporter spec helper', name, screenshot_call end context "with cucumber and #{automation}" do From e6c9ac069c249a340a05be1b389f3537eee2775f Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:10:45 +0200 Subject: [PATCH 6/7] Register the Raider formatter in spec_helper and cover run.json end to end --- lib/generators/templates/common/rspec.tt | 1 - .../templates/helpers/spec_helper.tt | 2 ++ .../content/common_content_spec.rb | 9 ++---- .../content/helper_content_spec.rb | 8 +++++ spec/integration/end_to_end_spec.rb | 31 ++++++++++++++++++- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/generators/templates/common/rspec.tt b/lib/generators/templates/common/rspec.tt index 557e8e01..88fcecc8 100644 --- a/lib/generators/templates/common/rspec.tt +++ b/lib/generators/templates/common/rspec.tt @@ -1,4 +1,3 @@ -I . --require helpers/spec_helper --format documentation ---format RaiderReporter::RSpec::Formatter diff --git a/lib/generators/templates/helpers/spec_helper.tt b/lib/generators/templates/helpers/spec_helper.tt index 53d13ecd..012616a3 100644 --- a/lib/generators/templates/helpers/spec_helper.tt +++ b/lib/generators/templates/helpers/spec_helper.tt @@ -5,6 +5,7 @@ require 'axe-rspec' <%- end -%> require 'rspec' require 'rspec/retry' +require 'digest' require 'raider_reporter/rspec' require 'tmpdir' require_relative 'allure_helper' @@ -23,6 +24,7 @@ module SpecHelper config.display_try_failure_messages = true config.default_retry_count = ENV.fetch('RETRY_COUNT', 0).to_i config.add_formatter(AllureHelper.formatter) + config.add_formatter(RaiderReporter::RSpec::Formatter) <%- if watir? -%> config.include(BrowserHelper) <%- else -%> diff --git a/spec/integration/content/common_content_spec.rb b/spec/integration/content/common_content_spec.rb index cb08b4f7..6bfd3598 100644 --- a/spec/integration/content/common_content_spec.rb +++ b/spec/integration/content/common_content_spec.rb @@ -90,12 +90,8 @@ expect(rspec_file).to include('--format documentation') end - it 'adds the raider formatter' do - expect(rspec_file).to include('--format RaiderReporter::RSpec::Formatter') - end - - it 'requires spec_helper before the formatters' do - expect(rspec_file.index('--require helpers/spec_helper')).to be < rspec_file.index('--format') + it 'leaves the raider formatter to spec_helper, since a command-line --format replaces .rspec formatters' do + expect(rspec_file).not_to include('RaiderReporter') end end @@ -236,5 +232,4 @@ it_behaves_like 'gemfile without rspec-retry', name it_behaves_like 'cucumber config with retry', name end - end diff --git a/spec/integration/content/helper_content_spec.rb b/spec/integration/content/helper_content_spec.rb index a5cad313..7bea8f1d 100644 --- a/spec/integration/content/helper_content_spec.rb +++ b/spec/integration/content/helper_content_spec.rb @@ -116,6 +116,14 @@ expect(helper).to include("require 'raider_reporter/rspec'") end + it 'requires digest, which allure-ruby-commons uses without requiring' do + expect(helper).to include("require 'digest'") + end + + it 'adds the raider formatter in spec_helper so a command-line --format cannot drop it' do + expect(helper).to include('config.add_formatter(RaiderReporter::RSpec::Formatter)') + end + it 'does not replace the formatter' do expect(helper).not_to match(/config\.formatter\s*=/) end diff --git a/spec/integration/end_to_end_spec.rb b/spec/integration/end_to_end_spec.rb index d9ff838e..4ff9cbe2 100644 --- a/spec/integration/end_to_end_spec.rb +++ b/spec/integration/end_to_end_spec.rb @@ -3,6 +3,7 @@ require_relative 'spec_helper' require 'timeout' require 'open3' +require 'json' # End-to-end integration tests that verify generated projects actually work # These tests: @@ -41,9 +42,19 @@ def run_in_project(project_name, command, timeout: 300) result end + # The generated Gemfile pins ruby_raider to this gem's major.minor, which may not be + # published yet (a release candidate branch). Resolve it from this checkout instead; + # the pin itself is covered by the Gemfile content specs. + def use_local_ruby_raider(project_name) + gemfile = File.join(project_name, 'Gemfile') + root = File.expand_path('../..', __dir__) + File.write(gemfile, File.read(gemfile).sub(/^gem 'ruby_raider'.*$/, "gem 'ruby_raider', path: '#{root}'")) + end + # Helper to install dependencies def bundle_install(project_name) puts "\n📦 Installing dependencies for #{project_name}..." + use_local_ruby_raider(project_name) result = run_in_project(project_name, 'bundle install --quiet', timeout: 180) unless result[:success] @@ -103,6 +114,25 @@ def run_cucumber(project_name) "STDOUT: #{result[:stdout]}\n" \ "STDERR: #{result[:stderr]}" end + + it 'writes raider-results/run.json listing every example without changing the exit status', :aggregate_failures, :slow do + skip 'Bundle install failed' unless bundle_install(project) + + FileUtils.rm_rf(File.join(project, 'raider-results')) + result = run_rspec(project) + summary = result[:stdout].match(/(\d+) examples?, (\d+) failures?/) + expect(summary).not_to be_nil, "No RSpec summary line.\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}" + examples = summary[1].to_i + failures = summary[2].to_i + + run_file = File.join(project, 'raider-results', 'run.json') + expect(File).to exist(run_file) + run = JSON.parse(File.read(run_file)) + expect(run['schema_version']).to eq(1) + expect(run['results'].size).to eq(examples) + expect(run['results'].count { |r| r['status'] == 'failed' }).to eq(failures) + expect(result[:exit_code]).to eq(failures.zero? ? 0 : 1) + end end # Shared example for Cucumber-based projects @@ -142,7 +172,6 @@ def run_cucumber(project_name) describe 'Watir + Cucumber' do include_examples 'executable cucumber project', 'cucumber_watir' end - end # Mobile and Visual frameworks require external services, so we only verify structure From 9fe73089f359e7a1bf6d2b30cd2043f659f20565 Mon Sep 17 00:00:00 2001 From: Augustin Gottlieb <33221555+aguspe@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:13:54 +0200 Subject: [PATCH 7/7] Resolve ruby_raider from the checkout in system tests --- spec/system/support/system_test_helper.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/system/support/system_test_helper.rb b/spec/system/support/system_test_helper.rb index 44ebf22d..f4cfcf21 100644 --- a/spec/system/support/system_test_helper.rb +++ b/spec/system/support/system_test_helper.rb @@ -15,6 +15,7 @@ def run_tests_with(framework, automation) Bundler.with_unbundled_env do Dir.chdir(project) do + use_checkout_ruby_raider result = run_command('bundle install --quiet') return result unless result[:success] @@ -25,6 +26,13 @@ def run_tests_with(framework, automation) private + # The generated Gemfile pins ruby_raider to this gem's major.minor, which may not be + # published yet. Resolve it from this checkout; the pin itself is covered by content specs. + def use_checkout_ruby_raider(root = File.expand_path('../../..', __dir__)) + gemfile = File.read('Gemfile') + File.write('Gemfile', gemfile.sub(/^gem 'ruby_raider'.*$/, "gem 'ruby_raider', path: '#{root}'")) + end + def run_command(command, env: {}) stdout, stderr, status = Open3.capture3(env, command) { success: status.success?, stdout:, stderr: }