ruby_raider 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +18 -29
- data/.github/workflows/security.yml +68 -0
- data/.rubocop.yml +28 -0
- data/README.md +22 -4
- data/docs/superpowers/plans/2026-10-25-failure-video-replay.md +400 -0
- data/docs/superpowers/plans/2026-10-26-parity-cleanup.md +233 -0
- data/docs/superpowers/plans/2026-10-28-failure-card.md +295 -0
- data/docs/superpowers/specs/2026-10-25-failure-video-replay-design.md +214 -0
- data/docs/superpowers/specs/2026-10-28-failure-card-design.md +210 -0
- data/lib/commands/scaffolding_commands.rb +3 -3
- data/lib/commands/utility_commands.rb +36 -6
- data/lib/generators/cucumber/templates/cucumber.tt +1 -1
- data/lib/generators/cucumber/templates/partials/appium_env.tt +6 -0
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +6 -0
- data/lib/generators/cucumber/templates/partials/watir_env.tt +6 -0
- data/lib/generators/helper_generator.rb +5 -1
- data/lib/generators/infrastructure/azure_generator.rb +1 -0
- data/lib/generators/infrastructure/github_generator.rb +1 -0
- data/lib/generators/infrastructure/templates/azure_pipelines.tt +12 -1
- data/lib/generators/infrastructure/templates/github.tt +12 -0
- data/lib/generators/infrastructure/templates/github_appium.tt +23 -0
- data/lib/generators/invoke_generators.rb +20 -2
- data/lib/generators/rspec/rspec_generator.rb +0 -1
- data/lib/generators/templates/common/gemfile.tt +1 -1
- data/lib/generators/templates/common/git_ignore.tt +3 -0
- data/lib/generators/templates/common/partials/mobile_config.tt +5 -0
- data/lib/generators/templates/common/partials/web_config.tt +6 -1
- data/lib/generators/templates/common/read_me.tt +60 -2
- data/lib/generators/templates/helpers/browser_helper.tt +5 -1
- data/lib/generators/templates/helpers/driver_helper.tt +1 -0
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +4 -1
- data/lib/generators/templates/helpers/spec_helper.tt +8 -0
- data/lib/generators/templates/helpers/video_helper.tt +338 -171
- data/lib/ruby_raider/ffmpeg.rb +280 -0
- data/lib/ruby_raider.rb +2 -3
- data/lib/scaffolding/project_detector.rb +1 -3
- data/lib/scaffolding/scaffolding.rb +0 -1
- data/lib/utilities/utilities.rb +15 -2
- data/lib/version +1 -1
- data/ruby_raider.gemspec +3 -1
- data/sig/commands/utility_commands.rbs +4 -1
- data/sig/generators/invoke_generators.rbs +2 -3
- data/sig/ruby_raider/ffmpeg.rbs +23 -0
- data/sig/utilities/utilities.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +56 -1
- data/spec/generators/fixtures/video_helper_stubs/selenium-webdriver.rb +28 -0
- data/spec/generators/video_helper_spec.rb +608 -0
- data/spec/integration/commands/browser_update_after_creation_spec.rb +7 -6
- data/spec/integration/commands/utility_commands_spec.rb +2 -2
- data/spec/integration/content/azure_content_spec.rb +29 -0
- data/spec/integration/content/ci_content_spec.rb +43 -0
- data/spec/integration/content/common_content_spec.rb +8 -0
- data/spec/integration/content/config_content_spec.rb +14 -0
- data/spec/integration/content/cucumber_reporter_content_spec.rb +22 -1
- data/spec/integration/content/gemfile_content_spec.rb +2 -3
- data/spec/integration/content/helper_content_spec.rb +146 -0
- data/spec/integration/content/page_content_spec.rb +0 -1
- data/spec/integration/content/readme_content_spec.rb +51 -0
- data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
- data/spec/integration/content/test_content_spec.rb +2 -3
- data/spec/integration/end_to_end_features_spec.rb +0 -2
- data/spec/integration/end_to_end_spec.rb +252 -26
- data/spec/integration/generators/azure_generator_spec.rb +7 -6
- data/spec/integration/generators/common_generator_spec.rb +1 -1
- data/spec/integration/generators/invoke_generators_spec.rb +52 -0
- data/spec/integration/scaffolding_e2e_spec.rb +13 -24
- data/spec/scaffolding/scaffold_project_detector_spec.rb +0 -1
- data/spec/scaffolding/scaffolding_features_spec.rb +0 -1
- data/spec/system/selenium_spec.rb +0 -1
- data/spec/system/watir_spec.rb +0 -1
- data/spec/utilities/ffmpeg_spec.rb +424 -0
- data/spec/utilities/utilities_spec.rb +19 -2
- data/spec/utilities/video_command_spec.rb +120 -0
- metadata +49 -11
- data/lib/commands/adopt_commands.rb +0 -42
- data/lib/commands/loaded_commands.rb +0 -9
- data/lib/commands/plugin_commands.rb +0 -32
- data/sig/commands/adopt_commands.rbs +0 -8
- data/sig/commands/loaded_commands.rbs +0 -5
- data/sig/commands/plugin_commands.rbs +0 -9
|
@@ -12,7 +12,9 @@ pool:
|
|
|
12
12
|
|
|
13
13
|
variables:
|
|
14
14
|
HEADLESS: 'true'
|
|
15
|
-
#
|
|
15
|
+
# Where ffmpeg for failure videos is cached between runs.
|
|
16
|
+
RAIDER_CACHE_DIR: $(Pipeline.Workspace)/.cache/raider
|
|
17
|
+
# Add RAIDER_TOKEN (secret) and RAIDER_ENDPOINT (your Raider Hideout URL) as pipeline variables.
|
|
16
18
|
|
|
17
19
|
jobs:
|
|
18
20
|
- job: tests
|
|
@@ -21,6 +23,15 @@ jobs:
|
|
|
21
23
|
inputs: { versionSpec: '3.4' }
|
|
22
24
|
- script: bundle install
|
|
23
25
|
displayName: Install gems
|
|
26
|
+
- task: Cache@2
|
|
27
|
+
displayName: Cache ffmpeg for failure videos
|
|
28
|
+
inputs:
|
|
29
|
+
key: 'raider-ffmpeg-<%= RubyRaider::Ffmpeg::MANIFEST[:version] %> | "$(Agent.OS)" | Gemfile.lock'
|
|
30
|
+
path: $(RAIDER_CACHE_DIR)
|
|
31
|
+
# Video is optional: a failed download is visible here but never fails the build.
|
|
32
|
+
- script: bundle exec raider u video install
|
|
33
|
+
displayName: Install ffmpeg for failure videos
|
|
34
|
+
continueOnError: true
|
|
24
35
|
- script: <%- if cucumber? -%>bundle exec raider-reporter exec -- bundle exec cucumber features/ --format pretty<%- else -%>bundle exec raider-reporter exec -- bundle exec rspec spec/ --format documentation<%- end %>
|
|
25
36
|
displayName: Run tests
|
|
26
37
|
env:
|
|
@@ -33,8 +33,20 @@ jobs:
|
|
|
33
33
|
with:
|
|
34
34
|
chrome-version: stable
|
|
35
35
|
|
|
36
|
+
- name: Cache ffmpeg for failure videos
|
|
37
|
+
uses: actions/cache@v4
|
|
38
|
+
with:
|
|
39
|
+
path: ~/.cache/raider
|
|
40
|
+
key: raider-ffmpeg-<%= RubyRaider::Ffmpeg::MANIFEST[:version] %>-${{ runner.os }}-${{ hashFiles('**/Gemfile.lock') }}
|
|
41
|
+
|
|
42
|
+
- name: Install ffmpeg for failure videos
|
|
43
|
+
run: bundle exec raider u video install
|
|
44
|
+
# Video is optional: a failed download is visible here but never fails the build.
|
|
45
|
+
continue-on-error: true
|
|
46
|
+
|
|
36
47
|
- name: Run tests
|
|
37
48
|
run: <%- if framework == 'cucumber' -%>bundle exec raider-reporter exec -- bundle exec cucumber features --format pretty<%- else -%>bundle exec raider-reporter exec -- bundle exec rspec spec --format documentation<%- end %>
|
|
49
|
+
# RAIDER_ENDPOINT: your Raider Hideout URL (repository variable); RAIDER_TOKEN: a project token (secret).
|
|
38
50
|
env:
|
|
39
51
|
HEADLESS: true
|
|
40
52
|
RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
|
|
@@ -44,12 +44,24 @@ jobs:
|
|
|
44
44
|
appium &
|
|
45
45
|
sleep 5
|
|
46
46
|
|
|
47
|
+
- name: Cache ffmpeg for failure videos
|
|
48
|
+
uses: actions/cache@v4
|
|
49
|
+
with:
|
|
50
|
+
path: ~/.cache/raider
|
|
51
|
+
key: raider-ffmpeg-<%= RubyRaider::Ffmpeg::MANIFEST[:version] %>-${{ runner.os }}-${{ hashFiles('**/Gemfile.lock') }}
|
|
52
|
+
|
|
53
|
+
- name: Install ffmpeg for failure videos
|
|
54
|
+
run: bundle exec raider u video install
|
|
55
|
+
# Video is optional: a failed download is visible here but never fails the build.
|
|
56
|
+
continue-on-error: true
|
|
57
|
+
|
|
47
58
|
- name: Run tests
|
|
48
59
|
<%- if cucumber? -%>
|
|
49
60
|
run: bundle exec raider-reporter exec -- bundle exec cucumber features/ --format pretty
|
|
50
61
|
<%- else -%>
|
|
51
62
|
run: bundle exec raider-reporter exec -- bundle exec rspec spec/ --format documentation
|
|
52
63
|
<%- end -%>
|
|
64
|
+
# RAIDER_ENDPOINT: your Raider Hideout URL (repository variable); RAIDER_TOKEN: a project token (secret).
|
|
53
65
|
env:
|
|
54
66
|
APP_PATH: ${{ github.workspace }}/apps/RaiderTestApp.app
|
|
55
67
|
RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
|
|
@@ -101,6 +113,17 @@ jobs:
|
|
|
101
113
|
appium &
|
|
102
114
|
sleep 5
|
|
103
115
|
|
|
116
|
+
- name: Cache ffmpeg for failure videos
|
|
117
|
+
uses: actions/cache@v4
|
|
118
|
+
with:
|
|
119
|
+
path: ~/.cache/raider
|
|
120
|
+
key: raider-ffmpeg-<%= RubyRaider::Ffmpeg::MANIFEST[:version] %>-${{ runner.os }}-${{ hashFiles('**/Gemfile.lock') }}
|
|
121
|
+
|
|
122
|
+
- name: Install ffmpeg for failure videos
|
|
123
|
+
run: bundle exec raider u video install
|
|
124
|
+
# Video is optional: a failed download is visible here but never fails the build.
|
|
125
|
+
continue-on-error: true
|
|
126
|
+
|
|
104
127
|
- name: Run Android Emulator and Tests
|
|
105
128
|
uses: reactivecircus/android-emulator-runner@v2
|
|
106
129
|
with:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'thor'
|
|
1
2
|
require_relative 'infrastructure/github_generator'
|
|
2
3
|
require_relative 'infrastructure/azure_generator'
|
|
3
4
|
require_relative 'automation/automation_generator'
|
|
@@ -7,11 +8,15 @@ require_relative 'helper_generator'
|
|
|
7
8
|
require_relative 'rspec/rspec_generator'
|
|
8
9
|
|
|
9
10
|
module InvokeGenerators
|
|
10
|
-
|
|
11
|
+
FRAMEWORKS = %w[rspec cucumber].freeze
|
|
12
|
+
AUTOMATIONS = %w[selenium watir ios android cross_platform].freeze
|
|
11
13
|
|
|
12
14
|
module_function
|
|
13
15
|
|
|
14
16
|
def generate_framework(structure = {})
|
|
17
|
+
structure = normalise_options(structure)
|
|
18
|
+
validate_option(:framework, structure[:framework], FRAMEWORKS)
|
|
19
|
+
validate_option(:automation, structure[:automation], AUTOMATIONS)
|
|
15
20
|
validate_ci(structure[:ci])
|
|
16
21
|
generators = %w[Automation Common Helpers]
|
|
17
22
|
framework = structure[:framework]
|
|
@@ -30,10 +35,23 @@ module InvokeGenerators
|
|
|
30
35
|
print_mobile_azure_notice(structure)
|
|
31
36
|
end
|
|
32
37
|
|
|
38
|
+
def normalise_options(structure)
|
|
39
|
+
structure.merge(
|
|
40
|
+
framework: structure[:framework].to_s.downcase,
|
|
41
|
+
automation: structure[:automation].to_s.downcase
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate_option(key, value, allowed)
|
|
46
|
+
return if allowed.include?(value.to_s)
|
|
47
|
+
|
|
48
|
+
raise Thor::Error, "Invalid #{key} #{value.to_s.inspect}. Allowed values: #{allowed.join(', ')}"
|
|
49
|
+
end
|
|
50
|
+
|
|
33
51
|
def validate_ci(provider)
|
|
34
52
|
return if [nil, '', 'github', 'azure'].include?(provider&.to_s)
|
|
35
53
|
|
|
36
|
-
raise
|
|
54
|
+
raise Thor::Error, 'ci must be github or azure'
|
|
37
55
|
end
|
|
38
56
|
|
|
39
57
|
def print_mobile_azure_notice(structure)
|
|
@@ -93,11 +93,13 @@ Every run writes machine-readable results to `raider-results/` (including `run.j
|
|
|
93
93
|
`features/support/env.rb` requires `raider_reporter/cucumber`, so no extra Cucumber formatter flag is needed.
|
|
94
94
|
<%- end -%>
|
|
95
95
|
|
|
96
|
-
To upload runs to Raider, set these environment variables before running the suite
|
|
96
|
+
To upload runs to Raider, set these environment variables before running the suite.
|
|
97
|
+
`RAIDER_TOKEN` and `RAIDER_ENDPOINT` come from your Raider Hideout server (its setup page, or `bin/rails hideout:setup` /
|
|
98
|
+
`hideout:token`):
|
|
97
99
|
|
|
98
100
|
```bash
|
|
99
101
|
export RAIDER_TOKEN=your-token
|
|
100
|
-
export RAIDER_ENDPOINT=https://
|
|
102
|
+
export RAIDER_ENDPOINT=https://hideout.example.com
|
|
101
103
|
```
|
|
102
104
|
|
|
103
105
|
<%- if rspec? -%>
|
|
@@ -108,6 +110,62 @@ Without `RAIDER_TOKEN`, nothing is uploaded and the suite output is unchanged. F
|
|
|
108
110
|
report and to Raider. See the [raider_reporter README](https://github.com/RaiderHQ/raider_reporter) for details.
|
|
109
111
|
<%- end -%>
|
|
110
112
|
|
|
113
|
+
### Video replay
|
|
114
|
+
|
|
115
|
+
Every test is recorded, and the video is kept only for failing tests<%= rspec? ? ' (on the last retry attempt)' : '' %>. It is attached
|
|
116
|
+
to the report as `video` and saved under `raider-results/attachments/`, next to the failure screenshot.
|
|
117
|
+
<%- if mobile? -%>
|
|
118
|
+
Appium records the device screen and the video is attached as `video.mp4`.
|
|
119
|
+
<%- else -%>
|
|
120
|
+
The video is a `video.webm` built from page screenshots with ffmpeg.
|
|
121
|
+
<%- end -%>
|
|
122
|
+
|
|
123
|
+
<%- if single_platform? -%>
|
|
124
|
+
- Turn video off for one run with `VIDEO=off bundle exec <%= rspec? ? 'rspec' : 'cucumber' %>`, or for the project with
|
|
125
|
+
`raider u video off`, which creates `config/config.yml` with a `video:` section (`raider u video on` turns it back
|
|
126
|
+
on).
|
|
127
|
+
<%- else -%>
|
|
128
|
+
- Video is on by default: `video:` in `config/config.yml` (`enabled: true`, `fps: 5`, `max_width: 1280`).
|
|
129
|
+
- Turn it off for one run with `VIDEO=off bundle exec <%= rspec? ? 'rspec' : 'cucumber' %>`, or for the project with
|
|
130
|
+
`raider u video off` (`raider u video on` turns it back on).
|
|
131
|
+
<%- end -%>
|
|
132
|
+
<%- unless mobile? -%>
|
|
133
|
+
- ffmpeg is downloaded on first use, checked against a pinned SHA-256 and cached in `~/.cache/raider`
|
|
134
|
+
(`%LOCALAPPDATA%\raider` on Windows). `raider u video install` downloads it ahead of time (CI does this before the
|
|
135
|
+
tests and caches the folder), and `raider u video status` prints the binary in use.
|
|
136
|
+
- `RAIDER_FFMPEG_PATH` uses your own ffmpeg (it needs libvpx) instead of the download, and `RAIDER_CACHE_DIR` moves
|
|
137
|
+
the cache.
|
|
138
|
+
<%- if azure? -%>
|
|
139
|
+
- `azure-pipelines.yml` sets `RAIDER_CACHE_DIR` to `$(Pipeline.Workspace)/.cache/raider`, the folder its `Cache@2`
|
|
140
|
+
step keeps between runs.
|
|
141
|
+
<%- end -%>
|
|
142
|
+
- The RaiderHQ `ffmpeg-7.1` release that the download comes from is not published yet. Until it is, set
|
|
143
|
+
`RAIDER_FFMPEG_PATH` to get videos; without it the suite runs as usual and prints one `[video]` line.
|
|
144
|
+
- If ffmpeg is missing or recording fails, the suite still runs and its exit code is unchanged; you get one `[video]`
|
|
145
|
+
line on stderr and no video.
|
|
146
|
+
- Known limits: a frame is taken after each page load, click and text change, plus one at the end of a failing test,
|
|
147
|
+
so changes the page makes on its own between actions show up in the next frame. There is one recorded driver per
|
|
148
|
+
process.
|
|
149
|
+
<%- else -%>
|
|
150
|
+
- Appium's screen recording needs `ffmpeg` on the Appium server host's `PATH` (this machine only when Appium runs
|
|
151
|
+
here): always for iOS (XCUITest), and for Android when a recording is longer than 180 s. Without it, recording
|
|
152
|
+
fails with one `[video]` line and the tests run as usual.
|
|
153
|
+
<%- if cross_platform? -%>
|
|
154
|
+
- Each test records for at most 1800 s on iOS and 180 s on Android, so Android needs no ffmpeg on the Appium host.
|
|
155
|
+
<%- elsif ios? -%>
|
|
156
|
+
- Each test records for at most 1800 s.
|
|
157
|
+
<%- else -%>
|
|
158
|
+
- Each test records for at most 180 s, so the Appium host needs no ffmpeg; longer tests keep only their first 180 s.
|
|
159
|
+
<%- end -%>
|
|
160
|
+
Set `time_limit` (seconds) under `video:` in `config/config.yml`<%= single_platform? ? ' (create it if it is missing)' : '' %> to change it; on Android, more
|
|
161
|
+
than 180 s needs ffmpeg on the Appium host.
|
|
162
|
+
- `raider u video install` and `raider u video status` manage the ffmpeg that web projects use, cached in
|
|
163
|
+
`~/.cache/raider` (override with `RAIDER_FFMPEG_PATH` or `RAIDER_CACHE_DIR`); they do not install ffmpeg on the
|
|
164
|
+
Appium host. The RaiderHQ `ffmpeg-7.1` release it comes from is not published yet.
|
|
165
|
+
- If recording fails, the suite still runs and its exit code is unchanged; you get one `[video]` line on stderr and
|
|
166
|
+
no video.
|
|
167
|
+
<%- end -%>
|
|
168
|
+
|
|
111
169
|
<%- unless azure? && mobile? -%>
|
|
112
170
|
### Quarantine
|
|
113
171
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'active_support/core_ext/string/inflections'
|
|
4
4
|
require 'yaml'
|
|
5
5
|
require 'watir'
|
|
6
|
+
require_relative 'video_helper'
|
|
6
7
|
|
|
7
8
|
module BrowserHelper
|
|
8
9
|
|
|
@@ -23,6 +24,9 @@ module BrowserHelper
|
|
|
23
24
|
args += ['--headless'] if (ENV['HEADLESS'] || config['headless']) && !args.include?('--headless')
|
|
24
25
|
capitalized = browser_name == 'ie' ? browser_name.upcase : browser_name.capitalize
|
|
25
26
|
browser_options = "Selenium::WebDriver::#{capitalized}::Options".constantize.new(args:)
|
|
26
|
-
|
|
27
|
+
arguments = { options: browser_options }
|
|
28
|
+
listener = VideoHelper.listener
|
|
29
|
+
arguments[:listener] = listener if listener
|
|
30
|
+
Watir::Browser.new(browser_name.to_sym, **arguments)
|
|
27
31
|
end
|
|
28
32
|
end
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
def create_driver(*opts)
|
|
2
2
|
@config = YAML.load_file('config/config.yml')
|
|
3
3
|
browser = @config['browser']
|
|
4
|
-
|
|
4
|
+
arguments = { options: create_webdriver_options(*opts) }
|
|
5
|
+
listener = VideoHelper.listener
|
|
6
|
+
arguments[:listener] = listener if listener
|
|
7
|
+
driver = Selenium::WebDriver.for(browser.to_sym, **arguments)
|
|
5
8
|
driver.manage.timeouts.implicit_wait = @config.fetch('timeout', 10)
|
|
6
9
|
driver
|
|
7
10
|
end
|
|
@@ -12,6 +12,7 @@ require_relative 'browser_helper'
|
|
|
12
12
|
<%- else -%>
|
|
13
13
|
require_relative 'driver_helper'
|
|
14
14
|
<%- end -%>
|
|
15
|
+
require_relative 'video_helper'
|
|
15
16
|
|
|
16
17
|
module SpecHelper
|
|
17
18
|
RSpec.configure do |config|
|
|
@@ -45,10 +46,14 @@ module SpecHelper
|
|
|
45
46
|
browser.window.maximize
|
|
46
47
|
end
|
|
47
48
|
<%- end -%>
|
|
49
|
+
@video_recorder = VideoHelper.recorder_for(<%= watir? ? 'browser' : 'driver' %>)
|
|
50
|
+
@video_recorder.start
|
|
48
51
|
end
|
|
49
52
|
|
|
50
53
|
config.after(:each) do |example|
|
|
51
54
|
example_name = example.description
|
|
55
|
+
keep_video = example.exception ? VideoHelper.final_attempt?(example) : false
|
|
56
|
+
video_path = @video_recorder&.stop(keep: keep_video)
|
|
52
57
|
if example.exception
|
|
53
58
|
@_exception = example.exception
|
|
54
59
|
Dir.mktmpdir do |temp_folder|
|
|
@@ -57,6 +62,9 @@ module SpecHelper
|
|
|
57
62
|
RaiderReporter.attach('screenshot', screenshot_path, type: :png)
|
|
58
63
|
end
|
|
59
64
|
end
|
|
65
|
+
RaiderReporter.attach('video', video_path, type: <%= mobile? ? ':mp4' : ':webm' %>) if video_path
|
|
66
|
+
ensure
|
|
67
|
+
@video_recorder&.cleanup
|
|
60
68
|
<%= partial('quit_driver', strip: true) %>
|
|
61
69
|
end
|
|
62
70
|
end
|