ruby_raider 3.8.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +18 -29
  3. data/.github/workflows/security.yml +68 -0
  4. data/.rubocop.yml +28 -0
  5. data/README.md +22 -4
  6. data/docs/superpowers/plans/2026-10-25-failure-video-replay.md +400 -0
  7. data/docs/superpowers/plans/2026-10-26-parity-cleanup.md +233 -0
  8. data/docs/superpowers/plans/2026-10-28-failure-card.md +295 -0
  9. data/docs/superpowers/specs/2026-10-25-failure-video-replay-design.md +214 -0
  10. data/docs/superpowers/specs/2026-10-28-failure-card-design.md +210 -0
  11. data/lib/commands/scaffolding_commands.rb +3 -3
  12. data/lib/commands/utility_commands.rb +36 -6
  13. data/lib/generators/cucumber/templates/cucumber.tt +1 -1
  14. data/lib/generators/cucumber/templates/partials/appium_env.tt +6 -0
  15. data/lib/generators/cucumber/templates/partials/selenium_env.tt +6 -0
  16. data/lib/generators/cucumber/templates/partials/watir_env.tt +6 -0
  17. data/lib/generators/helper_generator.rb +5 -1
  18. data/lib/generators/infrastructure/azure_generator.rb +1 -0
  19. data/lib/generators/infrastructure/github_generator.rb +1 -0
  20. data/lib/generators/infrastructure/templates/azure_pipelines.tt +26 -2
  21. data/lib/generators/infrastructure/templates/github.tt +31 -1
  22. data/lib/generators/infrastructure/templates/github_appium.tt +50 -1
  23. data/lib/generators/invoke_generators.rb +20 -2
  24. data/lib/generators/rspec/rspec_generator.rb +0 -1
  25. data/lib/generators/templates/common/gemfile.tt +1 -1
  26. data/lib/generators/templates/common/git_ignore.tt +3 -0
  27. data/lib/generators/templates/common/partials/mobile_config.tt +5 -0
  28. data/lib/generators/templates/common/partials/web_config.tt +6 -1
  29. data/lib/generators/templates/common/read_me.tt +80 -2
  30. data/lib/generators/templates/helpers/browser_helper.tt +5 -1
  31. data/lib/generators/templates/helpers/driver_helper.tt +1 -0
  32. data/lib/generators/templates/helpers/partials/selenium_driver.tt +4 -1
  33. data/lib/generators/templates/helpers/spec_helper.tt +8 -0
  34. data/lib/generators/templates/helpers/video_helper.tt +338 -171
  35. data/lib/ruby_raider/ffmpeg.rb +280 -0
  36. data/lib/ruby_raider.rb +2 -3
  37. data/lib/scaffolding/project_detector.rb +1 -3
  38. data/lib/scaffolding/scaffolding.rb +0 -1
  39. data/lib/utilities/utilities.rb +15 -2
  40. data/lib/version +1 -1
  41. data/ruby_raider.gemspec +3 -1
  42. data/sig/commands/utility_commands.rbs +4 -1
  43. data/sig/generators/invoke_generators.rbs +2 -3
  44. data/sig/ruby_raider/ffmpeg.rbs +23 -0
  45. data/sig/utilities/utilities.rbs +1 -0
  46. data/spec/commands/raider_commands_spec.rb +56 -1
  47. data/spec/generators/fixtures/video_helper_stubs/selenium-webdriver.rb +28 -0
  48. data/spec/generators/video_helper_spec.rb +608 -0
  49. data/spec/integration/commands/browser_update_after_creation_spec.rb +7 -6
  50. data/spec/integration/commands/utility_commands_spec.rb +2 -2
  51. data/spec/integration/content/azure_content_spec.rb +48 -3
  52. data/spec/integration/content/ci_content_spec.rb +115 -5
  53. data/spec/integration/content/common_content_spec.rb +8 -0
  54. data/spec/integration/content/config_content_spec.rb +14 -0
  55. data/spec/integration/content/cucumber_reporter_content_spec.rb +22 -1
  56. data/spec/integration/content/gemfile_content_spec.rb +2 -3
  57. data/spec/integration/content/helper_content_spec.rb +146 -0
  58. data/spec/integration/content/page_content_spec.rb +0 -1
  59. data/spec/integration/content/readme_content_spec.rb +65 -0
  60. data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
  61. data/spec/integration/content/test_content_spec.rb +2 -3
  62. data/spec/integration/end_to_end_features_spec.rb +0 -2
  63. data/spec/integration/end_to_end_spec.rb +252 -26
  64. data/spec/integration/generators/azure_generator_spec.rb +7 -6
  65. data/spec/integration/generators/common_generator_spec.rb +1 -1
  66. data/spec/integration/generators/invoke_generators_spec.rb +52 -0
  67. data/spec/integration/scaffolding_e2e_spec.rb +13 -24
  68. data/spec/scaffolding/scaffold_project_detector_spec.rb +0 -1
  69. data/spec/scaffolding/scaffolding_features_spec.rb +0 -1
  70. data/spec/system/selenium_spec.rb +0 -1
  71. data/spec/system/watir_spec.rb +0 -1
  72. data/spec/utilities/ffmpeg_spec.rb +424 -0
  73. data/spec/utilities/utilities_spec.rb +19 -2
  74. data/spec/utilities/video_command_spec.rb +120 -0
  75. metadata +49 -11
  76. data/lib/commands/adopt_commands.rb +0 -42
  77. data/lib/commands/loaded_commands.rb +0 -9
  78. data/lib/commands/plugin_commands.rb +0 -32
  79. data/sig/commands/adopt_commands.rbs +0 -8
  80. data/sig/commands/loaded_commands.rbs +0 -5
  81. data/sig/commands/plugin_commands.rbs +0 -9
@@ -12,7 +12,9 @@ pool:
12
12
 
13
13
  variables:
14
14
  HEADLESS: 'true'
15
- # Add RAIDER_TOKEN (secret) and RAIDER_ENDPOINT as pipeline variables.
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:
@@ -31,7 +42,7 @@ jobs:
31
42
  condition: always()
32
43
  inputs: { targetPath: raider-results, artifact: raider-results-$(System.JobPositionInPhase)-$(System.JobAttempt) }
33
44
  - job: raider_comment
34
- dependsOn: tests
45
+ dependsOn: [tests, raider_gate]
35
46
  condition: and(always(), eq(variables['Build.Reason'], 'PullRequest'))
36
47
  steps:
37
48
  - task: UseRubyVersion@0
@@ -43,3 +54,16 @@ jobs:
43
54
  RAIDER_TOKEN: $(RAIDER_TOKEN)
44
55
  RAIDER_ENDPOINT: $(RAIDER_ENDPOINT)
45
56
  SYSTEM_ACCESSTOKEN: $(System.AccessToken)
57
+ - job: raider_gate
58
+ dependsOn: tests
59
+ condition: and(always(), eq(variables['RAIDER_GATE'], '1'))
60
+ steps:
61
+ - task: UseRubyVersion@0
62
+ inputs: { versionSpec: '3.4' }
63
+ - script: bundle install
64
+ - script: bundle exec raider-reporter gate
65
+ displayName: Wait for the Raider quality gate
66
+ env:
67
+ RAIDER_TOKEN: $(RAIDER_TOKEN)
68
+ RAIDER_ENDPOINT: $(RAIDER_ENDPOINT)
69
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
@@ -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 }}
@@ -50,7 +62,7 @@ jobs:
50
62
  if-no-files-found: ignore
51
63
 
52
64
  raider-comment:
53
- needs: [build]
65
+ needs: [build, raider-gate]
54
66
  if: always() && github.event_name == 'pull_request'
55
67
  runs-on: ubuntu-latest
56
68
  permissions:
@@ -67,3 +79,21 @@ jobs:
67
79
  RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
68
80
  RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
69
81
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
82
+
83
+ raider-gate:
84
+ needs: [build]
85
+ if: always() && vars.RAIDER_GATE == '1'
86
+ runs-on: ubuntu-latest
87
+ permissions:
88
+ contents: read
89
+ statuses: write
90
+ steps:
91
+ - uses: actions/checkout@v4
92
+ - uses: ruby/setup-ruby@v1
93
+ with: { ruby-version: 3.4.0, bundler-cache: true }
94
+ - name: Wait for the Raider quality gate
95
+ run: bundle exec raider-reporter gate
96
+ env:
97
+ RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
98
+ RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
99
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -44,17 +44,33 @@ 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 }}
56
68
  RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
57
69
  RAIDER_QUARANTINE: "1"
70
+ <%- if cross_platform? -%>
71
+ RAIDER_SHARD_INDEX: "1"
72
+ RAIDER_SHARD_TOTAL: "2"
73
+ <%- end -%>
58
74
 
59
75
  - name: Upload Raider results
60
76
  uses: actions/upload-artifact@v4
@@ -97,6 +113,17 @@ jobs:
97
113
  appium &
98
114
  sleep 5
99
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
+
100
127
  - name: Run Android Emulator and Tests
101
128
  uses: reactivecircus/android-emulator-runner@v2
102
129
  with:
@@ -115,6 +142,10 @@ jobs:
115
142
  RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
116
143
  RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
117
144
  RAIDER_QUARANTINE: "1"
145
+ <%- if cross_platform? -%>
146
+ RAIDER_SHARD_INDEX: "2"
147
+ RAIDER_SHARD_TOTAL: "2"
148
+ <%- end -%>
118
149
 
119
150
  - name: Upload Raider results
120
151
  uses: actions/upload-artifact@v4
@@ -126,7 +157,7 @@ jobs:
126
157
  <%- end -%>
127
158
 
128
159
  raider-comment:
129
- needs: [<%= [('ios-tests' if ios? || cross_platform?), ('android-tests' if android? || cross_platform?)].compact.join(', ') %>]
160
+ needs: [<%= [('ios-tests' if ios? || cross_platform?), ('android-tests' if android? || cross_platform?)].compact.join(', ') %>, raider-gate]
130
161
  if: always() && github.event_name == 'pull_request'
131
162
  runs-on: ubuntu-latest
132
163
  permissions:
@@ -143,3 +174,21 @@ jobs:
143
174
  RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
144
175
  RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
145
176
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
177
+
178
+ raider-gate:
179
+ needs: [<%= [('ios-tests' if ios? || cross_platform?), ('android-tests' if android? || cross_platform?)].compact.join(', ') %>]
180
+ if: always() && vars.RAIDER_GATE == '1'
181
+ runs-on: ubuntu-latest
182
+ permissions:
183
+ contents: read
184
+ statuses: write
185
+ steps:
186
+ - uses: actions/checkout@v4
187
+ - uses: ruby/setup-ruby@v1
188
+ with: { ruby-version: 3.4.0, bundler-cache: true }
189
+ - name: Wait for the Raider quality gate
190
+ run: bundle exec raider-reporter gate
191
+ env:
192
+ RAIDER_TOKEN: ${{ secrets.RAIDER_TOKEN }}
193
+ RAIDER_ENDPOINT: ${{ vars.RAIDER_ENDPOINT }}
194
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -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
- class InvalidParameters < ArgumentError; end
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 InvalidParameters, 'ci must be github or azure'
54
+ raise Thor::Error, 'ci must be github or azure'
37
55
  end
38
56
 
39
57
  def print_mobile_azure_notice(structure)
@@ -20,5 +20,4 @@ class RspecGenerator < Generator
20
20
 
21
21
  template('accessibility_spec.tt', "#{name}/spec/accessibility_spec.rb")
22
22
  end
23
-
24
23
  end
@@ -13,7 +13,7 @@ gem 'parallel_split_test'
13
13
  gem 'parallel_tests'
14
14
  gem 'rake'
15
15
  gem 'reek'
16
- gem 'raider_reporter', '~> 0.8'
16
+ gem 'raider_reporter', '~> 0.10'
17
17
  <%- if rspec? -%>
18
18
  gem 'rspec-retry'
19
19
  <%- end -%>
@@ -1,4 +1,7 @@
1
1
  raider-results
2
2
  <%- if rspec? -%>
3
3
  spec/examples.txt
4
+ <%- end -%>
5
+ <%- if cucumber? -%>
6
+ rerun.txt
4
7
  <%- end -%>
@@ -1 +1,6 @@
1
1
  platform: :ios
2
+
3
+ video:
4
+ enabled: true
5
+ fps: 5
6
+ max_width: 1280
@@ -13,4 +13,9 @@ browser_arguments:
13
13
  - search-engine-choice-country
14
14
  firefox:
15
15
  - acceptInsecureCerts
16
- - no-sandbox
16
+ - no-sandbox
17
+
18
+ video:
19
+ enabled: true
20
+ fps: 5
21
+ max_width: 1280
@@ -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://your-raider-endpoint
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
 
@@ -115,6 +173,26 @@ CI runs the suite through `bundle exec raider-reporter exec -- …` with `RAIDER
115
173
  Raider still run and are still reported (as `quarantined`), and a build whose only failures are quarantined stays green.
116
174
  Local runs keep quarantine off. To turn it off in CI, remove `RAIDER_QUARANTINE` from the pipeline.
117
175
 
176
+ <%- end -%>
177
+ <%- unless azure? && mobile? -%>
178
+ ### Quality gate
179
+
180
+ The `<%= azure? ? 'raider_gate' : 'raider-gate' %>` CI job runs `bundle exec raider-reporter gate` after the tests and
181
+ fails the pipeline when the Raider quality gate fails. It is off until you turn it on:
182
+
183
+ - set the CI variable `RAIDER_GATE=1` (<%= azure? ? 'a pipeline variable' : 'a repository variable under Settings → Secrets and variables → Actions → Variables' %>);
184
+ - turn on rules under the project's Gate page in Raider: a minimum pass rate, no new failures against the default
185
+ branch, no new flaky tests;
186
+ - optionally require the `raider/gate` check in branch protection.
187
+
188
+ `raider-reporter gate` exits 1 when the gate fails, naming the failed rules, and 2 when it gets no verdict (timeout,
189
+ Raider unreachable, token rejected, build not found). `RAIDER_GATE_TIMEOUT` sets how long it waits, in seconds
190
+ (default 600). The job stays off by default because a project without `RAIDER_TOKEN`, or a pull request from a fork,
191
+ would exit 2 on every run.
192
+
193
+ Parallel test jobs that upload to one build must each set `RAIDER_SHARD_TOTAL` (the number of jobs) and
194
+ `RAIDER_SHARD_INDEX` (1 to that number), so the gate waits for all of them instead of judging the first one to finish.
195
+
118
196
  <%- end -%>
119
197
  <%- if azure? && mobile? -%>
120
198
  ## CI
@@ -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
- Watir::Browser.new(browser_name.to_sym, options: browser_options)
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
@@ -4,6 +4,7 @@ require 'yaml'
4
4
  <%- if selenium_based? -%>
5
5
  require 'active_support/inflector'
6
6
  require 'selenium-webdriver'
7
+ require_relative 'video_helper'
7
8
  <%- else -%>
8
9
  require 'appium_lib'
9
10
  <%- 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
- driver = Selenium::WebDriver.for(browser.to_sym, options: create_webdriver_options(*opts))
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