ruby_raider 1.1.4 → 2.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/integration.yml +4 -6
  3. data/.github/workflows/reek.yml +6 -5
  4. data/.github/workflows/release.yml +175 -0
  5. data/.github/workflows/rubocop.yml +7 -6
  6. data/.github/workflows/system_tests.yml +83 -0
  7. data/.gitignore +1 -1
  8. data/.rubocop.yml +24 -0
  9. data/README.md +3 -1
  10. data/RELEASE.md +412 -0
  11. data/RELEASE_QUICK_GUIDE.md +77 -0
  12. data/bin/release +186 -0
  13. data/lib/adopter/adopt_menu.rb +150 -0
  14. data/lib/adopter/converters/base_converter.rb +85 -0
  15. data/lib/adopter/converters/identity_converter.rb +56 -0
  16. data/lib/adopter/migration_plan.rb +75 -0
  17. data/lib/adopter/migrator.rb +96 -0
  18. data/lib/adopter/plan_builder.rb +278 -0
  19. data/lib/adopter/project_analyzer.rb +256 -0
  20. data/lib/adopter/project_detector.rb +159 -0
  21. data/lib/commands/adopt_commands.rb +43 -0
  22. data/lib/generators/automation/templates/account.tt +9 -5
  23. data/lib/generators/automation/templates/appium_caps.tt +60 -6
  24. data/lib/generators/automation/templates/home.tt +4 -4
  25. data/lib/generators/automation/templates/login.tt +61 -4
  26. data/lib/generators/automation/templates/page.tt +13 -7
  27. data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -4
  28. data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -1
  29. data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -4
  30. data/lib/generators/automation/templates/partials/visit_method.tt +11 -1
  31. data/lib/generators/automation/templates/pdp.tt +1 -1
  32. data/lib/generators/cucumber/templates/env.tt +6 -4
  33. data/lib/generators/cucumber/templates/partials/capybara_env.tt +20 -0
  34. data/lib/generators/cucumber/templates/partials/capybara_world.tt +6 -0
  35. data/lib/generators/cucumber/templates/partials/mobile_steps.tt +2 -2
  36. data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -3
  37. data/lib/generators/cucumber/templates/steps.tt +2 -2
  38. data/lib/generators/cucumber/templates/world.tt +5 -3
  39. data/lib/generators/generator.rb +14 -2
  40. data/lib/generators/helper_generator.rb +16 -3
  41. data/lib/generators/infrastructure/github_generator.rb +6 -0
  42. data/lib/generators/infrastructure/templates/github.tt +11 -7
  43. data/lib/generators/infrastructure/templates/github_appium.tt +108 -0
  44. data/lib/generators/infrastructure/templates/gitlab.tt +5 -2
  45. data/lib/generators/invoke_generators.rb +1 -0
  46. data/lib/generators/menu_generator.rb +2 -0
  47. data/lib/generators/minitest/minitest_generator.rb +23 -0
  48. data/lib/generators/minitest/templates/test.tt +93 -0
  49. data/lib/generators/rspec/templates/spec.tt +12 -10
  50. data/lib/generators/template_renderer/partial_cache.rb +116 -0
  51. data/lib/generators/template_renderer/partial_resolver.rb +103 -0
  52. data/lib/generators/template_renderer/template_error.rb +50 -0
  53. data/lib/generators/template_renderer.rb +90 -0
  54. data/lib/generators/templates/common/config.tt +2 -2
  55. data/lib/generators/templates/common/gemfile.tt +15 -3
  56. data/lib/generators/templates/common/partials/web_config.tt +1 -1
  57. data/lib/generators/templates/common/read_me.tt +3 -1
  58. data/lib/generators/templates/helpers/allure_helper.tt +2 -2
  59. data/lib/generators/templates/helpers/browser_helper.tt +1 -0
  60. data/lib/generators/templates/helpers/capybara_helper.tt +28 -0
  61. data/lib/generators/templates/helpers/driver_helper.tt +1 -1
  62. data/lib/generators/templates/helpers/partials/allure_imports.tt +3 -1
  63. data/lib/generators/templates/helpers/partials/allure_requirements.tt +3 -1
  64. data/lib/generators/templates/helpers/partials/appium_driver.tt +46 -0
  65. data/lib/generators/templates/helpers/partials/axe_driver.tt +10 -0
  66. data/lib/generators/templates/helpers/partials/browserstack_config.tt +13 -0
  67. data/lib/generators/templates/helpers/partials/driver_and_options.tt +6 -114
  68. data/lib/generators/templates/helpers/partials/quit_driver.tt +3 -1
  69. data/lib/generators/templates/helpers/partials/screenshot.tt +3 -1
  70. data/lib/generators/templates/helpers/partials/selenium_driver.tt +25 -0
  71. data/lib/generators/templates/helpers/spec_helper.tt +17 -4
  72. data/lib/generators/templates/helpers/test_helper.tt +26 -0
  73. data/lib/generators/templates/helpers/visual_spec_helper.tt +1 -1
  74. data/lib/ruby_raider.rb +5 -0
  75. data/lib/version +1 -1
  76. data/spec/adopter/adopt_menu_spec.rb +176 -0
  77. data/spec/adopter/converters/identity_converter_spec.rb +145 -0
  78. data/spec/adopter/migration_plan_spec.rb +113 -0
  79. data/spec/adopter/migrator_spec.rb +277 -0
  80. data/spec/adopter/plan_builder_spec.rb +298 -0
  81. data/spec/adopter/project_analyzer_spec.rb +337 -0
  82. data/spec/adopter/project_detector_spec.rb +295 -0
  83. data/spec/generators/fixtures/templates/test.tt +1 -0
  84. data/spec/generators/fixtures/templates/test_partial.tt +1 -0
  85. data/spec/generators/template_renderer_spec.rb +298 -0
  86. data/spec/integration/commands/scaffolding_commands_spec.rb +2 -2
  87. data/spec/integration/commands/utility_commands_spec.rb +2 -2
  88. data/spec/integration/end_to_end_spec.rb +325 -0
  89. data/spec/integration/generators/automation_generator_spec.rb +11 -11
  90. data/spec/integration/generators/common_generator_spec.rb +40 -40
  91. data/spec/integration/generators/cucumber_generator_spec.rb +7 -7
  92. data/spec/integration/generators/github_generator_spec.rb +8 -8
  93. data/spec/integration/generators/gitlab_generator_spec.rb +8 -8
  94. data/spec/integration/generators/helpers_generator_spec.rb +73 -35
  95. data/spec/integration/generators/minitest_generator_spec.rb +70 -0
  96. data/spec/integration/generators/rspec_generator_spec.rb +7 -7
  97. data/spec/integration/settings_helper.rb +1 -1
  98. data/spec/integration/spec_helper.rb +20 -2
  99. data/spec/system/capybara_spec.rb +42 -0
  100. data/spec/system/selenium_spec.rb +19 -17
  101. data/spec/system/support/system_test_helper.rb +35 -0
  102. data/spec/system/watir_spec.rb +19 -17
  103. metadata +46 -16
  104. data/.github/workflows/push_gem.yml +0 -37
  105. data/.github/workflows/selenium.yml +0 -22
  106. data/.github/workflows/watir.yml +0 -22
  107. data/lib/generators/automation/templates/partials/android_caps.tt +0 -17
  108. data/lib/generators/automation/templates/partials/cross_platform_caps.tt +0 -25
  109. data/lib/generators/automation/templates/partials/ios_caps.tt +0 -18
  110. data/lib/generators/automation/templates/partials/selenium_account.tt +0 -9
  111. data/lib/generators/automation/templates/partials/selenium_login.tt +0 -34
  112. data/lib/generators/automation/templates/partials/watir_account.tt +0 -7
  113. data/lib/generators/automation/templates/partials/watir_login.tt +0 -32
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../integration/spec_helper'
4
+ require_relative '../../lib/generators/template_renderer'
5
+ require_relative '../../lib/generators/generator'
6
+
7
+ RSpec.describe TemplateRenderer do
8
+ # Create a test generator class that includes TemplateRenderer
9
+ let(:test_generator_class) do
10
+ Class.new do
11
+ include TemplateRenderer
12
+
13
+ def self.source_paths
14
+ [File.expand_path('../fixtures/templates', __dir__)]
15
+ end
16
+
17
+ # Mock predicate methods that templates might use
18
+ def selenium_based?
19
+ true
20
+ end
21
+
22
+ def watir?
23
+ false
24
+ end
25
+
26
+ def mobile?
27
+ false
28
+ end
29
+ end
30
+ end
31
+
32
+ let(:test_instance) { test_generator_class.new }
33
+
34
+ describe '#partial' do
35
+ context 'when rendering a simple partial' do
36
+ it 'renders the partial content' do
37
+ # This test requires a fixtures directory with test templates
38
+ # For now, we'll test the API interface
39
+ expect(test_instance).to respond_to(:partial)
40
+ end
41
+
42
+ it 'accepts a partial name as first argument' do
43
+ expect { test_instance.partial('test_partial') }.not_to raise_error(ArgumentError)
44
+ end
45
+
46
+ it 'accepts options hash' do
47
+ expect { test_instance.partial('test', trim_mode: '-', strip: true) }.not_to raise_error(ArgumentError)
48
+ end
49
+ end
50
+
51
+ context 'with strip option' do
52
+ it 'applies strip when strip: true' do
53
+ # Mock the template renderer to verify strip is called
54
+ allow_any_instance_of(TemplateRenderer::PartialCache).to receive(:render_partial)
55
+ .and_return(" content \n")
56
+
57
+ result = test_instance.partial('test', strip: true)
58
+ expect(result).to eq('content')
59
+ end
60
+
61
+ it 'does not apply strip when strip: false (default)' do
62
+ allow_any_instance_of(TemplateRenderer::PartialCache).to receive(:render_partial)
63
+ .and_return(" content \n")
64
+
65
+ result = test_instance.partial('test', strip: false)
66
+ expect(result).to eq(" content \n")
67
+ end
68
+ end
69
+
70
+ context 'with trim_mode option' do
71
+ it 'uses default trim_mode: "-" when not specified' do
72
+ cache = test_generator_class.template_renderer
73
+ expect(cache).to receive(:render_partial).with('test', anything, hash_including(trim_mode: '-'))
74
+ test_instance.partial('test')
75
+ end
76
+
77
+ it 'allows custom trim_mode' do
78
+ cache = test_generator_class.template_renderer
79
+ expect(cache).to receive(:render_partial).with('test', anything, hash_including(trim_mode: '<>'))
80
+ test_instance.partial('test', trim_mode: '<>')
81
+ end
82
+
83
+ it 'disables trim_mode when trim: false' do
84
+ cache = test_generator_class.template_renderer
85
+ expect(cache).to receive(:render_partial).with('test', anything, hash_including(trim_mode: nil))
86
+ test_instance.partial('test', trim: false)
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'ClassMethods' do
92
+ describe '.template_renderer' do
93
+ it 'returns a PartialCache instance' do
94
+ expect(test_generator_class.template_renderer).to be_a(TemplateRenderer::PartialCache)
95
+ end
96
+
97
+ it 'returns the same instance on multiple calls (memoization)' do
98
+ first = test_generator_class.template_renderer
99
+ second = test_generator_class.template_renderer
100
+ expect(first).to be(second)
101
+ end
102
+ end
103
+
104
+ describe '.clear_template_cache' do
105
+ it 'clears the template cache' do
106
+ test_generator_class.template_renderer # Initialize cache
107
+ test_generator_class.clear_template_cache
108
+ expect(test_generator_class.instance_variable_get(:@template_renderer)).to be_nil
109
+ end
110
+ end
111
+
112
+ describe '.template_cache_stats' do
113
+ it 'returns cache statistics' do
114
+ stats = test_generator_class.template_cache_stats
115
+ expect(stats).to have_key(:size)
116
+ expect(stats).to have_key(:entries)
117
+ expect(stats).to have_key(:memory_estimate)
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ RSpec.describe TemplateRenderer::PartialCache do
124
+ let(:generator_class) do
125
+ Class.new do
126
+ def self.source_paths
127
+ [File.expand_path('../fixtures/templates', __dir__)]
128
+ end
129
+ end
130
+ end
131
+
132
+ let(:cache) { described_class.new(generator_class) }
133
+ let(:test_binding) { binding }
134
+
135
+ describe '#render_partial' do
136
+ context 'when partial does not exist' do
137
+ it 'raises TemplateNotFoundError' do
138
+ expect do
139
+ cache.render_partial('nonexistent', test_binding, {})
140
+ end.to raise_error(TemplateRenderer::TemplateNotFoundError)
141
+ end
142
+
143
+ it 'includes searched paths in error message' do
144
+ expect do
145
+ cache.render_partial('missing', test_binding, {})
146
+ end.to raise_error(TemplateRenderer::TemplateNotFoundError, /Searched in/)
147
+ end
148
+ end
149
+
150
+ context 'when partial has syntax errors' do
151
+ it 'raises TemplateRenderError' do
152
+ # This would require a fixture file with ERB syntax errors
153
+ # For now, test that the error class exists
154
+ expect(TemplateRenderer::TemplateRenderError).to be < TemplateRenderer::TemplateError
155
+ end
156
+ end
157
+ end
158
+
159
+ describe '#clear' do
160
+ it 'clears the cache' do
161
+ cache.clear
162
+ expect(cache.stats[:size]).to eq(0)
163
+ end
164
+ end
165
+
166
+ describe '#stats' do
167
+ it 'returns cache statistics hash' do
168
+ stats = cache.stats
169
+ expect(stats).to be_a(Hash)
170
+ expect(stats).to have_key(:size)
171
+ expect(stats).to have_key(:entries)
172
+ expect(stats).to have_key(:memory_estimate)
173
+ end
174
+
175
+ it 'estimates memory usage' do
176
+ stats = cache.stats
177
+ expect(stats[:memory_estimate]).to be >= 0
178
+ end
179
+ end
180
+
181
+ describe 'caching behavior' do
182
+ it 'caches compiled ERB objects' do
183
+ # This test would require actual fixture files
184
+ # Verifies that repeated renders use cache
185
+ expect(cache.instance_variable_get(:@cache)).to be_a(Hash)
186
+ end
187
+ end
188
+ end
189
+
190
+ RSpec.describe TemplateRenderer::PartialResolver do
191
+ let(:generator_class) do
192
+ Class.new do
193
+ def self.source_paths
194
+ [
195
+ '/path/to/templates',
196
+ '/another/path/templates'
197
+ ]
198
+ end
199
+ end
200
+ end
201
+
202
+ let(:resolver) { described_class.new(generator_class) }
203
+ let(:test_binding) { binding }
204
+
205
+ describe '#resolve' do
206
+ it 'attempts to resolve relative to caller first' do
207
+ # Mock file existence check
208
+ allow(File).to receive(:exist?).and_return(false)
209
+
210
+ expect do
211
+ resolver.resolve('test', test_binding)
212
+ end.to raise_error(TemplateRenderer::TemplateNotFoundError)
213
+ end
214
+ end
215
+
216
+ describe '#search_paths' do
217
+ it 'returns all paths that were searched' do
218
+ paths = resolver.search_paths('screenshot', test_binding)
219
+
220
+ expect(paths).to be_an(Array)
221
+ expect(paths).not_to be_empty
222
+ expect(paths.first).to include('screenshot.tt')
223
+ end
224
+
225
+ it 'includes relative path if caller file is available' do
226
+ paths = resolver.search_paths('screenshot', test_binding)
227
+ # Should include relative path attempt
228
+ expect(paths.length).to be >= 1
229
+ end
230
+
231
+ it 'includes all source_paths in search' do
232
+ paths = resolver.search_paths('screenshot', test_binding)
233
+
234
+ # Should search in all configured source paths
235
+ expect(paths.any? { |p| p.include?('/path/to/templates') }).to be true
236
+ end
237
+ end
238
+ end
239
+
240
+ RSpec.describe TemplateRenderer::TemplateError do
241
+ describe TemplateRenderer::TemplateNotFoundError do
242
+ it 'formats error message with searched paths' do
243
+ error = described_class.new(
244
+ 'Partial not found',
245
+ partial_name: 'test',
246
+ searched_paths: ['/path/1', '/path/2']
247
+ )
248
+
249
+ message = error.to_s
250
+ expect(message).to include("Partial 'test' not found")
251
+ expect(message).to include('/path/1')
252
+ expect(message).to include('/path/2')
253
+ expect(message).to include('Searched in:')
254
+ end
255
+ end
256
+
257
+ describe TemplateRenderer::TemplateRenderError do
258
+ it 'includes original error information' do
259
+ original = StandardError.new('syntax error')
260
+ original.set_backtrace(['line 1', 'line 2'])
261
+
262
+ error = described_class.new(
263
+ 'Render failed',
264
+ partial_name: 'test',
265
+ original_error: original
266
+ )
267
+
268
+ message = error.to_s
269
+ expect(message).to include('Error rendering partial')
270
+ expect(message).to include('syntax error')
271
+ expect(message).to include('Backtrace:')
272
+ end
273
+ end
274
+ end
275
+
276
+ # Integration test with Generator base class
277
+ RSpec.describe Generator do
278
+ it 'includes TemplateRenderer module' do
279
+ expect(described_class.ancestors).to include(TemplateRenderer)
280
+ end
281
+
282
+ it 'has partial method available' do
283
+ # Generator requires arguments, so we'll test the class directly
284
+ expect(described_class.instance_methods).to include(:partial)
285
+ end
286
+
287
+ it 'has template_renderer class method' do
288
+ expect(described_class).to respond_to(:template_renderer)
289
+ end
290
+
291
+ it 'has clear_template_cache class method' do
292
+ expect(described_class).to respond_to(:clear_template_cache)
293
+ end
294
+
295
+ it 'has template_cache_stats class method' do
296
+ expect(described_class).to respond_to(:template_cache_stats)
297
+ end
298
+ end
@@ -18,7 +18,7 @@ describe ScaffoldingCommands do
18
18
  context 'with a spec folder' do
19
19
  let(:new_path) { 'test_folder' }
20
20
 
21
- path = "#{FRAMEWORKS.last}_#{AUTOMATION_TYPES[2]}"
21
+ path = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
22
22
 
23
23
  before do
24
24
  Dir.chdir path
@@ -63,7 +63,7 @@ describe ScaffoldingCommands do
63
63
  context 'with a features folder' do
64
64
  let(:new_path) { 'test_folder' }
65
65
 
66
- path = "#{FRAMEWORKS.first}_#{AUTOMATION_TYPES.last}"
66
+ path = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::APPLITOOLS}"
67
67
 
68
68
  before do
69
69
  Dir.chdir path
@@ -19,7 +19,7 @@ describe UtilityCommands do
19
19
  context 'with a spec folder' do
20
20
  let(:new_path) { 'test_folder' }
21
21
 
22
- path = "#{FRAMEWORKS.last}_#{AUTOMATION_TYPES[2]}"
22
+ path = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
23
23
 
24
24
  before do
25
25
  Dir.chdir path
@@ -53,7 +53,7 @@ describe UtilityCommands do
53
53
  context 'with a features folder' do
54
54
  let(:new_path) { 'test_folder' }
55
55
 
56
- path = "#{FRAMEWORKS.first}_#{AUTOMATION_TYPES.last}"
56
+ path = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::APPLITOOLS}"
57
57
 
58
58
  before do
59
59
  Dir.chdir path
@@ -0,0 +1,325 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'spec_helper'
4
+ require 'timeout'
5
+ require 'open3'
6
+
7
+ # End-to-end integration tests that verify generated projects actually work
8
+ # These tests:
9
+ # 1. Generate a complete project
10
+ # 2. Install dependencies (bundle install)
11
+ # 3. Run the generated tests (rspec/cucumber)
12
+ # 4. Verify tests pass
13
+ #
14
+ # This ensures the template system produces working, executable test frameworks
15
+ describe 'End-to-End Project Generation and Execution' do
16
+ # Helper to run commands in a generated project directory
17
+ def run_in_project(project_name, command, timeout: 300)
18
+ result = {
19
+ success: false,
20
+ stdout: '',
21
+ stderr: '',
22
+ exit_code: nil
23
+ }
24
+
25
+ Dir.chdir(project_name) do
26
+ Bundler.with_unbundled_env do
27
+ Timeout.timeout(timeout) do
28
+ stdout, stderr, status = Open3.capture3(command)
29
+ result[:stdout] = stdout
30
+ result[:stderr] = stderr
31
+ result[:exit_code] = status.exitstatus
32
+ result[:success] = status.success?
33
+ end
34
+ end
35
+ end
36
+
37
+ result
38
+ rescue Timeout::Error
39
+ result[:stderr] = "Command timed out after #{timeout} seconds"
40
+ result[:exit_code] = -1
41
+ result
42
+ end
43
+
44
+ # Helper to install dependencies
45
+ def bundle_install(project_name)
46
+ puts "\nšŸ“¦ Installing dependencies for #{project_name}..."
47
+ result = run_in_project(project_name, 'bundle install --quiet', timeout: 180)
48
+
49
+ unless result[:success]
50
+ puts 'āŒ Bundle install failed:'
51
+ puts result[:stderr]
52
+ puts result[:stdout]
53
+ end
54
+
55
+ result[:success]
56
+ end
57
+
58
+ # Helper to run RSpec tests
59
+ def run_rspec(project_name)
60
+ puts "\n🧪 Running RSpec tests in #{project_name}..."
61
+ result = run_in_project(project_name, 'bundle exec rspec spec/ --format documentation', timeout: 120)
62
+
63
+ puts result[:stdout] if result[:stdout].length.positive?
64
+
65
+ unless result[:success]
66
+ puts 'āŒ RSpec tests failed:'
67
+ puts result[:stderr] if result[:stderr].length.positive?
68
+ end
69
+
70
+ result
71
+ end
72
+
73
+ # Helper to run Cucumber tests
74
+ def run_cucumber(project_name)
75
+ puts "\nšŸ„’ Running Cucumber tests in #{project_name}..."
76
+ result = run_in_project(project_name, 'bundle exec cucumber features/ --format pretty', timeout: 120)
77
+
78
+ puts result[:stdout] if result[:stdout].length.positive?
79
+
80
+ unless result[:success]
81
+ puts 'āŒ Cucumber tests failed:'
82
+ puts result[:stderr] if result[:stderr].length.positive?
83
+ end
84
+
85
+ result
86
+ end
87
+
88
+ # Helper to run Minitest tests
89
+ def run_minitest(project_name)
90
+ puts "\nRunning Minitest tests in #{project_name}..."
91
+ result = run_in_project(project_name, 'bundle exec ruby -Itest test/test_login_page.rb', timeout: 120)
92
+
93
+ puts result[:stdout] if result[:stdout].length.positive?
94
+
95
+ unless result[:success]
96
+ puts 'Minitest tests failed:'
97
+ puts result[:stderr] if result[:stderr].length.positive?
98
+ end
99
+
100
+ result
101
+ end
102
+
103
+ # Shared example for RSpec-based projects
104
+ shared_examples 'executable rspec project' do |project_name|
105
+ let(:project) { project_name }
106
+
107
+ it 'installs dependencies successfully' do
108
+ expect(bundle_install(project)).to be true
109
+ end
110
+
111
+ it 'runs generated RSpec tests successfully', :slow do
112
+ skip 'Bundle install failed' unless bundle_install(project)
113
+
114
+ result = run_rspec(project)
115
+
116
+ expect(result[:success]).to be(true),
117
+ "RSpec tests failed with exit code #{result[:exit_code]}.\n" \
118
+ "STDOUT: #{result[:stdout]}\n" \
119
+ "STDERR: #{result[:stderr]}"
120
+ end
121
+ end
122
+
123
+ # Shared example for Cucumber-based projects
124
+ shared_examples 'executable cucumber project' do |project_name|
125
+ let(:project) { project_name }
126
+
127
+ it 'installs dependencies successfully' do
128
+ expect(bundle_install(project)).to be true
129
+ end
130
+
131
+ it 'runs generated Cucumber tests successfully', :slow do
132
+ skip 'Bundle install failed' unless bundle_install(project)
133
+
134
+ result = run_cucumber(project)
135
+
136
+ expect(result[:success]).to be(true),
137
+ "Cucumber tests failed with exit code #{result[:exit_code]}.\n" \
138
+ "STDOUT: #{result[:stdout]}\n" \
139
+ "STDERR: #{result[:stderr]}"
140
+ end
141
+ end
142
+
143
+ # Shared example for Minitest-based projects
144
+ shared_examples 'executable minitest project' do |project_name|
145
+ let(:project) { project_name }
146
+
147
+ it 'installs dependencies successfully' do
148
+ expect(bundle_install(project)).to be true
149
+ end
150
+
151
+ it 'runs generated Minitest tests successfully', :slow do
152
+ skip 'Bundle install failed' unless bundle_install(project)
153
+
154
+ result = run_minitest(project)
155
+
156
+ expect(result[:success]).to be(true),
157
+ "Minitest tests failed with exit code #{result[:exit_code]}.\n" \
158
+ "STDOUT: #{result[:stdout]}\n" \
159
+ "STDERR: #{result[:stderr]}"
160
+ end
161
+ end
162
+
163
+ # Test Web Frameworks (these can run without external services)
164
+ context 'Web Automation Frameworks' do
165
+ describe 'Selenium + RSpec' do
166
+ include_examples 'executable rspec project', 'rspec_selenium'
167
+ end
168
+
169
+ describe 'Watir + RSpec' do
170
+ include_examples 'executable rspec project', 'rspec_watir'
171
+ end
172
+
173
+ describe 'Capybara + RSpec' do
174
+ include_examples 'executable rspec project', 'rspec_capybara'
175
+ end
176
+
177
+ describe 'Selenium + Cucumber' do
178
+ include_examples 'executable cucumber project', 'cucumber_selenium'
179
+ end
180
+
181
+ describe 'Watir + Cucumber' do
182
+ include_examples 'executable cucumber project', 'cucumber_watir'
183
+ end
184
+
185
+ describe 'Capybara + Cucumber' do
186
+ include_examples 'executable cucumber project', 'cucumber_capybara'
187
+ end
188
+
189
+ describe 'Axe + Cucumber' do
190
+ include_examples 'executable cucumber project', 'cucumber_axe'
191
+ end
192
+
193
+ describe 'Selenium + Minitest' do
194
+ include_examples 'executable minitest project', 'minitest_selenium'
195
+ end
196
+
197
+ describe 'Watir + Minitest' do
198
+ include_examples 'executable minitest project', 'minitest_watir'
199
+ end
200
+
201
+ describe 'Capybara + Minitest' do
202
+ include_examples 'executable minitest project', 'minitest_capybara'
203
+ end
204
+ end
205
+
206
+ # Mobile and Visual frameworks require external services, so we only verify structure
207
+ context 'Mobile Automation Frameworks (structure validation only)' do
208
+ describe 'iOS + RSpec' do
209
+ it 'generates valid project structure' do
210
+ expect(File).to exist('rspec_ios/helpers/spec_helper.rb')
211
+ expect(File).to exist('rspec_ios/Gemfile')
212
+ expect(File).to exist('rspec_ios/spec')
213
+ end
214
+
215
+ it 'has valid Ruby syntax in generated files' do
216
+ result = run_in_project('rspec_ios', 'ruby -c helpers/spec_helper.rb')
217
+ expect(result[:success]).to be true
218
+ end
219
+ end
220
+
221
+ describe 'Android + Cucumber' do
222
+ it 'generates valid project structure' do
223
+ expect(File).to exist('cucumber_android/features/support/env.rb')
224
+ expect(File).to exist('cucumber_android/Gemfile')
225
+ expect(File).to exist('cucumber_android/features')
226
+ end
227
+
228
+ it 'has valid Ruby syntax in generated files' do
229
+ result = run_in_project('cucumber_android', 'ruby -c features/support/env.rb')
230
+ expect(result[:success]).to be true
231
+ end
232
+ end
233
+
234
+ describe 'Cross-Platform + RSpec' do
235
+ it 'generates valid project structure' do
236
+ expect(File).to exist('rspec_cross_platform/helpers/appium_helper.rb')
237
+ expect(File).to exist('rspec_cross_platform/Gemfile')
238
+ end
239
+ end
240
+ end
241
+
242
+ context 'Minitest Mobile Frameworks (structure validation only)' do
243
+ describe 'Android + Minitest' do
244
+ it 'generates valid project structure' do
245
+ expect(File).to exist('minitest_android/helpers/test_helper.rb')
246
+ expect(File).to exist('minitest_android/Gemfile')
247
+ expect(File).to exist('minitest_android/test')
248
+ end
249
+
250
+ it 'has valid Ruby syntax in generated files' do
251
+ result = run_in_project('minitest_android', 'ruby -c helpers/test_helper.rb')
252
+ expect(result[:success]).to be true
253
+ end
254
+ end
255
+
256
+ describe 'iOS + Minitest' do
257
+ it 'generates valid project structure' do
258
+ expect(File).to exist('minitest_ios/helpers/test_helper.rb')
259
+ expect(File).to exist('minitest_ios/Gemfile')
260
+ expect(File).to exist('minitest_ios/test')
261
+ end
262
+
263
+ it 'has valid Ruby syntax in generated files' do
264
+ result = run_in_project('minitest_ios', 'ruby -c helpers/test_helper.rb')
265
+ expect(result[:success]).to be true
266
+ end
267
+ end
268
+ end
269
+
270
+ context 'Visual Testing Frameworks (structure validation only)' do
271
+ describe 'Applitools + RSpec' do
272
+ it 'generates valid project structure with visual helper' do
273
+ expect(File).to exist('rspec_applitools/helpers/visual_helper.rb')
274
+ expect(File).to exist('rspec_applitools/helpers/spec_helper.rb')
275
+ end
276
+
277
+ it 'includes Applitools dependencies' do
278
+ gemfile = File.read('rspec_applitools/Gemfile')
279
+ expect(gemfile).to include('eyes_selenium')
280
+ end
281
+
282
+ it 'has valid Ruby syntax in generated files' do
283
+ result = run_in_project('rspec_applitools', 'ruby -c helpers/visual_helper.rb')
284
+ expect(result[:success]).to be true
285
+ end
286
+ end
287
+ end
288
+
289
+ # Verify template system performance (caching working)
290
+ context 'Template System Performance' do
291
+ it 'benefits from template caching on second generation' do
292
+ require_relative '../../lib/generators/generator'
293
+
294
+ # Clear cache
295
+ Generator.clear_template_cache
296
+
297
+ # First generation (cache miss)
298
+ start_time = Time.now
299
+ settings1 = create_settings(framework: 'rspec', automation: 'selenium')
300
+ settings1[:name] = 'test_cache_1'
301
+ generate_framework(settings1)
302
+ first_duration = Time.now - start_time
303
+
304
+ # Second generation (cache hit)
305
+ start_time = Time.now
306
+ settings2 = create_settings(framework: 'rspec', automation: 'selenium')
307
+ settings2[:name] = 'test_cache_2'
308
+ generate_framework(settings2)
309
+ second_duration = Time.now - start_time
310
+
311
+ # Second generation should be faster (or similar if I/O dominates)
312
+ # At minimum, cache should not slow things down
313
+ expect(second_duration).to be <= (first_duration * 1.2)
314
+
315
+ # Cleanup
316
+ FileUtils.rm_rf('test_cache_1')
317
+ FileUtils.rm_rf('test_cache_2')
318
+
319
+ # Verify cache has entries
320
+ stats = Generator.template_cache_stats
321
+ expect(stats[:size]).to be > 0
322
+ puts "\nšŸ“Š Template cache: #{stats[:size]} entries, ~#{stats[:memory_estimate] / 1024}KB"
323
+ end
324
+ end
325
+ end