ruby_raider 2.0.0 → 3.0.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 (224) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +58 -0
  3. data/.github/workflows/steep.yml +21 -0
  4. data/.gitignore +1 -1
  5. data/.reek.yml +46 -4
  6. data/.ruby-version +1 -1
  7. data/README.md +138 -77
  8. data/Steepfile +22 -0
  9. data/assets/ruby_raider_logo.svg +51 -0
  10. data/lib/adopter/adopt_menu.rb +11 -15
  11. data/lib/adopter/converters/base_converter.rb +1 -2
  12. data/lib/adopter/converters/identity_converter.rb +3 -6
  13. data/lib/adopter/migration_plan.rb +0 -1
  14. data/lib/adopter/plan_builder.rb +2 -5
  15. data/lib/adopter/project_analyzer.rb +1 -5
  16. data/lib/adopter/project_detector.rb +3 -5
  17. data/lib/commands/adopt_commands.rb +0 -1
  18. data/lib/commands/plugin_commands.rb +0 -2
  19. data/lib/commands/scaffolding_commands.rb +220 -37
  20. data/lib/commands/utility_commands.rb +82 -2
  21. data/lib/generators/automation/automation_generator.rb +0 -7
  22. data/lib/generators/automation/templates/partials/element.tt +1 -1
  23. data/lib/generators/automation/templates/partials/initialize_selector.tt +0 -7
  24. data/lib/generators/automation/templates/partials/url_methods.tt +0 -1
  25. data/lib/generators/common_generator.rb +12 -0
  26. data/lib/generators/cucumber/cucumber_generator.rb +36 -0
  27. data/lib/generators/cucumber/templates/accessibility_feature.tt +5 -0
  28. data/lib/generators/cucumber/templates/accessibility_steps.tt +21 -0
  29. data/lib/generators/cucumber/templates/cucumber.tt +8 -1
  30. data/lib/generators/cucumber/templates/feature.tt +0 -4
  31. data/lib/generators/cucumber/templates/partials/appium_env.tt +5 -0
  32. data/lib/generators/cucumber/templates/partials/capybara_env.tt +19 -1
  33. data/lib/generators/cucumber/templates/partials/driver_world.tt +1 -4
  34. data/lib/generators/cucumber/templates/partials/selenium_env.tt +22 -35
  35. data/lib/generators/cucumber/templates/partials/watir_env.tt +20 -1
  36. data/lib/generators/cucumber/templates/partials/web_steps.tt +6 -12
  37. data/lib/generators/cucumber/templates/performance_feature.tt +5 -0
  38. data/lib/generators/cucumber/templates/performance_steps.tt +17 -0
  39. data/lib/generators/cucumber/templates/visual_feature.tt +5 -0
  40. data/lib/generators/cucumber/templates/visual_steps.tt +19 -0
  41. data/lib/generators/generator.rb +38 -7
  42. data/lib/generators/helper_generator.rb +24 -7
  43. data/lib/generators/infrastructure/templates/github.tt +1 -1
  44. data/lib/generators/infrastructure/templates/github_appium.tt +2 -2
  45. data/lib/generators/infrastructure/templates/gitlab.tt +1 -1
  46. data/lib/generators/invoke_generators.rb +42 -9
  47. data/lib/generators/menu_generator.rb +120 -11
  48. data/lib/generators/minitest/minitest_generator.rb +16 -4
  49. data/lib/generators/minitest/templates/accessibility_test.tt +26 -0
  50. data/lib/generators/minitest/templates/performance_test.tt +18 -0
  51. data/lib/generators/minitest/templates/test.tt +5 -34
  52. data/lib/generators/minitest/templates/visual_test.tt +23 -0
  53. data/lib/generators/rspec/rspec_generator.rb +16 -4
  54. data/lib/generators/rspec/templates/accessibility_spec.tt +25 -0
  55. data/lib/generators/rspec/templates/performance_spec.tt +18 -0
  56. data/lib/generators/rspec/templates/spec.tt +5 -35
  57. data/lib/generators/rspec/templates/visual_spec.tt +20 -0
  58. data/lib/generators/template_renderer/partial_cache.rb +11 -1
  59. data/lib/generators/template_renderer/partial_resolver.rb +17 -10
  60. data/lib/generators/template_renderer.rb +17 -1
  61. data/lib/generators/templates/common/gemfile.tt +21 -6
  62. data/lib/generators/templates/common/git_ignore.tt +6 -1
  63. data/lib/generators/templates/common/partials/mobile_config.tt +5 -1
  64. data/lib/generators/templates/common/partials/web_config.tt +16 -7
  65. data/lib/generators/templates/common/rakefile.tt +36 -0
  66. data/lib/generators/templates/common/read_me.tt +41 -91
  67. data/lib/generators/templates/common/rspec.tt +3 -0
  68. data/lib/generators/templates/common/ruby_version.tt +1 -0
  69. data/lib/generators/templates/helpers/allure_helper.tt +11 -0
  70. data/lib/generators/templates/helpers/browser_helper.tt +12 -2
  71. data/lib/generators/templates/helpers/capybara_helper.tt +5 -1
  72. data/lib/generators/templates/helpers/debug_helper.tt +190 -0
  73. data/lib/generators/templates/helpers/driver_helper.tt +2 -10
  74. data/lib/generators/templates/helpers/partials/appium_driver.tt +0 -2
  75. data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +7 -0
  76. data/lib/generators/templates/helpers/partials/debug_start.tt +7 -0
  77. data/lib/generators/templates/helpers/partials/driver_and_options.tt +1 -3
  78. data/lib/generators/templates/helpers/partials/selenium_driver.tt +8 -7
  79. data/lib/generators/templates/helpers/partials/video_start.tt +9 -0
  80. data/lib/generators/templates/helpers/partials/video_stop.tt +4 -0
  81. data/lib/generators/templates/helpers/performance_helper.tt +57 -0
  82. data/lib/generators/templates/helpers/spec_helper.tt +57 -8
  83. data/lib/generators/templates/helpers/test_helper.tt +69 -1
  84. data/lib/generators/templates/helpers/video_helper.tt +270 -0
  85. data/lib/generators/templates/helpers/visual_helper.tt +39 -46
  86. data/lib/llm/client.rb +79 -0
  87. data/lib/llm/config.rb +57 -0
  88. data/lib/llm/prompts.rb +84 -0
  89. data/lib/llm/provider.rb +27 -0
  90. data/lib/llm/providers/anthropic_provider.rb +43 -0
  91. data/lib/llm/providers/ollama_provider.rb +56 -0
  92. data/lib/llm/providers/openai_provider.rb +42 -0
  93. data/lib/llm/response_parser.rb +67 -0
  94. data/lib/plugin/plugin.rb +22 -20
  95. data/lib/plugin/plugin_exposer.rb +16 -38
  96. data/lib/ruby_raider.rb +47 -12
  97. data/lib/scaffolding/crud_generator.rb +94 -0
  98. data/lib/scaffolding/dry_run_presenter.rb +16 -0
  99. data/lib/scaffolding/name_normalizer.rb +63 -0
  100. data/lib/scaffolding/page_introspector.rb +45 -0
  101. data/lib/scaffolding/project_detector.rb +72 -0
  102. data/lib/scaffolding/scaffold_menu.rb +103 -0
  103. data/lib/scaffolding/scaffolding.rb +158 -11
  104. data/lib/scaffolding/templates/component.tt +30 -0
  105. data/lib/scaffolding/templates/feature.tt +4 -4
  106. data/lib/scaffolding/templates/helper.tt +15 -1
  107. data/lib/scaffolding/templates/page_from_url.tt +75 -0
  108. data/lib/scaffolding/templates/page_object.tt +50 -1
  109. data/lib/scaffolding/templates/spec.tt +33 -2
  110. data/lib/scaffolding/templates/spec_from_page.tt +31 -0
  111. data/lib/scaffolding/templates/spec_from_url.tt +46 -0
  112. data/lib/scaffolding/templates/steps.tt +17 -5
  113. data/lib/scaffolding/url_analyzer.rb +179 -0
  114. data/lib/utilities/desktop_downloader.rb +177 -0
  115. data/lib/utilities/logo.rb +83 -0
  116. data/lib/utilities/utilities.rb +53 -20
  117. data/lib/version +1 -1
  118. data/ruby_raider.gemspec +1 -0
  119. data/sig/adopter/adopt_menu.rbs +25 -0
  120. data/sig/adopter/converters/base_converter.rbs +23 -0
  121. data/sig/adopter/converters/identity_converter.rbs +16 -0
  122. data/sig/adopter/migration_plan.rbs +34 -0
  123. data/sig/adopter/migrator.rbs +21 -0
  124. data/sig/adopter/plan_builder.rbs +38 -0
  125. data/sig/adopter/project_analyzer.rbs +39 -0
  126. data/sig/adopter/project_detector.rbs +26 -0
  127. data/sig/commands/adopt_commands.rbs +8 -0
  128. data/sig/commands/loaded_commands.rbs +5 -0
  129. data/sig/commands/plugin_commands.rbs +9 -0
  130. data/sig/commands/scaffolding_commands.rbs +28 -0
  131. data/sig/commands/utility_commands.rbs +21 -0
  132. data/sig/generators/automation/automation_generator.rbs +20 -0
  133. data/sig/generators/common_generator.rbs +12 -0
  134. data/sig/generators/cucumber/cucumber_generator.rbs +16 -0
  135. data/sig/generators/generator.rbs +40 -0
  136. data/sig/generators/helper_generator.rbs +18 -0
  137. data/sig/generators/infrastructure/github_generator.rbs +5 -0
  138. data/sig/generators/infrastructure/gitlab_generator.rbs +4 -0
  139. data/sig/generators/invoke_generators.rbs +10 -0
  140. data/sig/generators/menu_generator.rbs +29 -0
  141. data/sig/generators/minitest/minitest_generator.rbs +8 -0
  142. data/sig/generators/rspec/rspec_generator.rbs +8 -0
  143. data/sig/generators/template_renderer/partial_cache.rbs +20 -0
  144. data/sig/generators/template_renderer/partial_resolver.rbs +20 -0
  145. data/sig/generators/template_renderer/template_error.rbs +19 -0
  146. data/sig/generators/template_renderer.rbs +10 -0
  147. data/sig/llm/client.rbs +15 -0
  148. data/sig/llm/config.rbs +20 -0
  149. data/sig/llm/prompts.rbs +8 -0
  150. data/sig/llm/provider.rbs +12 -0
  151. data/sig/llm/providers/anthropic_provider.rbs +16 -0
  152. data/sig/llm/providers/ollama_provider.rbs +18 -0
  153. data/sig/llm/providers/openai_provider.rbs +16 -0
  154. data/sig/llm/response_parser.rbs +13 -0
  155. data/sig/plugin/plugin.rbs +24 -0
  156. data/sig/plugin/plugin_exposer.rbs +20 -0
  157. data/sig/ruby_raider.rbs +15 -0
  158. data/sig/scaffolding/crud_generator.rbs +16 -0
  159. data/sig/scaffolding/dry_run_presenter.rbs +4 -0
  160. data/sig/scaffolding/name_normalizer.rbs +17 -0
  161. data/sig/scaffolding/page_introspector.rbs +14 -0
  162. data/sig/scaffolding/project_detector.rbs +14 -0
  163. data/sig/scaffolding/scaffold_menu.rbs +18 -0
  164. data/sig/scaffolding/scaffolding.rbs +55 -0
  165. data/sig/scaffolding/url_analyzer.rbs +28 -0
  166. data/sig/utilities/desktop_downloader.rbs +23 -0
  167. data/sig/utilities/logger.rbs +13 -0
  168. data/sig/utilities/logo.rbs +16 -0
  169. data/sig/utilities/utilities.rbs +30 -0
  170. data/sig/vendor/thor.rbs +34 -0
  171. data/sig/vendor/tty_prompt.rbs +15 -0
  172. data/spec/adopter/adopt_menu_spec.rb +12 -12
  173. data/spec/adopter/migration_plan_spec.rb +1 -1
  174. data/spec/adopter/migrator_spec.rb +2 -2
  175. data/spec/adopter/project_detector_spec.rb +1 -1
  176. data/spec/commands/raider_commands_spec.rb +129 -0
  177. data/spec/generators/generator_spec.rb +23 -0
  178. data/spec/integration/commands/scaffolding_commands_spec.rb +1 -1
  179. data/spec/integration/commands/utility_commands_spec.rb +23 -3
  180. data/spec/integration/content/ci_content_spec.rb +119 -0
  181. data/spec/integration/content/common_content_spec.rb +288 -0
  182. data/spec/integration/content/config_content_spec.rb +175 -0
  183. data/spec/integration/content/content_helper.rb +32 -0
  184. data/spec/integration/content/gemfile_content_spec.rb +209 -0
  185. data/spec/integration/content/helper_content_spec.rb +485 -0
  186. data/spec/integration/content/page_content_spec.rb +259 -0
  187. data/spec/integration/content/reporter_content_spec.rb +236 -0
  188. data/spec/integration/content/skip_flags_content_spec.rb +206 -0
  189. data/spec/integration/content/syntax_validation_spec.rb +30 -0
  190. data/spec/integration/content/test_content_spec.rb +266 -0
  191. data/spec/integration/end_to_end_features_spec.rb +690 -0
  192. data/spec/integration/end_to_end_spec.rb +52 -16
  193. data/spec/integration/generators/automation_generator_spec.rb +0 -12
  194. data/spec/integration/generators/axe_addon_spec.rb +150 -0
  195. data/spec/integration/generators/common_generator_spec.rb +12 -13
  196. data/spec/integration/generators/config_features_spec.rb +155 -0
  197. data/spec/integration/generators/debug_helper_spec.rb +68 -0
  198. data/spec/integration/generators/helpers_generator_spec.rb +0 -12
  199. data/spec/integration/generators/lighthouse_addon_spec.rb +132 -0
  200. data/spec/integration/generators/minitest_generator_spec.rb +0 -6
  201. data/spec/integration/generators/reporter_spec.rb +159 -0
  202. data/spec/integration/generators/skip_flags_spec.rb +134 -0
  203. data/spec/integration/generators/visual_addon_spec.rb +148 -0
  204. data/spec/integration/settings_helper.rb +0 -3
  205. data/spec/integration/spec_helper.rb +30 -13
  206. data/spec/llm/client_spec.rb +79 -0
  207. data/spec/llm/config_spec.rb +92 -0
  208. data/spec/llm/prompts_spec.rb +49 -0
  209. data/spec/llm/response_parser_spec.rb +92 -0
  210. data/spec/menus/adopter_adopt_menu_spec.rb +97 -0
  211. data/spec/menus/menu_generator_spec.rb +263 -0
  212. data/spec/scaffolding/name_normalizer_spec.rb +113 -0
  213. data/spec/scaffolding/page_introspector_spec.rb +82 -0
  214. data/spec/scaffolding/scaffold_project_detector_spec.rb +104 -0
  215. data/spec/scaffolding/scaffolding_features_spec.rb +311 -0
  216. data/spec/scaffolding/url_analyzer_spec.rb +110 -0
  217. data/spec/system/adopt_matrix_spec.rb +537 -0
  218. data/spec/system/adopt_spec.rb +225 -0
  219. data/spec/system/support/system_test_helper.rb +0 -2
  220. data/spec/utilities/desktop_downloader_spec.rb +92 -0
  221. metadata +150 -5
  222. data/lib/generators/automation/templates/visual_options.tt +0 -16
  223. data/lib/generators/templates/helpers/partials/axe_driver.tt +0 -10
  224. data/lib/generators/templates/helpers/visual_spec_helper.tt +0 -35
@@ -0,0 +1,690 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_helper'
4
+ require_relative 'content/content_helper'
5
+ require_relative '../../lib/commands/scaffolding_commands'
6
+ require_relative '../../lib/scaffolding/scaffolding'
7
+ require 'timeout'
8
+ require 'open3'
9
+
10
+ # End-to-end tests for features C1-C8 (skip flags, reporter selection, retry, tags, etc.)
11
+ # These tests generate complete projects with feature flags and verify they are executable.
12
+ describe 'End-to-End Feature Validation' do
13
+ include ContentHelper
14
+ def run_in_project(project_name, command, timeout: 300)
15
+ result = { success: false, stdout: '', stderr: '', exit_code: nil }
16
+
17
+ Dir.chdir(project_name) do
18
+ Bundler.with_unbundled_env do
19
+ Timeout.timeout(timeout) do
20
+ stdout, stderr, status = Open3.capture3(command)
21
+ result[:stdout] = stdout
22
+ result[:stderr] = stderr
23
+ result[:exit_code] = status.exitstatus
24
+ result[:success] = status.success?
25
+ end
26
+ end
27
+ end
28
+
29
+ result
30
+ rescue Timeout::Error
31
+ result[:stderr] = "Command timed out after #{timeout} seconds"
32
+ result[:exit_code] = -1
33
+ result
34
+ end
35
+
36
+ def bundle_install(project_name)
37
+ run_in_project(project_name, 'bundle install --quiet', timeout: 180)[:success]
38
+ end
39
+
40
+ def run_rspec(project_name)
41
+ run_in_project(project_name, 'bundle exec rspec spec/ --format documentation', timeout: 120)
42
+ end
43
+
44
+ # Validate Ruby files, excluding abstract base classes (pre-existing template without frozen_string_literal)
45
+ def ruby_files(project_name)
46
+ Dir.glob("#{project_name}/**/*.rb").reject { |f| f.include?('/abstract/') }
47
+ end
48
+
49
+ # --- Shared examples ---
50
+
51
+ shared_examples 'executable rspec project with features' do |project_name|
52
+ it 'installs dependencies successfully', :slow do
53
+ expect(bundle_install(project_name)).to be true
54
+ end
55
+
56
+ it 'runs generated RSpec tests successfully', :slow do
57
+ skip 'Bundle install failed' unless bundle_install(project_name)
58
+
59
+ result = run_rspec(project_name)
60
+ expect(result[:success]).to be(true),
61
+ "RSpec tests failed:\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
62
+ end
63
+
64
+ it 'has rake tasks available', :slow do
65
+ skip 'Bundle install failed' unless bundle_install(project_name)
66
+
67
+ result = run_in_project(project_name, 'bundle exec rake -T', timeout: 30)
68
+ expect(result[:success]).to be true
69
+ expect(result[:stdout]).to include('spec')
70
+ end
71
+ end
72
+
73
+ shared_examples 'project with valid file structure' do |project_name|
74
+ it 'has Gemfile' do
75
+ expect(File).to exist("#{project_name}/Gemfile")
76
+ end
77
+
78
+ it 'has Rakefile' do
79
+ expect(File).to exist("#{project_name}/Rakefile")
80
+ end
81
+
82
+ it 'has spec_helper' do
83
+ expect(File).to exist("#{project_name}/helpers/spec_helper.rb")
84
+ end
85
+
86
+ it 'has .rspec' do
87
+ expect(File).to exist("#{project_name}/.rspec")
88
+ end
89
+
90
+ it 'all Ruby files have valid syntax' do
91
+ ruby_files(project_name).each do |file|
92
+ content = File.read(file)
93
+ expect(content).to have_valid_ruby_syntax,
94
+ "#{file} has invalid syntax"
95
+ end
96
+ end
97
+
98
+ it 'all Ruby files have frozen_string_literal' do
99
+ ruby_files(project_name).each do |file|
100
+ content = File.read(file)
101
+ expect(content).to have_frozen_string_literal,
102
+ "#{file} missing frozen_string_literal"
103
+ end
104
+ end
105
+ end
106
+
107
+ # --- Feature: Skip flags (C1) ---
108
+
109
+ context 'Skip flags' do
110
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
111
+ InvokeGenerators.generate_framework(
112
+ automation: 'selenium', framework: 'rspec', name: 'e2e_skip_allure',
113
+ skip_allure: true
114
+ )
115
+ InvokeGenerators.generate_framework(
116
+ automation: 'selenium', framework: 'rspec', name: 'e2e_skip_video',
117
+ skip_video: true
118
+ )
119
+ InvokeGenerators.generate_framework(
120
+ automation: 'selenium', framework: 'rspec', name: 'e2e_skip_all',
121
+ skip_allure: true, skip_video: true
122
+ )
123
+ end
124
+
125
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
126
+ %w[e2e_skip_allure e2e_skip_video e2e_skip_all].each { |n| FileUtils.rm_rf(n) }
127
+ end
128
+
129
+ describe 'project with --skip-allure' do
130
+ include_examples 'project with valid file structure', 'e2e_skip_allure'
131
+ include_examples 'executable rspec project with features', 'e2e_skip_allure'
132
+ end
133
+
134
+ describe 'project with --skip-video' do
135
+ include_examples 'project with valid file structure', 'e2e_skip_video'
136
+ include_examples 'executable rspec project with features', 'e2e_skip_video'
137
+ end
138
+
139
+ describe 'project with all skip flags' do
140
+ include_examples 'project with valid file structure', 'e2e_skip_all'
141
+ include_examples 'executable rspec project with features', 'e2e_skip_all'
142
+ end
143
+ end
144
+
145
+ # --- Feature: Reporter selection (C7/C8) ---
146
+
147
+ context 'Reporter selection' do
148
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
149
+ InvokeGenerators.generate_framework(
150
+ automation: 'selenium', framework: 'rspec', name: 'e2e_reporter_junit',
151
+ reporter: 'junit'
152
+ )
153
+ InvokeGenerators.generate_framework(
154
+ automation: 'selenium', framework: 'rspec', name: 'e2e_reporter_none',
155
+ reporter: 'none'
156
+ )
157
+ InvokeGenerators.generate_framework(
158
+ automation: 'selenium', framework: 'rspec', name: 'e2e_reporter_both',
159
+ reporter: 'both'
160
+ )
161
+ end
162
+
163
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
164
+ %w[e2e_reporter_junit e2e_reporter_none e2e_reporter_both].each { |n| FileUtils.rm_rf(n) }
165
+ end
166
+
167
+ describe 'project with reporter: junit' do
168
+ include_examples 'project with valid file structure', 'e2e_reporter_junit'
169
+ include_examples 'executable rspec project with features', 'e2e_reporter_junit'
170
+
171
+ it 'includes rspec_junit_formatter in Gemfile' do
172
+ gemfile = File.read('e2e_reporter_junit/Gemfile')
173
+ expect(gemfile).to include('rspec_junit_formatter')
174
+ end
175
+
176
+ it 'does not include allure gems in Gemfile' do
177
+ gemfile = File.read('e2e_reporter_junit/Gemfile')
178
+ expect(gemfile).not_to include('allure-rspec')
179
+ end
180
+ end
181
+
182
+ describe 'project with reporter: none' do
183
+ include_examples 'project with valid file structure', 'e2e_reporter_none'
184
+ include_examples 'executable rspec project with features', 'e2e_reporter_none'
185
+
186
+ it 'does not include reporter gems' do
187
+ gemfile = File.read('e2e_reporter_none/Gemfile')
188
+ expect(gemfile).not_to include('allure-rspec')
189
+ expect(gemfile).not_to include('rspec_junit_formatter')
190
+ end
191
+ end
192
+
193
+ describe 'project with reporter: both' do
194
+ include_examples 'project with valid file structure', 'e2e_reporter_both'
195
+ include_examples 'executable rspec project with features', 'e2e_reporter_both'
196
+
197
+ it 'includes both allure and junit gems in Gemfile' do
198
+ gemfile = File.read('e2e_reporter_both/Gemfile')
199
+ expect(gemfile).to include('allure-rspec')
200
+ expect(gemfile).to include('rspec_junit_formatter')
201
+ end
202
+ end
203
+ end
204
+
205
+ # --- Feature: Retry config (C4) ---
206
+
207
+ context 'Retry configuration' do
208
+ it 'default rspec projects include rspec-retry gem' do
209
+ gemfile = read_generated("#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}", 'Gemfile')
210
+ expect(gemfile).to include("gem 'rspec-retry'")
211
+ end
212
+
213
+ it 'default rspec projects have retry config in spec_helper' do
214
+ helper = read_generated("#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}", 'helpers/spec_helper.rb')
215
+ expect(helper).to include('config.verbose_retry = true')
216
+ expect(helper).to include("ENV.fetch('RETRY_COUNT', 0)")
217
+ end
218
+
219
+ it 'cucumber projects have --retry in cucumber.yml' do
220
+ cucumber_yml = read_generated("#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}", 'cucumber.yml')
221
+ expect(cucumber_yml).to include('--retry')
222
+ end
223
+ end
224
+
225
+ # --- Feature: Tag-based rake tasks (C5) ---
226
+
227
+ context 'Tag-based rake tasks' do
228
+ let(:rspec_project) { "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}" }
229
+ let(:cucumber_project) { "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}" }
230
+
231
+ it 'rspec Rakefile has smoke task' do
232
+ rakefile = read_generated(rspec_project, 'Rakefile')
233
+ expect(rakefile).to include('RakeTask.new(:smoke)')
234
+ expect(rakefile).to include("'--tag smoke'")
235
+ end
236
+
237
+ it 'rspec Rakefile has regression task' do
238
+ rakefile = read_generated(rspec_project, 'Rakefile')
239
+ expect(rakefile).to include('RakeTask.new(:regression)')
240
+ expect(rakefile).to include("'--tag regression'")
241
+ end
242
+
243
+ it 'cucumber Rakefile has smoke task with cucumber tags' do
244
+ rakefile = read_generated(cucumber_project, 'Rakefile')
245
+ expect(rakefile).to include("'--tags @smoke'")
246
+ end
247
+
248
+ it 'rspec smoke rake task works', :slow do
249
+ skip 'Bundle install needed' unless bundle_install(rspec_project)
250
+
251
+ result = run_in_project(rspec_project, 'bundle exec rake smoke', timeout: 60)
252
+ expect(result[:success]).to be true
253
+ end
254
+ end
255
+
256
+ # --- Feature: Re-run failed tests (C6) ---
257
+
258
+ context 'Re-run failed tests' do
259
+ let(:rspec_project) { "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}" }
260
+ let(:cucumber_project) { "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}" }
261
+
262
+ it 'rspec projects have .rspec file' do
263
+ expect(File).to exist("#{rspec_project}/.rspec")
264
+ end
265
+
266
+ it '.rspec includes documentation format' do
267
+ rspec = read_generated(rspec_project, '.rspec')
268
+ expect(rspec).to include('--format documentation')
269
+ end
270
+
271
+ it 'spec_helper has persistence file path' do
272
+ helper = read_generated(rspec_project, 'helpers/spec_helper.rb')
273
+ expect(helper).to include("config.example_status_persistence_file_path = 'spec/examples.txt'")
274
+ end
275
+
276
+ it '.gitignore includes spec/examples.txt' do
277
+ gitignore = read_generated(rspec_project, '.gitignore')
278
+ expect(gitignore).to include('spec/examples.txt')
279
+ end
280
+
281
+ it 'cucumber projects do not have .rspec file' do
282
+ expect(File).not_to exist("#{cucumber_project}/.rspec")
283
+ end
284
+ end
285
+
286
+ # --- Feature: Destroy command (C2) ---
287
+
288
+ context 'Destroy command' do
289
+ let(:project) { "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}" }
290
+
291
+ it 'creates and destroys scaffolded files' do
292
+ original_dir = Dir.pwd
293
+ Dir.chdir(project)
294
+
295
+ ScaffoldingCommands.new.invoke(:scaffold, nil, %w[e2e_checkout])
296
+ expect(File).to exist('page_objects/pages/e2e_checkout.rb')
297
+ expect(File).to exist('spec/e2e_checkout_page_spec.rb')
298
+
299
+ ScaffoldingCommands.new.invoke(:destroy, nil, %w[e2e_checkout])
300
+ expect(File).not_to exist('page_objects/pages/e2e_checkout.rb')
301
+ expect(File).not_to exist('spec/e2e_checkout_page_spec.rb')
302
+ ensure
303
+ Dir.chdir(original_dir)
304
+ end
305
+ end
306
+
307
+ # --- Feature: Accessibility addon (axe) ---
308
+
309
+ context 'Accessibility addon' do
310
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
311
+ InvokeGenerators.generate_framework(
312
+ automation: 'selenium', framework: 'rspec', name: 'e2e_axe_rspec',
313
+ accessibility: true
314
+ )
315
+ InvokeGenerators.generate_framework(
316
+ automation: 'selenium', framework: 'cucumber', name: 'e2e_axe_cucumber',
317
+ accessibility: true
318
+ )
319
+ InvokeGenerators.generate_framework(
320
+ automation: 'selenium', framework: 'minitest', name: 'e2e_axe_minitest',
321
+ accessibility: true
322
+ )
323
+ end
324
+
325
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
326
+ %w[e2e_axe_rspec e2e_axe_cucumber e2e_axe_minitest].each { |n| FileUtils.rm_rf(n) }
327
+ end
328
+
329
+ describe 'axe + RSpec' do
330
+ include_examples 'project with valid file structure', 'e2e_axe_rspec'
331
+
332
+ it 'creates accessibility_spec.rb' do
333
+ expect(File).to exist('e2e_axe_rspec/spec/accessibility_spec.rb')
334
+ end
335
+
336
+ it 'includes axe gems in Gemfile' do
337
+ gemfile = File.read('e2e_axe_rspec/Gemfile')
338
+ expect(gemfile).to include('axe')
339
+ end
340
+
341
+ it 'accessibility spec has valid Ruby syntax' do
342
+ content = File.read('e2e_axe_rspec/spec/accessibility_spec.rb')
343
+ expect { RubyVM::InstructionSequence.compile(content) }.not_to raise_error
344
+ end
345
+ end
346
+
347
+ describe 'axe + Cucumber' do
348
+ it 'creates accessibility.feature' do
349
+ expect(File).to exist('e2e_axe_cucumber/features/accessibility.feature')
350
+ end
351
+
352
+ it 'creates accessibility_steps.rb' do
353
+ expect(File).to exist('e2e_axe_cucumber/features/step_definitions/accessibility_steps.rb')
354
+ end
355
+
356
+ it 'includes axe gems in Gemfile' do
357
+ gemfile = File.read('e2e_axe_cucumber/Gemfile')
358
+ expect(gemfile).to include('axe')
359
+ end
360
+
361
+ it 'all Ruby files have valid syntax' do
362
+ ruby_files('e2e_axe_cucumber').each do |file|
363
+ content = File.read(file)
364
+ expect(content).to have_valid_ruby_syntax, "#{file} has invalid syntax"
365
+ end
366
+ end
367
+ end
368
+
369
+ describe 'axe + Minitest' do
370
+ it 'creates test_accessibility.rb' do
371
+ expect(File).to exist('e2e_axe_minitest/test/test_accessibility.rb')
372
+ end
373
+
374
+ it 'includes axe gems in Gemfile' do
375
+ gemfile = File.read('e2e_axe_minitest/Gemfile')
376
+ expect(gemfile).to include('axe')
377
+ end
378
+
379
+ it 'accessibility test has valid Ruby syntax' do
380
+ content = File.read('e2e_axe_minitest/test/test_accessibility.rb')
381
+ expect { RubyVM::InstructionSequence.compile(content) }.not_to raise_error
382
+ end
383
+ end
384
+ end
385
+
386
+ # --- Feature: Visual regression addon ---
387
+
388
+ context 'Visual regression addon' do
389
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
390
+ InvokeGenerators.generate_framework(
391
+ automation: 'selenium', framework: 'rspec', name: 'e2e_visual_rspec',
392
+ visual: true
393
+ )
394
+ InvokeGenerators.generate_framework(
395
+ automation: 'selenium', framework: 'cucumber', name: 'e2e_visual_cucumber',
396
+ visual: true
397
+ )
398
+ InvokeGenerators.generate_framework(
399
+ automation: 'selenium', framework: 'minitest', name: 'e2e_visual_minitest',
400
+ visual: true
401
+ )
402
+ end
403
+
404
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
405
+ %w[e2e_visual_rspec e2e_visual_cucumber e2e_visual_minitest].each { |n| FileUtils.rm_rf(n) }
406
+ end
407
+
408
+ describe 'visual + RSpec' do
409
+ include_examples 'project with valid file structure', 'e2e_visual_rspec'
410
+
411
+ it 'creates visual_helper.rb' do
412
+ expect(File).to exist('e2e_visual_rspec/helpers/visual_helper.rb')
413
+ end
414
+
415
+ it 'creates visual_spec.rb' do
416
+ expect(File).to exist('e2e_visual_rspec/spec/visual_spec.rb')
417
+ end
418
+
419
+ it 'includes chunky_png in Gemfile' do
420
+ gemfile = File.read('e2e_visual_rspec/Gemfile')
421
+ expect(gemfile).to include("gem 'chunky_png'")
422
+ end
423
+
424
+ it 'visual helper has valid Ruby syntax' do
425
+ content = File.read('e2e_visual_rspec/helpers/visual_helper.rb')
426
+ expect { RubyVM::InstructionSequence.compile(content) }.not_to raise_error
427
+ end
428
+ end
429
+
430
+ describe 'visual + Cucumber' do
431
+ it 'creates visual.feature' do
432
+ expect(File).to exist('e2e_visual_cucumber/features/visual.feature')
433
+ end
434
+
435
+ it 'creates visual_steps.rb' do
436
+ expect(File).to exist('e2e_visual_cucumber/features/step_definitions/visual_steps.rb')
437
+ end
438
+
439
+ it 'all Ruby files have valid syntax' do
440
+ ruby_files('e2e_visual_cucumber').each do |file|
441
+ content = File.read(file)
442
+ expect(content).to have_valid_ruby_syntax, "#{file} has invalid syntax"
443
+ end
444
+ end
445
+ end
446
+
447
+ describe 'visual + Minitest' do
448
+ it 'creates test_visual.rb' do
449
+ expect(File).to exist('e2e_visual_minitest/test/test_visual.rb')
450
+ end
451
+
452
+ it 'visual test has valid Ruby syntax' do
453
+ content = File.read('e2e_visual_minitest/test/test_visual.rb')
454
+ expect { RubyVM::InstructionSequence.compile(content) }.not_to raise_error
455
+ end
456
+ end
457
+ end
458
+
459
+ # --- Feature: Lighthouse performance addon ---
460
+
461
+ context 'Lighthouse performance addon' do
462
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
463
+ InvokeGenerators.generate_framework(
464
+ automation: 'selenium', framework: 'rspec', name: 'e2e_lighthouse_rspec',
465
+ performance: true
466
+ )
467
+ InvokeGenerators.generate_framework(
468
+ automation: 'selenium', framework: 'cucumber', name: 'e2e_lighthouse_cucumber',
469
+ performance: true
470
+ )
471
+ InvokeGenerators.generate_framework(
472
+ automation: 'selenium', framework: 'minitest', name: 'e2e_lighthouse_minitest',
473
+ performance: true
474
+ )
475
+ end
476
+
477
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
478
+ %w[e2e_lighthouse_rspec e2e_lighthouse_cucumber e2e_lighthouse_minitest].each { |n| FileUtils.rm_rf(n) }
479
+ end
480
+
481
+ describe 'lighthouse + RSpec' do
482
+ include_examples 'project with valid file structure', 'e2e_lighthouse_rspec'
483
+
484
+ it 'creates performance_helper.rb' do
485
+ expect(File).to exist('e2e_lighthouse_rspec/helpers/performance_helper.rb')
486
+ end
487
+
488
+ it 'creates performance_spec.rb' do
489
+ expect(File).to exist('e2e_lighthouse_rspec/spec/performance_spec.rb')
490
+ end
491
+
492
+ it 'performance helper has valid Ruby syntax' do
493
+ content = File.read('e2e_lighthouse_rspec/helpers/performance_helper.rb')
494
+ expect { RubyVM::InstructionSequence.compile(content) }.not_to raise_error
495
+ end
496
+ end
497
+
498
+ describe 'lighthouse + Cucumber' do
499
+ it 'creates performance.feature' do
500
+ expect(File).to exist('e2e_lighthouse_cucumber/features/performance.feature')
501
+ end
502
+
503
+ it 'creates performance_steps.rb' do
504
+ expect(File).to exist('e2e_lighthouse_cucumber/features/step_definitions/performance_steps.rb')
505
+ end
506
+
507
+ it 'all Ruby files have valid syntax' do
508
+ ruby_files('e2e_lighthouse_cucumber').each do |file|
509
+ content = File.read(file)
510
+ expect(content).to have_valid_ruby_syntax, "#{file} has invalid syntax"
511
+ end
512
+ end
513
+ end
514
+
515
+ describe 'lighthouse + Minitest' do
516
+ it 'creates test_performance.rb' do
517
+ expect(File).to exist('e2e_lighthouse_minitest/test/test_performance.rb')
518
+ end
519
+
520
+ it 'performance test has valid Ruby syntax' do
521
+ content = File.read('e2e_lighthouse_minitest/test/test_performance.rb')
522
+ expect { RubyVM::InstructionSequence.compile(content) }.not_to raise_error
523
+ end
524
+ end
525
+ end
526
+
527
+ # --- Feature: CI/CD platform generation ---
528
+
529
+ context 'CI/CD platform generation' do
530
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
531
+ InvokeGenerators.generate_framework(
532
+ automation: 'selenium', framework: 'rspec', name: 'e2e_ci_github',
533
+ ci_platform: 'github'
534
+ )
535
+ InvokeGenerators.generate_framework(
536
+ automation: 'selenium', framework: 'rspec', name: 'e2e_ci_gitlab',
537
+ ci_platform: 'gitlab'
538
+ )
539
+ end
540
+
541
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
542
+ %w[e2e_ci_github e2e_ci_gitlab].each { |n| FileUtils.rm_rf(n) }
543
+ end
544
+
545
+ describe 'GitHub Actions' do
546
+ include_examples 'project with valid file structure', 'e2e_ci_github'
547
+
548
+ it 'creates GitHub Actions workflow' do
549
+ expect(File).to exist('e2e_ci_github/.github/workflows/test_pipeline.yml')
550
+ end
551
+
552
+ it 'workflow is valid YAML' do
553
+ content = File.read('e2e_ci_github/.github/workflows/test_pipeline.yml')
554
+ expect { YAML.safe_load(content) }.not_to raise_error
555
+ end
556
+
557
+ it 'workflow includes rspec run step' do
558
+ content = File.read('e2e_ci_github/.github/workflows/test_pipeline.yml')
559
+ expect(content).to include('rspec')
560
+ end
561
+ end
562
+
563
+ describe 'GitLab CI' do
564
+ include_examples 'project with valid file structure', 'e2e_ci_gitlab'
565
+
566
+ it 'creates GitLab CI config' do
567
+ expect(File).to exist('e2e_ci_gitlab/gitlab-ci.yml')
568
+ end
569
+
570
+ it 'config is valid YAML' do
571
+ content = File.read('e2e_ci_gitlab/gitlab-ci.yml')
572
+ expect { YAML.safe_load(content) }.not_to raise_error
573
+ end
574
+
575
+ it 'config includes stages' do
576
+ content = File.read('e2e_ci_gitlab/gitlab-ci.yml')
577
+ expect(content).to include('stages:')
578
+ end
579
+ end
580
+ end
581
+
582
+ # --- Feature: Debug helper ---
583
+
584
+ context 'Debug helper generation' do
585
+ it 'web projects include debug_helper.rb' do
586
+ expect(File).to exist("#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}/helpers/debug_helper.rb")
587
+ end
588
+
589
+ it 'debug helper has valid Ruby syntax' do
590
+ content = File.read("#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}/helpers/debug_helper.rb")
591
+ expect { RubyVM::InstructionSequence.compile(content) }.not_to raise_error
592
+ end
593
+
594
+ it 'debug helper defines DebugHelper module' do
595
+ content = File.read("#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}/helpers/debug_helper.rb")
596
+ expect(content).to include('module DebugHelper')
597
+ end
598
+
599
+ it 'config.yml includes debug section' do
600
+ config = YAML.load_file("#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}/config/config.yml")
601
+ expect(config).to have_key('debug')
602
+ expect(config['debug']['enabled']).to be false
603
+ end
604
+ end
605
+
606
+ # --- Feature: Combined addons ---
607
+
608
+ context 'Multiple addons combined' do
609
+ before(:all) do # rubocop:disable RSpec/BeforeAfterAll
610
+ InvokeGenerators.generate_framework(
611
+ automation: 'selenium', framework: 'rspec', name: 'e2e_all_addons',
612
+ accessibility: true, visual: true, performance: true
613
+ )
614
+ end
615
+
616
+ after(:all) do # rubocop:disable RSpec/BeforeAfterAll
617
+ FileUtils.rm_rf('e2e_all_addons')
618
+ end
619
+
620
+ it 'creates all addon files' do
621
+ expect(File).to exist('e2e_all_addons/spec/accessibility_spec.rb')
622
+ expect(File).to exist('e2e_all_addons/helpers/visual_helper.rb')
623
+ expect(File).to exist('e2e_all_addons/spec/visual_spec.rb')
624
+ expect(File).to exist('e2e_all_addons/helpers/performance_helper.rb')
625
+ expect(File).to exist('e2e_all_addons/spec/performance_spec.rb')
626
+ end
627
+
628
+ it 'Gemfile includes all addon gems' do
629
+ gemfile = File.read('e2e_all_addons/Gemfile')
630
+ expect(gemfile).to include('axe')
631
+ expect(gemfile).to include("gem 'chunky_png'")
632
+ end
633
+
634
+ it 'all Ruby files have valid syntax' do
635
+ ruby_files('e2e_all_addons').each do |file|
636
+ content = File.read(file)
637
+ expect(content).to have_valid_ruby_syntax, "#{file} has invalid syntax"
638
+ end
639
+ end
640
+
641
+ it 'all Ruby files (except abstract) have frozen_string_literal' do
642
+ ruby_files('e2e_all_addons').each do |file|
643
+ content = File.read(file)
644
+ expect(content).to have_frozen_string_literal, "#{file} missing frozen_string_literal"
645
+ end
646
+ end
647
+ end
648
+
649
+ # --- Feature: Template override (C3) ---
650
+
651
+ context 'Template override system' do
652
+ let(:project) { "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}" }
653
+
654
+ it 'uses override template when present' do
655
+ original_dir = Dir.pwd
656
+ Dir.chdir(project)
657
+ FileUtils.mkdir_p('.ruby_raider/templates')
658
+
659
+ File.write('.ruby_raider/templates/page_object.tt', <<~ERB)
660
+ # frozen_string_literal: true
661
+
662
+ class <%= page_class_name %> < Page
663
+ # E2E_CUSTOM_OVERRIDE_MARKER
664
+ end
665
+ ERB
666
+
667
+ Scaffolding.new(%w[e2e_override]).generate_page
668
+ content = File.read('page_objects/pages/e2e_override.rb')
669
+ expect(content).to include('E2E_CUSTOM_OVERRIDE_MARKER')
670
+ expect(content).to include('class E2eOverridePage < Page')
671
+ ensure
672
+ FileUtils.rm_f('page_objects/pages/e2e_override.rb')
673
+ FileUtils.rm_rf('.ruby_raider')
674
+ Dir.chdir(original_dir)
675
+ end
676
+
677
+ it 'falls back to default when no override present' do
678
+ original_dir = Dir.pwd
679
+ Dir.chdir(project)
680
+
681
+ Scaffolding.new(%w[e2e_default]).generate_page
682
+ content = File.read('page_objects/pages/e2e_default.rb')
683
+ expect(content).not_to include('E2E_CUSTOM_OVERRIDE_MARKER')
684
+ expect(content).to include('class E2eDefaultPage < Page')
685
+ ensure
686
+ FileUtils.rm_f('page_objects/pages/e2e_default.rb')
687
+ Dir.chdir(original_dir)
688
+ end
689
+ end
690
+ end