ruby_raider 3.2.0 → 3.3.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -9
  3. data/RELEASE.md +11 -0
  4. data/lib/commands/scaffolding_commands.rb +6 -14
  5. data/lib/generators/cucumber/templates/accessibility_steps.tt +10 -10
  6. data/lib/generators/cucumber/templates/partials/web_steps.tt +0 -5
  7. data/lib/generators/generator.rb +5 -0
  8. data/lib/generators/infrastructure/templates/github.tt +18 -0
  9. data/lib/generators/infrastructure/templates/github_appium.tt +26 -2
  10. data/lib/generators/rspec/templates/spec.tt +0 -5
  11. data/lib/generators/templates/common/gemfile.tt +2 -5
  12. data/lib/generators/templates/common/git_ignore.tt +1 -0
  13. data/lib/generators/templates/common/read_me.tt +17 -0
  14. data/lib/generators/templates/helpers/partials/screenshot.tt +3 -3
  15. data/lib/generators/templates/helpers/spec_helper.tt +10 -5
  16. data/lib/generators/templates/helpers/test_helper.tt +1 -0
  17. data/lib/scaffolding/scaffolding.rb +0 -6
  18. data/lib/scaffolding/templates/page_object.tt +0 -3
  19. data/lib/scaffolding/templates/spec.tt +0 -6
  20. data/lib/scaffolding/templates/steps.tt +0 -3
  21. data/lib/version +1 -1
  22. data/sig/commands/scaffolding_commands.rbs +1 -1
  23. data/sig/scaffolding/scaffolding.rbs +0 -3
  24. data/spec/integration/content/ci_content_spec.rb +85 -0
  25. data/spec/integration/content/common_content_spec.rb +12 -1
  26. data/spec/integration/content/gemfile_content_spec.rb +38 -16
  27. data/spec/integration/content/helper_content_spec.rb +62 -0
  28. data/spec/integration/content/readme_content_spec.rb +31 -0
  29. data/spec/integration/end_to_end_spec.rb +30 -1
  30. data/spec/integration/scaffolding_e2e_spec.rb +0 -37
  31. data/spec/scaffolding/scaffolding_features_spec.rb +0 -20
  32. data/spec/system/support/system_test_helper.rb +8 -0
  33. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0842e723974ba7cc29e671c8e566b1287f91bcda6a8615003f6504758a921faf'
4
- data.tar.gz: 6aa352dc3a9e74d15c3ffa77e705165de92533adaf071d9dd00dfa492951161f
3
+ metadata.gz: 8f5fc3418c82e5a121ebecbc862acd716ded56f9d7dc49be670e9f0361917deb
4
+ data.tar.gz: b7464935c9d32155bd30d80750c914e6fd3ba95b3db3d6ed8eb95d5461f86fcb
5
5
  SHA512:
6
- metadata.gz: be15c65fb5974c102e822375e064427d6deff7d727cc4265074ab6006f6f6c2454ccf3d4456d81e10cdc686acb0fec70d2a6695573d6b066fd871b8b9c44d31d
7
- data.tar.gz: d210f0c563e7a8d89a654a95b5d93022f1a35be4814587e9bc7221342394a10c07c7ce148047bc6ee715a7b045c0d79280deef47a42be485998fc89a2ee9d54b
6
+ metadata.gz: 827db06d8dab522391496aeedbff5e199119ac287e548241e1ce23bb9ff65616043c1626cb35226ce1230a8c154953c9eb0bc3f65d5a7abaa9ee323dfd76522d
7
+ data.tar.gz: e37a2899b4e8f78c941dd0f570f2a5c2833a61cc74cb30e392cc7e53565f1df78374853b0fd73b38aeabb7d8849fd22210cf534e153f3c8d9664128bd1566b5a
data/README.md CHANGED
@@ -51,18 +51,30 @@ Ruby Raider is a CLI gem and API backend for scaffolding and generating UI test
51
51
  |--------|------|-------------|
52
52
  | Accessibility | `--accessibility` | Adds axe gem + example accessibility tests |
53
53
 
54
- All generated projects include a **GitHub Actions** CI pipeline out of the box.
54
+ This works on all platforms (Mac OS, Linux and Windows).
55
+
56
+ ## What's Included
57
+
58
+ Every generated project comes with:
55
59
 
56
- ***The web tests run against the [Raider Test Store](https://raider-test-site.onrender.com/).***
60
+ - **Page Object Model** — Base page and component classes with example pages
61
+ - **Test examples** — Working login tests against the [Raider Test Store](https://raider-test-site.onrender.com/)
62
+ - **Allure reporting** — Pre-configured with screenshot capture on failure
63
+ - **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
64
+ - **GitHub Actions CI** — Ready-to-use test pipeline
65
+ - **Rake tasks** — Default, smoke, and regression tasks with tag-based filtering
66
+ - **Retry support** — RSpec retry with configurable retry count via `RETRY_COUNT` env var
67
+ - **Re-run failed tests** — Persistence file for tracking example status
68
+ - **Parallel execution** — parallel_tests and parallel_split_test gems included
69
+ - **Configurable browser** — Chrome/Firefox with customizable arguments, viewport, timeout, and headless mode
70
+ - **Linting** — RuboCop and Reek configs included
57
71
 
58
- ***To run Appium tests, download the example [app](https://github.com/RaiderHQ/raider_test_app) and start the server:***
72
+ For Appium projects, download the example [app](https://github.com/RaiderHQ/raider_test_app) and start the server:
59
73
 
60
74
  ```bash
61
75
  raider u start_appium
62
76
  ```
63
77
 
64
- This works on all platforms (Mac OS, Linux and Windows).
65
-
66
78
  ## Getting Started
67
79
 
68
80
  Install the gem:
@@ -83,7 +95,7 @@ Or skip the menu with parameters:
83
95
  raider new [project_name] -p framework:rspec automation:selenium
84
96
  ```
85
97
 
86
- Add optional features:
98
+ Add accessibility testing:
87
99
 
88
100
  ```bash
89
101
  raider new my_project -p framework:rspec automation:selenium --accessibility
@@ -119,7 +131,6 @@ raider g scaffold [NAME(S)] # Create page + test + steps
119
131
 
120
132
  Options:
121
133
 
122
- * `--uses [PAGES]` — Specify page dependencies
123
134
  * `--path [PATH]` — Custom output path
124
135
 
125
136
  ### Utility Commands
@@ -128,8 +139,9 @@ Options:
128
139
  raider u path [PATH] # Set default paths for scaffolding
129
140
  raider u url [URL] # Set default project URL
130
141
  raider u browser [BROWSER] # Set default browser
131
- raider u browser_options [OPTS] # Set browser options
132
- raider u raid # Run all tests
142
+ raider u browser_options [OPTS] # Set browser arguments (e.g., no-sandbox, headless)
143
+ raider u headless [on/off] # Toggle headless mode
144
+ raider u raid # Run all tests (-p for parallel)
133
145
  raider u timeout [SECONDS] # Set test timeout
134
146
  raider u viewport [DIMENSIONS] # Set viewport size (e.g., 1920x1080)
135
147
  raider u platform [PLATFORM] # Set platform for cross-platform tests
data/RELEASE.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Release Process Documentation
2
2
 
3
+ ## Release Notes
4
+
5
+ ### 3.3.0
6
+
7
+ - 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.
8
+ - Screenshots (Allure too) are now taken only when an example fails, and are attached to both Allure and Raider.
9
+ - 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.
10
+ - Fixed the generated Gemfile `ruby_raider` pin so it follows the gem's own major.minor version.
11
+
12
+ ---
13
+
3
14
  ## Overview
4
15
 
5
16
  Ruby Raider uses a **fully automated release process**. You only need to run a single command, and everything else happens automatically via GitHub Actions.
@@ -13,11 +13,9 @@ class ScaffoldingCommands < Thor
13
13
  desc 'page [PAGE_NAME]', 'Creates a new page object'
14
14
  option :path, type: :string, required: false,
15
15
  desc: 'The path where your page will be created', aliases: '-p'
16
- option :uses, type: :array, required: false,
17
- desc: 'Dependent pages to require', aliases: '-u'
18
16
 
19
17
  def page(name)
20
- generate_scaffolding(name, 'page', options[:path], uses: options[:uses])
18
+ generate_scaffolding(name, 'page', options[:path])
21
19
  end
22
20
 
23
21
  desc 'feature [NAME]', 'Creates a new feature'
@@ -31,11 +29,9 @@ class ScaffoldingCommands < Thor
31
29
  desc 'spec [SPEC_NAME]', 'Creates a new spec'
32
30
  option :path, type: :string, required: false,
33
31
  desc: 'The path where your spec will be created', aliases: '-p'
34
- option :uses, type: :array, required: false,
35
- desc: 'Dependent pages to require', aliases: '-u'
36
32
 
37
33
  def spec(name)
38
- generate_scaffolding(name, 'spec', options[:path], uses: options[:uses])
34
+ generate_scaffolding(name, 'spec', options[:path])
39
35
  end
40
36
 
41
37
  desc 'helper [HELPER_NAME]', 'Creates a new helper'
@@ -63,8 +59,6 @@ class ScaffoldingCommands < Thor
63
59
  end
64
60
 
65
61
  desc 'scaffold [NAMES...]', 'Generates pages, specs/features, and helpers for one or more names'
66
- option :uses, type: :array, required: false,
67
- desc: 'Dependent pages to require', aliases: '-u'
68
62
 
69
63
  def scaffold(*names)
70
64
  names.each { |name| generate_default_scaffold(name) }
@@ -95,23 +89,21 @@ class ScaffoldingCommands < Thor
95
89
  end
96
90
  end
97
91
 
98
- def generate_scaffolding(name, type, path, uses: nil)
92
+ def generate_scaffolding(name, type, path)
99
93
  path ||= load_config_path(type)
100
94
  scaffolding = Scaffolding.new([name, path])
101
- scaffolding.uses = Array(uses) if uses
102
95
  scaffolding.send("generate_#{type}")
103
96
  end
104
97
 
105
98
  def generate_default_scaffold(name)
106
99
  validate_project!
107
- uses = options[:uses]
108
100
  if Pathname.new('spec').exist? && !Pathname.new('features').exist?
109
- generate_scaffolding(name, 'spec', load_config_path('spec'), uses:)
101
+ generate_scaffolding(name, 'spec', load_config_path('spec'))
110
102
  else
111
103
  generate_scaffolding(name, 'feature', load_config_path('feature'))
112
- generate_scaffolding(name, 'steps', load_config_path('steps'), uses:)
104
+ generate_scaffolding(name, 'steps', load_config_path('steps'))
113
105
  end
114
- generate_scaffolding(name, 'page', load_config_path('page'), uses:)
106
+ generate_scaffolding(name, 'page', load_config_path('page'))
115
107
  end
116
108
  end
117
109
  end
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Given('I am on the home page') do
4
- <% if watir? -%>
5
- browser.goto 'https://raider-test-site.onrender.com/'
6
- <% else -%>
7
- driver.get 'https://raider-test-site.onrender.com/'
8
- <% end -%>
4
+ <%- if watir? -%>
5
+ browser.goto 'https://raider-test-site.onrender.com/'
6
+ <%- else -%>
7
+ driver.get 'https://raider-test-site.onrender.com/'
8
+ <%- end -%>
9
9
  end
10
10
 
11
11
  Then('the page should be accessible') do
12
- <% if watir? -%>
13
- expect(browser.driver).to be_axe_clean
14
- <% else -%>
15
- expect(driver).to be_axe_clean
16
- <% end -%>
12
+ <%- if watir? -%>
13
+ expect(browser.driver).to be_axe_clean
14
+ <%- else -%>
15
+ expect(driver).to be_axe_clean
16
+ <%- end -%>
17
17
  end
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Uncomment for interactive debugging:
4
- # require 'debug' # Ruby's built-in debugger (binding.break)
5
- # require 'pry' # Pry debugger (binding.pry)
6
-
7
3
  require_relative '../../page_objects/pages/account'
8
4
  require_relative '../../page_objects/pages/login'
9
5
 
@@ -14,7 +10,6 @@ Given("I'm a {user} on the login page") do |user|
14
10
  end
15
11
 
16
12
  When('I login with my credentials') do
17
- # binding.break # Uncomment to pause here and inspect state
18
13
  @login_page.login(@user['username'], @user['password'])
19
14
  end
20
15
 
@@ -71,6 +71,11 @@ class Generator < Thor::Group
71
71
  args.include?('selenium')
72
72
  end
73
73
 
74
+ def ruby_raider_version_requirement
75
+ major, minor = File.read(File.expand_path('../version', __dir__)).strip.split('.')
76
+ "~> #{major}.#{minor}"
77
+ end
78
+
74
79
  private
75
80
 
76
81
  def _initializer
@@ -1,6 +1,9 @@
1
1
  name: <%- if framework == 'cucumber' -%>Cucumber Tests<%- else -%>Rspec Tests<%- end -%>
2
2
 
3
3
  on:
4
+ push:
5
+ branches: [ main, master ]
6
+ pull_request:
4
7
  workflow_dispatch:
5
8
  inputs:
6
9
  browser:
@@ -37,6 +40,21 @@ jobs:
37
40
  run: <%- if framework == 'cucumber' -%>bundle exec cucumber features --format pretty<%- else -%>bundle exec rspec spec --format documentation<%- end %>
38
41
  env:
39
42
  HEADLESS: true
43
+ <%- unless framework == 'cucumber' -%>
44
+ RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
45
+ RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
46
+ <%- end -%>
47
+
48
+ <%- unless framework == 'cucumber' -%>
49
+ - name: Upload Raider results
50
+ uses: actions/upload-artifact@v4
51
+ if: always()
52
+ with:
53
+ name: raider-results
54
+ path: raider-results/
55
+ if-no-files-found: ignore
56
+
57
+ <%- end -%>
40
58
 
41
59
  - name: Get Allure history
42
60
  uses: actions/checkout@v2
@@ -2,9 +2,9 @@ name: Appium Tests
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ main ]
5
+ branches: [ main, master ]
6
6
  pull_request:
7
- branches: [ main ]
7
+ workflow_dispatch:
8
8
 
9
9
  jobs:
10
10
  <%- if ios? || cross_platform? -%>
@@ -52,6 +52,18 @@ jobs:
52
52
  <%- end -%>
53
53
  env:
54
54
  APP_PATH: ${{ github.workspace }}/apps/RaiderTestApp.app
55
+ <%- unless cucumber? -%>
56
+ RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
57
+ RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
58
+
59
+ - name: Upload Raider results
60
+ uses: actions/upload-artifact@v4
61
+ if: always()
62
+ with:
63
+ name: raider-results-ios
64
+ path: raider-results/
65
+ if-no-files-found: ignore
66
+ <%- end -%>
55
67
  <%- end -%>
56
68
 
57
69
  <%- if android? || cross_platform? -%>
@@ -101,4 +113,16 @@ jobs:
101
113
  <%- end -%>
102
114
  env:
103
115
  APP_PATH: ${{ github.workspace }}/apps/app-release.apk
116
+ <%- unless cucumber? -%>
117
+ RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
118
+ RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
119
+
120
+ - name: Upload Raider results
121
+ uses: actions/upload-artifact@v4
122
+ if: always()
123
+ with:
124
+ name: raider-results-android
125
+ path: raider-results/
126
+ if-no-files-found: ignore
127
+ <%- end -%>
104
128
  <%- end -%>
@@ -6,10 +6,6 @@ require_relative '../models/model_factory'
6
6
  require_relative '../page_objects/pages/account'
7
7
  require_relative '../page_objects/pages/login'
8
8
 
9
- # Uncomment for interactive debugging:
10
- # require 'debug' # Ruby's built-in debugger (binding.break)
11
- # require 'pry' # Pry debugger (binding.pry)
12
-
13
9
  describe 'Login' do
14
10
  subject(:header) { account_page.header.customer_name }
15
11
 
@@ -26,7 +22,6 @@ describe 'Login' do
26
22
  let(:password) { user['password'] }
27
23
 
28
24
  it 'can successfully log in' do
29
- # binding.break # Uncomment to pause here and inspect state
30
25
  account_page.visit
31
26
  expect(header).to eq "Welcome back #{user['name']}"
32
27
  end
@@ -20,6 +20,7 @@ gem 'parallel_tests'
20
20
  gem 'rake'
21
21
  gem 'reek'
22
22
  <%- if rspec? -%>
23
+ gem 'raider_reporter', '~> 0.2'
23
24
  gem 'rspec-retry'
24
25
  <%- end -%>
25
26
  gem '<%= framework %>'
@@ -30,14 +31,10 @@ gem 'rubocop'
30
31
  <%- if rspec? -%>
31
32
  gem 'rubocop-rspec'
32
33
  <%- end -%>
33
- gem 'ruby_raider', '~> 1.1.0'
34
+ gem 'ruby_raider', '<%= ruby_raider_version_requirement %>'
34
35
  <%- if selenium_based? %>
35
36
  gem 'selenium-webdriver'
36
37
  <%- end -%>
37
38
  <%- if watir? -%>
38
39
  gem 'watir'
39
40
  <%- end -%>
40
- <%- if web? -%>
41
- gem 'debug', '>= 1.0'
42
- gem 'pry'
43
- <%- end -%>
@@ -1,4 +1,5 @@
1
1
  allure-results
2
2
  <%- if rspec? -%>
3
+ raider-results
3
4
  spec/examples.txt
4
5
  <%- end -%>
@@ -82,6 +82,23 @@ raider g steps [NAME] # Create step definitions
82
82
  raider g scaffold [NAME] # Create page + test + steps
83
83
  ```
84
84
 
85
+ <%- if rspec? -%>
86
+ ## Reporting
87
+
88
+ Every run writes machine-readable results to `raider-results/` (including `run.json`), next to the usual Allure results.
89
+ Allure reports are still produced.
90
+
91
+ To upload runs to Raider, set these environment variables before running the suite:
92
+
93
+ ```bash
94
+ export RAIDER_TOKEN=your-token
95
+ export RAIDER_ENDPOINT=https://your-raider-endpoint
96
+ ```
97
+
98
+ Without `RAIDER_TOKEN`, nothing is uploaded and the suite output is unchanged. Failure screenshots are attached to both
99
+ Allure and Raider. See the [raider_reporter README](https://github.com/RaiderHQ/raider_reporter) for details.
100
+
101
+ <%- end -%>
85
102
  ### Sponsors
86
103
 
87
104
  This project is tested with BrowserStack.
@@ -1,7 +1,7 @@
1
1
  <% if selenium_based? %>
2
- screenshot = driver.save_screenshot("#{temp_folder}/#{example_name}.png")
2
+ screenshot = driver.save_screenshot(screenshot_path)
3
3
  <% elsif watir? %>
4
- screenshot = browser.screenshot.save("#{temp_folder}/#{example_name}.png")
4
+ screenshot = browser.screenshot.save(screenshot_path)
5
5
  <% else %>
6
- screenshot = driver.screenshot("#{temp_folder}/#{example_name}.png")
6
+ screenshot = driver.screenshot(screenshot_path)
7
7
  <% end %>
@@ -5,6 +5,8 @@ require 'axe-rspec'
5
5
  <%- end -%>
6
6
  require 'rspec'
7
7
  require 'rspec/retry'
8
+ require 'digest'
9
+ require 'raider_reporter/rspec'
8
10
  require 'tmpdir'
9
11
  require_relative 'allure_helper'
10
12
  <%- if watir? -%>
@@ -21,7 +23,8 @@ module SpecHelper
21
23
  config.verbose_retry = true
22
24
  config.display_try_failure_messages = true
23
25
  config.default_retry_count = ENV.fetch('RETRY_COUNT', 0).to_i
24
- config.formatter = AllureHelper.formatter
26
+ config.add_formatter(AllureHelper.formatter)
27
+ config.add_formatter(RaiderReporter::RSpec::Formatter)
25
28
  <%- if watir? -%>
26
29
  config.include(BrowserHelper)
27
30
  <%- else -%>
@@ -53,10 +56,12 @@ module SpecHelper
53
56
  example_name = example.description
54
57
  if example.exception
55
58
  @_exception = example.exception
56
- end
57
- Dir.mktmpdir do |temp_folder|
58
- <%= partial('screenshot', strip: true) %>
59
- AllureHelper.add_screenshot(example_name, screenshot)
59
+ Dir.mktmpdir do |temp_folder|
60
+ screenshot_path = "#{temp_folder}/#{example_name}.png"
61
+ <%= partial('screenshot', strip: true) %>
62
+ AllureHelper.add_screenshot(example_name, screenshot)
63
+ RaiderReporter.attach('screenshot', screenshot_path, type: :png)
64
+ end
60
65
  end
61
66
  <%= partial('quit_driver', strip: true) %>
62
67
  end
@@ -74,6 +74,7 @@ module TestHelper
74
74
  <%- end -%>
75
75
  <%- end -%>
76
76
  Dir.mktmpdir do |temp_folder|
77
+ screenshot_path = "#{temp_folder}/#{example_name}.png"
77
78
  <%= partial('screenshot', strip: true) %>
78
79
  <%- if allure_reporter? -%>
79
80
  AllureHelper.add_screenshot(example_name, screenshot)
@@ -11,8 +11,6 @@ class Scaffolding < Thor::Group
11
11
  argument :name, optional: true
12
12
  argument :path, optional: true
13
13
 
14
- attr_writer :uses
15
-
16
14
  def self.source_root
17
15
  "#{File.dirname(__FILE__)}/templates"
18
16
  end
@@ -94,10 +92,6 @@ class Scaffolding < Thor::Group
94
92
  automation_type == 'watir'
95
93
  end
96
94
 
97
- def uses_list
98
- Array(@uses).reject(&:empty?)
99
- end
100
-
101
95
  def default_path(standard_path, file_type)
102
96
  path ? "#{path}/#{normalized_name}#{file_type}" : standard_path
103
97
  end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../abstract/page'
4
- <%- uses_list.each do |dep| -%>
5
- require_relative '<%= dep %>'
6
- <%- end -%>
7
4
  <% if nested? -%>
8
5
  <% module_parts.each_with_index do |mod, i| -%>
9
6
  <%= ' ' * i %>module <%= mod %>
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../page_objects/pages/<%= normalized_name %>'
4
- <%- uses_list.each do |dep| -%>
5
- require_relative '../page_objects/pages/<%= dep %>'
6
- <%- end -%>
7
4
 
8
5
  describe '<%= page_class_name %>' do
9
6
  <%- if selenium? -%>
@@ -11,9 +8,6 @@ describe '<%= page_class_name %>' do
11
8
  <%- elsif watir? -%>
12
9
  let(:page) { <%= page_class_name %>.new(browser) }
13
10
  <%- end -%>
14
- <%- uses_list.each do |dep| -%>
15
- let(:<%= dep %>) { <%= NameNormalizer.to_page_class(dep) %>.new<%= selenium? ? '(driver)' : '(browser)' %> }
16
- <%- end -%>
17
11
  <%- if project_config['url'] -%>
18
12
 
19
13
  before do
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../page_objects/pages/<%= normalized_name %>'
4
- <%- uses_list.each do |dep| -%>
5
- require_relative '../../page_objects/pages/<%= dep %>'
6
- <%- end -%>
7
4
 
8
5
  Given('I am on the <%= normalized_name.tr("_", " ") %> page') do
9
6
  <%- if selenium? -%>
data/lib/version CHANGED
@@ -1 +1 @@
1
- 3.2.0
1
+ 3.3.0
@@ -11,6 +11,6 @@ class ScaffoldingCommands < Thor
11
11
  private
12
12
 
13
13
  def load_config_path: (String type) -> String?
14
- def generate_scaffolding: (String name, String type, String? path, ?uses: Array[String]?) -> void
14
+ def generate_scaffolding: (String name, String type, String? path) -> void
15
15
  def generate_default_scaffold: (String name) -> void
16
16
  end
@@ -2,8 +2,6 @@
2
2
  class Scaffolding < Thor::Group
3
3
  include Thor::Actions
4
4
 
5
- attr_writer uses: Array[String]
6
-
7
5
  def self.source_root: () -> String
8
6
 
9
7
  # Generation methods
@@ -27,6 +25,5 @@ class Scaffolding < Thor::Group
27
25
  def project_config: () -> Hash[String, untyped]
28
26
  def selenium?: () -> bool
29
27
  def watir?: () -> bool
30
- def uses_list: () -> Array[String]
31
28
  def default_path: (String standard_path, String file_type) -> String
32
29
  end
@@ -44,21 +44,106 @@ describe 'CI pipeline content' do
44
44
  end
45
45
  end
46
46
 
47
+ shared_examples 'github triggers' do |project_name, file|
48
+ subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") }
49
+
50
+ let(:triggers) { YAML.safe_load(pipeline)[true] }
51
+
52
+ it 'triggers on push to main and master' do
53
+ expect(triggers['push']['branches']).to include('main', 'master')
54
+ end
55
+
56
+ it 'triggers on pull_request' do
57
+ expect(triggers).to have_key('pull_request')
58
+ end
59
+
60
+ it 'triggers on workflow_dispatch' do
61
+ expect(triggers).to have_key('workflow_dispatch')
62
+ end
63
+ end
64
+
65
+ shared_examples 'github raider wiring' do |project_name, file|
66
+ subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") }
67
+
68
+ let(:steps) { YAML.safe_load(pipeline)['jobs'].values.flat_map { |job| job['steps'] } }
69
+
70
+ let(:test_steps) do
71
+ steps.select { |step| step['run'].to_s.include?('rspec') || step['with'].to_h['script'].to_s.include?('rspec') }
72
+ end
73
+
74
+ it 'passes Raider settings to every test step' do
75
+ expect(test_steps).not_to be_empty
76
+ test_steps.each do |step|
77
+ expect(step['env']).to include('RAIDER_TOKEN' => '${{ secrets.RAIDER_TOKEN }}',
78
+ 'RAIDER_ENDPOINT' => '${{ vars.RAIDER_ENDPOINT }}')
79
+ end
80
+ end
81
+
82
+ it 'uploads raider-results as an artifact even on failure in every job' do
83
+ uploads = steps.select { |step| step['uses'] == 'actions/upload-artifact@v4' }
84
+ expect(uploads.size).to eq(YAML.safe_load(pipeline)['jobs'].size)
85
+ uploads.each do |upload|
86
+ expect(upload['with']['path']).to eq('raider-results/')
87
+ expect(upload['if']).to eq('always()')
88
+ end
89
+ end
90
+ end
91
+
92
+ shared_examples 'github without raider wiring' do |project_name, file|
93
+ subject(:pipeline) { read_generated(project_name, ".github/workflows/#{file}") }
94
+
95
+ it 'has no Raider settings or artifact' do
96
+ expect(pipeline).not_to include('RAIDER_')
97
+ expect(pipeline).not_to include('raider-results')
98
+ end
99
+ end
100
+
101
+ shared_examples 'github allure steps' do |project_name|
102
+ subject(:pipeline) { read_generated(project_name, '.github/workflows/test_pipeline.yml') }
103
+
104
+ it 'keeps the Allure steps' do
105
+ expect(pipeline).to include('simple-elf/allure-report-action', 'peaceiris/actions-gh-pages')
106
+ end
107
+ end
108
+
47
109
  context 'with rspec and selenium' do
48
110
  name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
49
111
  include_examples 'valid github pipeline', name
50
112
  include_examples 'github rspec pipeline', name
113
+ include_examples 'github triggers', name, 'test_pipeline.yml'
114
+ include_examples 'github raider wiring', name, 'test_pipeline.yml'
115
+ include_examples 'github allure steps', name
51
116
  end
52
117
 
53
118
  context 'with cucumber and selenium' do
54
119
  name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
55
120
  include_examples 'valid github pipeline', name
56
121
  include_examples 'github cucumber pipeline', name
122
+ include_examples 'github triggers', name, 'test_pipeline.yml'
123
+ include_examples 'github without raider wiring', name, 'test_pipeline.yml'
124
+ include_examples 'github allure steps', name
57
125
  end
58
126
 
59
127
  context 'with rspec and watir' do
60
128
  name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::WATIR}"
61
129
  include_examples 'valid github pipeline', name
62
130
  include_examples 'github rspec pipeline', name
131
+ include_examples 'github triggers', name, 'test_pipeline.yml'
132
+ include_examples 'github raider wiring', name, 'test_pipeline.yml'
133
+ include_examples 'github allure steps', name
134
+ end
135
+
136
+ %w[android ios cross_platform].each do |automation|
137
+ context "with rspec and #{automation}" do
138
+ name = "#{FrameworkIndex::RSPEC}_#{automation}"
139
+ include_examples 'github triggers', name, 'appium_pipeline.yml'
140
+ include_examples 'github raider wiring', name, 'appium_pipeline.yml'
141
+ end
142
+
143
+ context "with cucumber and #{automation}" do
144
+ name = "#{FrameworkIndex::CUCUMBER}_#{automation}"
145
+ include_examples 'github triggers', name, 'appium_pipeline.yml'
146
+ include_examples 'github without raider wiring', name, 'appium_pipeline.yml'
147
+ end
63
148
  end
64
149
  end
@@ -89,6 +89,10 @@ describe 'Common file content' do
89
89
  it 'uses documentation format' do
90
90
  expect(rspec_file).to include('--format documentation')
91
91
  end
92
+
93
+ it 'leaves the raider formatter to spec_helper, since a command-line --format replaces .rspec formatters' do
94
+ expect(rspec_file).not_to include('RaiderReporter')
95
+ end
92
96
  end
93
97
 
94
98
  shared_examples 'no rspec dotfile' do |project_name|
@@ -109,6 +113,10 @@ describe 'Common file content' do
109
113
  it 'includes spec/examples.txt' do
110
114
  expect(gitignore).to include('spec/examples.txt')
111
115
  end
116
+
117
+ it 'includes raider-results' do
118
+ expect(gitignore).to include('raider-results')
119
+ end
112
120
  end
113
121
 
114
122
  shared_examples 'non-rspec gitignore' do |project_name|
@@ -121,6 +129,10 @@ describe 'Common file content' do
121
129
  it 'does not include spec/examples.txt' do
122
130
  expect(gitignore).not_to include('spec/examples.txt')
123
131
  end
132
+
133
+ it 'does not include raider-results' do
134
+ expect(gitignore).not_to include('raider-results')
135
+ end
124
136
  end
125
137
 
126
138
  # --- Spec helper retry config (C4) ---
@@ -220,5 +232,4 @@ describe 'Common file content' do
220
232
  it_behaves_like 'gemfile without rspec-retry', name
221
233
  it_behaves_like 'cucumber config with retry', name
222
234
  end
223
-
224
235
  end
@@ -91,24 +91,11 @@ describe 'Gemfile content' do
91
91
  end
92
92
  end
93
93
 
94
- shared_examples 'contains debug gems' do |project_name|
95
- subject(:gemfile) { read_generated(project_name, 'Gemfile') }
96
-
97
- it 'includes debug gem' do
98
- expect(gemfile).to include("gem 'debug'")
99
- end
100
-
101
- it 'includes pry gem' do
102
- expect(gemfile).to include("gem 'pry'")
103
- end
104
- end
105
-
106
94
  context 'with rspec and selenium' do
107
95
  name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
108
96
  include_examples 'contains common gems', name
109
97
  include_examples 'contains rspec gems', name
110
98
  include_examples 'contains selenium gems', name
111
- include_examples 'contains debug gems', name
112
99
  end
113
100
 
114
101
  context 'with rspec and watir' do
@@ -116,7 +103,6 @@ describe 'Gemfile content' do
116
103
  include_examples 'contains common gems', name
117
104
  include_examples 'contains rspec gems', name
118
105
  include_examples 'contains watir gems', name
119
- include_examples 'contains debug gems', name
120
106
  end
121
107
 
122
108
  context 'with cucumber and selenium' do
@@ -124,7 +110,6 @@ describe 'Gemfile content' do
124
110
  include_examples 'contains common gems', name
125
111
  include_examples 'contains cucumber gems', name
126
112
  include_examples 'contains selenium gems', name
127
- include_examples 'contains debug gems', name
128
113
  end
129
114
 
130
115
  context 'with cucumber and watir' do
@@ -132,7 +117,44 @@ describe 'Gemfile content' do
132
117
  include_examples 'contains common gems', name
133
118
  include_examples 'contains cucumber gems', name
134
119
  include_examples 'contains watir gems', name
135
- include_examples 'contains debug gems', name
120
+ end
121
+
122
+ describe 'raider_reporter and ruby_raider pin' do
123
+ major_minor = File.read('lib/version').strip.split('.').first(2).join('.')
124
+
125
+ AUTOMATION_TYPES.each do |automation|
126
+ context "with rspec and #{automation}" do
127
+ let(:gemfile) { read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'Gemfile') }
128
+
129
+ it 'includes raider_reporter' do
130
+ expect(gemfile).to include("gem 'raider_reporter', '~> 0.2'")
131
+ end
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'")
135
+ end
136
+
137
+ it 'pins ruby_raider to the generator major.minor' do
138
+ expect(gemfile).to include("gem 'ruby_raider', '~> #{major_minor}'")
139
+ end
140
+ end
141
+
142
+ context "with cucumber and #{automation}" do
143
+ let(:gemfile) { read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'Gemfile') }
144
+
145
+ it 'does not include raider_reporter' do
146
+ expect(gemfile).not_to include('raider_reporter')
147
+ end
148
+
149
+ it 'keeps allure-cucumber' do
150
+ expect(gemfile).to include("gem 'allure-cucumber', '2.28.0'")
151
+ end
152
+
153
+ it 'pins ruby_raider to the generator major.minor' do
154
+ expect(gemfile).to include("gem 'ruby_raider', '~> #{major_minor}'")
155
+ end
156
+ end
157
+ end
136
158
  end
137
159
 
138
160
  end
@@ -109,6 +109,47 @@ describe 'Helper file content' do
109
109
  end
110
110
  end
111
111
 
112
+ shared_examples 'raider reporter spec helper' do |project_name, screenshot_call|
113
+ subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') }
114
+
115
+ it 'requires raider_reporter/rspec' do
116
+ expect(helper).to include("require 'raider_reporter/rspec'")
117
+ end
118
+
119
+ it 'requires digest, which allure-ruby-commons uses without requiring' do
120
+ expect(helper).to include("require 'digest'")
121
+ end
122
+
123
+ it 'adds the raider formatter in spec_helper so a command-line --format cannot drop it' do
124
+ expect(helper).to include('config.add_formatter(RaiderReporter::RSpec::Formatter)')
125
+ end
126
+
127
+ it 'does not replace the formatter' do
128
+ expect(helper).not_to match(/config\.formatter\s*=/)
129
+ end
130
+
131
+ it 'adds the allure formatter' do
132
+ expect(helper).to include('config.add_formatter(AllureHelper.formatter)')
133
+ end
134
+
135
+ it 'screenshots only on failure' do
136
+ lines = helper.lines
137
+ start = lines.index { |line| line.include?('if example.exception') }
138
+ expect(start).not_to be_nil
139
+ indent = lines[start][/\A */].size
140
+ body = lines[(start + 1)..].take_while { |line| line.strip.empty? || line[/\A */].size > indent }
141
+ guarded = body.join
142
+
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)
145
+ expect(lines.select { |line| line.include?(screenshot_call) }.size).to eq(1)
146
+ end
147
+
148
+ it 'attaches the screenshot to raider by path' do
149
+ expect(helper).to include("RaiderReporter.attach('screenshot', screenshot_path, type: :png)")
150
+ end
151
+ end
152
+
112
153
  shared_examples 'selenium spec helper' do |project_name|
113
154
  subject(:helper) { read_generated(project_name, 'helpers/spec_helper.rb') }
114
155
 
@@ -200,4 +241,25 @@ describe 'Helper file content' do
200
241
  name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
201
242
 
202
243
  end
244
+
245
+ # --- Raider reporter wiring (every rspec automation) ---
246
+
247
+ AUTOMATION_TYPES.each do |automation|
248
+ context "with rspec and #{automation} raider reporter wiring" do
249
+ name = "#{FrameworkIndex::RSPEC}_#{automation}"
250
+ screenshot_call = {
251
+ 'selenium' => 'driver.save_screenshot(screenshot_path)',
252
+ 'watir' => 'browser.screenshot.save(screenshot_path)'
253
+ }.fetch(automation, 'driver.screenshot(screenshot_path)')
254
+ it_behaves_like 'raider reporter spec helper', name, screenshot_call
255
+ end
256
+
257
+ context "with cucumber and #{automation}" do
258
+ it 'has no raider reporter wiring' do
259
+ project = "#{FrameworkIndex::CUCUMBER}_#{automation}"
260
+ files = Dir.glob("#{project}/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) }
261
+ expect(files.select { |f| File.read(f).match?(/raider_reporter|RaiderReporter/) }).to be_empty
262
+ end
263
+ end
264
+ end
203
265
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'content_helper'
4
+
5
+ describe 'Generated Readme content' do
6
+ AUTOMATION_TYPES.each do |automation|
7
+ context "with rspec and #{automation}" do
8
+ subject(:readme) { read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'Readme.md') }
9
+
10
+ it 'has a Reporting section' do
11
+ expect(readme).to include('## Reporting')
12
+ end
13
+
14
+ it 'documents local results, upload settings and the raider_reporter README' do
15
+ expect(readme).to include('raider-results/', 'RAIDER_TOKEN', 'RAIDER_ENDPOINT',
16
+ 'https://github.com/RaiderHQ/raider_reporter')
17
+ end
18
+
19
+ it 'says Allure is still produced' do
20
+ expect(readme).to match(/Allure/)
21
+ end
22
+ end
23
+
24
+ context "with cucumber and #{automation}" do
25
+ it 'has no Reporting section' do
26
+ readme = read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'Readme.md')
27
+ expect(readme).not_to include('RAIDER_TOKEN')
28
+ end
29
+ end
30
+ end
31
+ end
@@ -3,6 +3,7 @@
3
3
  require_relative 'spec_helper'
4
4
  require 'timeout'
5
5
  require 'open3'
6
+ require 'json'
6
7
 
7
8
  # End-to-end integration tests that verify generated projects actually work
8
9
  # These tests:
@@ -41,9 +42,19 @@ describe 'End-to-End Project Generation and Execution' do
41
42
  result
42
43
  end
43
44
 
45
+ # The generated Gemfile pins ruby_raider to this gem's major.minor, which may not be
46
+ # published yet (a release candidate branch). Resolve it from this checkout instead;
47
+ # the pin itself is covered by the Gemfile content specs.
48
+ def use_local_ruby_raider(project_name)
49
+ gemfile = File.join(project_name, 'Gemfile')
50
+ root = File.expand_path('../..', __dir__)
51
+ File.write(gemfile, File.read(gemfile).sub(/^gem 'ruby_raider'.*$/, "gem 'ruby_raider', path: '#{root}'"))
52
+ end
53
+
44
54
  # Helper to install dependencies
45
55
  def bundle_install(project_name)
46
56
  puts "\n📦 Installing dependencies for #{project_name}..."
57
+ use_local_ruby_raider(project_name)
47
58
  result = run_in_project(project_name, 'bundle install --quiet', timeout: 180)
48
59
 
49
60
  unless result[:success]
@@ -103,6 +114,25 @@ describe 'End-to-End Project Generation and Execution' do
103
114
  "STDOUT: #{result[:stdout]}\n" \
104
115
  "STDERR: #{result[:stderr]}"
105
116
  end
117
+
118
+ it 'writes raider-results/run.json listing every example without changing the exit status', :aggregate_failures, :slow do
119
+ skip 'Bundle install failed' unless bundle_install(project)
120
+
121
+ FileUtils.rm_rf(File.join(project, 'raider-results'))
122
+ result = run_rspec(project)
123
+ summary = result[:stdout].match(/(\d+) examples?, (\d+) failures?/)
124
+ expect(summary).not_to be_nil, "No RSpec summary line.\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
125
+ examples = summary[1].to_i
126
+ failures = summary[2].to_i
127
+
128
+ run_file = File.join(project, 'raider-results', 'run.json')
129
+ expect(File).to exist(run_file)
130
+ run = JSON.parse(File.read(run_file))
131
+ expect(run['schema_version']).to eq(1)
132
+ expect(run['results'].size).to eq(examples)
133
+ expect(run['results'].count { |r| r['status'] == 'failed' }).to eq(failures)
134
+ expect(result[:exit_code]).to eq(failures.zero? ? 0 : 1)
135
+ end
106
136
  end
107
137
 
108
138
  # Shared example for Cucumber-based projects
@@ -142,7 +172,6 @@ describe 'End-to-End Project Generation and Execution' do
142
172
  describe 'Watir + Cucumber' do
143
173
  include_examples 'executable cucumber project', 'cucumber_watir'
144
174
  end
145
-
146
175
  end
147
176
 
148
177
  # Mobile and Visual frameworks require external services, so we only verify structure
@@ -497,43 +497,6 @@ describe 'Scaffolding E2E' do # rubocop:disable RSpec/DescribeClass
497
497
  end
498
498
  end
499
499
 
500
- # ── Relationships ──────────────────────
501
-
502
- describe 'relationships' do
503
- before do
504
- File.write('page_objects/pages/login.rb', "class LoginPage < Page; end\n")
505
- end
506
-
507
- after do
508
- FileUtils.rm_f('page_objects/pages/dashboard.rb')
509
- FileUtils.rm_f('spec/dashboard_page_spec.rb')
510
- FileUtils.rm_f('features/dashboard.feature')
511
- FileUtils.rm_f('features/step_definitions/dashboard_steps.rb')
512
- FileUtils.rm_f('page_objects/pages/login.rb')
513
- FileUtils.rm_rf('features') unless framework == 'cucumber'
514
- FileUtils.rm_rf('spec') unless framework == 'rspec'
515
- end
516
-
517
- it 'adds require_relative for dependent page' do
518
- scaffold.new.invoke(:page, nil, %w[dashboard --uses login])
519
- content = File.read('page_objects/pages/dashboard.rb')
520
- expect(content).to include("require_relative 'login'")
521
- end
522
-
523
- if style == :spec
524
- it 'adds dependency to spec let declarations' do
525
- scaffold.new.invoke(:spec, nil, %w[dashboard --uses login])
526
- content = File.read('spec/dashboard_page_spec.rb')
527
- expect(content).to include('LoginPage')
528
-
529
- case automation
530
- when 'selenium' then expect(content).to include('LoginPage.new(driver)')
531
- when 'watir' then expect(content).to include('LoginPage.new(browser)')
532
- end
533
- end
534
- end
535
- end
536
-
537
500
  end
538
501
  end
539
502
  end
@@ -93,26 +93,6 @@ RSpec.describe 'Scaffolding features' do
93
93
  end
94
94
  end
95
95
 
96
- # --- Feature 9: Relationships ---
97
-
98
- describe 'relationships (--uses)' do
99
- before do
100
- File.write('page_objects/pages/login.rb', "class LoginPage < Page; end\n")
101
- end
102
-
103
- it 'adds require_relative for dependent pages' do
104
- scaffold.new.invoke(:page, nil, %w[dashboard --uses login])
105
- content = File.read('page_objects/pages/dashboard.rb')
106
- expect(content).to include("require_relative 'login'")
107
- end
108
-
109
- it 'adds uses to spec as let declarations' do
110
- scaffold.new.invoke(:spec, nil, %w[dashboard --uses login])
111
- content = File.read('spec/dashboard_page_spec.rb')
112
- expect(content).to include('LoginPage')
113
- end
114
- end
115
-
116
96
  # --- Feature 1: Batch scaffold ---
117
97
 
118
98
  describe 'batch scaffolding' do
@@ -15,6 +15,7 @@ module SystemTestHelper
15
15
 
16
16
  Bundler.with_unbundled_env do
17
17
  Dir.chdir(project) do
18
+ use_checkout_ruby_raider
18
19
  result = run_command('bundle install --quiet')
19
20
  return result unless result[:success]
20
21
 
@@ -25,6 +26,13 @@ module SystemTestHelper
25
26
 
26
27
  private
27
28
 
29
+ # The generated Gemfile pins ruby_raider to this gem's major.minor, which may not be
30
+ # published yet. Resolve it from this checkout; the pin itself is covered by content specs.
31
+ def use_checkout_ruby_raider(root = File.expand_path('../../..', __dir__))
32
+ gemfile = File.read('Gemfile')
33
+ File.write('Gemfile', gemfile.sub(/^gem 'ruby_raider'.*$/, "gem 'ruby_raider', path: '#{root}'"))
34
+ end
35
+
28
36
  def run_command(command, env: {})
29
37
  stdout, stderr, status = Open3.capture3(env, command)
30
38
  { success: status.success?, stdout:, stderr: }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_raider
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Agustin Pequeno
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-14 00:00:00.000000000 Z
10
+ date: 2026-10-11 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake
@@ -309,6 +309,7 @@ files:
309
309
  - spec/integration/content/gemfile_content_spec.rb
310
310
  - spec/integration/content/helper_content_spec.rb
311
311
  - spec/integration/content/page_content_spec.rb
312
+ - spec/integration/content/readme_content_spec.rb
312
313
  - spec/integration/content/syntax_validation_spec.rb
313
314
  - spec/integration/content/test_content_spec.rb
314
315
  - spec/integration/end_to_end_features_spec.rb