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.
- checksums.yaml +4 -4
- data/.github/workflows/integration.yml +4 -6
- data/.github/workflows/reek.yml +6 -5
- data/.github/workflows/release.yml +175 -0
- data/.github/workflows/rubocop.yml +7 -6
- data/.github/workflows/system_tests.yml +83 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +24 -0
- data/README.md +3 -1
- data/RELEASE.md +412 -0
- data/RELEASE_QUICK_GUIDE.md +77 -0
- data/bin/release +186 -0
- data/lib/adopter/adopt_menu.rb +150 -0
- data/lib/adopter/converters/base_converter.rb +85 -0
- data/lib/adopter/converters/identity_converter.rb +56 -0
- data/lib/adopter/migration_plan.rb +75 -0
- data/lib/adopter/migrator.rb +96 -0
- data/lib/adopter/plan_builder.rb +278 -0
- data/lib/adopter/project_analyzer.rb +256 -0
- data/lib/adopter/project_detector.rb +159 -0
- data/lib/commands/adopt_commands.rb +43 -0
- data/lib/generators/automation/templates/account.tt +9 -5
- data/lib/generators/automation/templates/appium_caps.tt +60 -6
- data/lib/generators/automation/templates/home.tt +4 -4
- data/lib/generators/automation/templates/login.tt +61 -4
- data/lib/generators/automation/templates/page.tt +13 -7
- data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -4
- data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -1
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -4
- data/lib/generators/automation/templates/partials/visit_method.tt +11 -1
- data/lib/generators/automation/templates/pdp.tt +1 -1
- data/lib/generators/cucumber/templates/env.tt +6 -4
- data/lib/generators/cucumber/templates/partials/capybara_env.tt +20 -0
- data/lib/generators/cucumber/templates/partials/capybara_world.tt +6 -0
- data/lib/generators/cucumber/templates/partials/mobile_steps.tt +2 -2
- data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -3
- data/lib/generators/cucumber/templates/steps.tt +2 -2
- data/lib/generators/cucumber/templates/world.tt +5 -3
- data/lib/generators/generator.rb +14 -2
- data/lib/generators/helper_generator.rb +16 -3
- data/lib/generators/infrastructure/github_generator.rb +6 -0
- data/lib/generators/infrastructure/templates/github.tt +11 -7
- data/lib/generators/infrastructure/templates/github_appium.tt +108 -0
- data/lib/generators/infrastructure/templates/gitlab.tt +5 -2
- data/lib/generators/invoke_generators.rb +1 -0
- data/lib/generators/menu_generator.rb +2 -0
- data/lib/generators/minitest/minitest_generator.rb +23 -0
- data/lib/generators/minitest/templates/test.tt +93 -0
- data/lib/generators/rspec/templates/spec.tt +12 -10
- data/lib/generators/template_renderer/partial_cache.rb +116 -0
- data/lib/generators/template_renderer/partial_resolver.rb +103 -0
- data/lib/generators/template_renderer/template_error.rb +50 -0
- data/lib/generators/template_renderer.rb +90 -0
- data/lib/generators/templates/common/config.tt +2 -2
- data/lib/generators/templates/common/gemfile.tt +15 -3
- data/lib/generators/templates/common/partials/web_config.tt +1 -1
- data/lib/generators/templates/common/read_me.tt +3 -1
- data/lib/generators/templates/helpers/allure_helper.tt +2 -2
- data/lib/generators/templates/helpers/browser_helper.tt +1 -0
- data/lib/generators/templates/helpers/capybara_helper.tt +28 -0
- data/lib/generators/templates/helpers/driver_helper.tt +1 -1
- data/lib/generators/templates/helpers/partials/allure_imports.tt +3 -1
- data/lib/generators/templates/helpers/partials/allure_requirements.tt +3 -1
- data/lib/generators/templates/helpers/partials/appium_driver.tt +46 -0
- data/lib/generators/templates/helpers/partials/axe_driver.tt +10 -0
- data/lib/generators/templates/helpers/partials/browserstack_config.tt +13 -0
- data/lib/generators/templates/helpers/partials/driver_and_options.tt +6 -114
- data/lib/generators/templates/helpers/partials/quit_driver.tt +3 -1
- data/lib/generators/templates/helpers/partials/screenshot.tt +3 -1
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +25 -0
- data/lib/generators/templates/helpers/spec_helper.tt +17 -4
- data/lib/generators/templates/helpers/test_helper.tt +26 -0
- data/lib/generators/templates/helpers/visual_spec_helper.tt +1 -1
- data/lib/ruby_raider.rb +5 -0
- data/lib/version +1 -1
- data/spec/adopter/adopt_menu_spec.rb +176 -0
- data/spec/adopter/converters/identity_converter_spec.rb +145 -0
- data/spec/adopter/migration_plan_spec.rb +113 -0
- data/spec/adopter/migrator_spec.rb +277 -0
- data/spec/adopter/plan_builder_spec.rb +298 -0
- data/spec/adopter/project_analyzer_spec.rb +337 -0
- data/spec/adopter/project_detector_spec.rb +295 -0
- data/spec/generators/fixtures/templates/test.tt +1 -0
- data/spec/generators/fixtures/templates/test_partial.tt +1 -0
- data/spec/generators/template_renderer_spec.rb +298 -0
- data/spec/integration/commands/scaffolding_commands_spec.rb +2 -2
- data/spec/integration/commands/utility_commands_spec.rb +2 -2
- data/spec/integration/end_to_end_spec.rb +325 -0
- data/spec/integration/generators/automation_generator_spec.rb +11 -11
- data/spec/integration/generators/common_generator_spec.rb +40 -40
- data/spec/integration/generators/cucumber_generator_spec.rb +7 -7
- data/spec/integration/generators/github_generator_spec.rb +8 -8
- data/spec/integration/generators/gitlab_generator_spec.rb +8 -8
- data/spec/integration/generators/helpers_generator_spec.rb +73 -35
- data/spec/integration/generators/minitest_generator_spec.rb +70 -0
- data/spec/integration/generators/rspec_generator_spec.rb +7 -7
- data/spec/integration/settings_helper.rb +1 -1
- data/spec/integration/spec_helper.rb +20 -2
- data/spec/system/capybara_spec.rb +42 -0
- data/spec/system/selenium_spec.rb +19 -17
- data/spec/system/support/system_test_helper.rb +35 -0
- data/spec/system/watir_spec.rb +19 -17
- metadata +46 -16
- data/.github/workflows/push_gem.yml +0 -37
- data/.github/workflows/selenium.yml +0 -22
- data/.github/workflows/watir.yml +0 -22
- data/lib/generators/automation/templates/partials/android_caps.tt +0 -17
- data/lib/generators/automation/templates/partials/cross_platform_caps.tt +0 -25
- data/lib/generators/automation/templates/partials/ios_caps.tt +0 -18
- data/lib/generators/automation/templates/partials/selenium_account.tt +0 -9
- data/lib/generators/automation/templates/partials/selenium_login.tt +0 -34
- data/lib/generators/automation/templates/partials/watir_account.tt +0 -7
- 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 = "#{
|
|
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 = "#{
|
|
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 = "#{
|
|
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 = "#{
|
|
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
|