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.
- checksums.yaml +4 -4
- data/.github/workflows/e2e_tests.yml +58 -0
- data/.github/workflows/steep.yml +21 -0
- data/.gitignore +1 -1
- data/.reek.yml +46 -4
- data/.ruby-version +1 -1
- data/README.md +138 -77
- data/Steepfile +22 -0
- data/assets/ruby_raider_logo.svg +51 -0
- data/lib/adopter/adopt_menu.rb +11 -15
- data/lib/adopter/converters/base_converter.rb +1 -2
- data/lib/adopter/converters/identity_converter.rb +3 -6
- data/lib/adopter/migration_plan.rb +0 -1
- data/lib/adopter/plan_builder.rb +2 -5
- data/lib/adopter/project_analyzer.rb +1 -5
- data/lib/adopter/project_detector.rb +3 -5
- data/lib/commands/adopt_commands.rb +0 -1
- data/lib/commands/plugin_commands.rb +0 -2
- data/lib/commands/scaffolding_commands.rb +220 -37
- data/lib/commands/utility_commands.rb +82 -2
- data/lib/generators/automation/automation_generator.rb +0 -7
- data/lib/generators/automation/templates/partials/element.tt +1 -1
- data/lib/generators/automation/templates/partials/initialize_selector.tt +0 -7
- data/lib/generators/automation/templates/partials/url_methods.tt +0 -1
- data/lib/generators/common_generator.rb +12 -0
- data/lib/generators/cucumber/cucumber_generator.rb +36 -0
- data/lib/generators/cucumber/templates/accessibility_feature.tt +5 -0
- data/lib/generators/cucumber/templates/accessibility_steps.tt +21 -0
- data/lib/generators/cucumber/templates/cucumber.tt +8 -1
- data/lib/generators/cucumber/templates/feature.tt +0 -4
- data/lib/generators/cucumber/templates/partials/appium_env.tt +5 -0
- data/lib/generators/cucumber/templates/partials/capybara_env.tt +19 -1
- data/lib/generators/cucumber/templates/partials/driver_world.tt +1 -4
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +22 -35
- data/lib/generators/cucumber/templates/partials/watir_env.tt +20 -1
- data/lib/generators/cucumber/templates/partials/web_steps.tt +6 -12
- data/lib/generators/cucumber/templates/performance_feature.tt +5 -0
- data/lib/generators/cucumber/templates/performance_steps.tt +17 -0
- data/lib/generators/cucumber/templates/visual_feature.tt +5 -0
- data/lib/generators/cucumber/templates/visual_steps.tt +19 -0
- data/lib/generators/generator.rb +38 -7
- data/lib/generators/helper_generator.rb +24 -7
- data/lib/generators/infrastructure/templates/github.tt +1 -1
- data/lib/generators/infrastructure/templates/github_appium.tt +2 -2
- data/lib/generators/infrastructure/templates/gitlab.tt +1 -1
- data/lib/generators/invoke_generators.rb +42 -9
- data/lib/generators/menu_generator.rb +120 -11
- data/lib/generators/minitest/minitest_generator.rb +16 -4
- data/lib/generators/minitest/templates/accessibility_test.tt +26 -0
- data/lib/generators/minitest/templates/performance_test.tt +18 -0
- data/lib/generators/minitest/templates/test.tt +5 -34
- data/lib/generators/minitest/templates/visual_test.tt +23 -0
- data/lib/generators/rspec/rspec_generator.rb +16 -4
- data/lib/generators/rspec/templates/accessibility_spec.tt +25 -0
- data/lib/generators/rspec/templates/performance_spec.tt +18 -0
- data/lib/generators/rspec/templates/spec.tt +5 -35
- data/lib/generators/rspec/templates/visual_spec.tt +20 -0
- data/lib/generators/template_renderer/partial_cache.rb +11 -1
- data/lib/generators/template_renderer/partial_resolver.rb +17 -10
- data/lib/generators/template_renderer.rb +17 -1
- data/lib/generators/templates/common/gemfile.tt +21 -6
- data/lib/generators/templates/common/git_ignore.tt +6 -1
- data/lib/generators/templates/common/partials/mobile_config.tt +5 -1
- data/lib/generators/templates/common/partials/web_config.tt +16 -7
- data/lib/generators/templates/common/rakefile.tt +36 -0
- data/lib/generators/templates/common/read_me.tt +41 -91
- data/lib/generators/templates/common/rspec.tt +3 -0
- data/lib/generators/templates/common/ruby_version.tt +1 -0
- data/lib/generators/templates/helpers/allure_helper.tt +11 -0
- data/lib/generators/templates/helpers/browser_helper.tt +12 -2
- data/lib/generators/templates/helpers/capybara_helper.tt +5 -1
- data/lib/generators/templates/helpers/debug_helper.tt +190 -0
- data/lib/generators/templates/helpers/driver_helper.tt +2 -10
- data/lib/generators/templates/helpers/partials/appium_driver.tt +0 -2
- data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +7 -0
- data/lib/generators/templates/helpers/partials/debug_start.tt +7 -0
- data/lib/generators/templates/helpers/partials/driver_and_options.tt +1 -3
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +8 -7
- data/lib/generators/templates/helpers/partials/video_start.tt +9 -0
- data/lib/generators/templates/helpers/partials/video_stop.tt +4 -0
- data/lib/generators/templates/helpers/performance_helper.tt +57 -0
- data/lib/generators/templates/helpers/spec_helper.tt +57 -8
- data/lib/generators/templates/helpers/test_helper.tt +69 -1
- data/lib/generators/templates/helpers/video_helper.tt +270 -0
- data/lib/generators/templates/helpers/visual_helper.tt +39 -46
- data/lib/llm/client.rb +79 -0
- data/lib/llm/config.rb +57 -0
- data/lib/llm/prompts.rb +84 -0
- data/lib/llm/provider.rb +27 -0
- data/lib/llm/providers/anthropic_provider.rb +43 -0
- data/lib/llm/providers/ollama_provider.rb +56 -0
- data/lib/llm/providers/openai_provider.rb +42 -0
- data/lib/llm/response_parser.rb +67 -0
- data/lib/plugin/plugin.rb +22 -20
- data/lib/plugin/plugin_exposer.rb +16 -38
- data/lib/ruby_raider.rb +47 -12
- data/lib/scaffolding/crud_generator.rb +94 -0
- data/lib/scaffolding/dry_run_presenter.rb +16 -0
- data/lib/scaffolding/name_normalizer.rb +63 -0
- data/lib/scaffolding/page_introspector.rb +45 -0
- data/lib/scaffolding/project_detector.rb +72 -0
- data/lib/scaffolding/scaffold_menu.rb +103 -0
- data/lib/scaffolding/scaffolding.rb +158 -11
- data/lib/scaffolding/templates/component.tt +30 -0
- data/lib/scaffolding/templates/feature.tt +4 -4
- data/lib/scaffolding/templates/helper.tt +15 -1
- data/lib/scaffolding/templates/page_from_url.tt +75 -0
- data/lib/scaffolding/templates/page_object.tt +50 -1
- data/lib/scaffolding/templates/spec.tt +33 -2
- data/lib/scaffolding/templates/spec_from_page.tt +31 -0
- data/lib/scaffolding/templates/spec_from_url.tt +46 -0
- data/lib/scaffolding/templates/steps.tt +17 -5
- data/lib/scaffolding/url_analyzer.rb +179 -0
- data/lib/utilities/desktop_downloader.rb +177 -0
- data/lib/utilities/logo.rb +83 -0
- data/lib/utilities/utilities.rb +53 -20
- data/lib/version +1 -1
- data/ruby_raider.gemspec +1 -0
- data/sig/adopter/adopt_menu.rbs +25 -0
- data/sig/adopter/converters/base_converter.rbs +23 -0
- data/sig/adopter/converters/identity_converter.rbs +16 -0
- data/sig/adopter/migration_plan.rbs +34 -0
- data/sig/adopter/migrator.rbs +21 -0
- data/sig/adopter/plan_builder.rbs +38 -0
- data/sig/adopter/project_analyzer.rbs +39 -0
- data/sig/adopter/project_detector.rbs +26 -0
- data/sig/commands/adopt_commands.rbs +8 -0
- data/sig/commands/loaded_commands.rbs +5 -0
- data/sig/commands/plugin_commands.rbs +9 -0
- data/sig/commands/scaffolding_commands.rbs +28 -0
- data/sig/commands/utility_commands.rbs +21 -0
- data/sig/generators/automation/automation_generator.rbs +20 -0
- data/sig/generators/common_generator.rbs +12 -0
- data/sig/generators/cucumber/cucumber_generator.rbs +16 -0
- data/sig/generators/generator.rbs +40 -0
- data/sig/generators/helper_generator.rbs +18 -0
- data/sig/generators/infrastructure/github_generator.rbs +5 -0
- data/sig/generators/infrastructure/gitlab_generator.rbs +4 -0
- data/sig/generators/invoke_generators.rbs +10 -0
- data/sig/generators/menu_generator.rbs +29 -0
- data/sig/generators/minitest/minitest_generator.rbs +8 -0
- data/sig/generators/rspec/rspec_generator.rbs +8 -0
- data/sig/generators/template_renderer/partial_cache.rbs +20 -0
- data/sig/generators/template_renderer/partial_resolver.rbs +20 -0
- data/sig/generators/template_renderer/template_error.rbs +19 -0
- data/sig/generators/template_renderer.rbs +10 -0
- data/sig/llm/client.rbs +15 -0
- data/sig/llm/config.rbs +20 -0
- data/sig/llm/prompts.rbs +8 -0
- data/sig/llm/provider.rbs +12 -0
- data/sig/llm/providers/anthropic_provider.rbs +16 -0
- data/sig/llm/providers/ollama_provider.rbs +18 -0
- data/sig/llm/providers/openai_provider.rbs +16 -0
- data/sig/llm/response_parser.rbs +13 -0
- data/sig/plugin/plugin.rbs +24 -0
- data/sig/plugin/plugin_exposer.rbs +20 -0
- data/sig/ruby_raider.rbs +15 -0
- data/sig/scaffolding/crud_generator.rbs +16 -0
- data/sig/scaffolding/dry_run_presenter.rbs +4 -0
- data/sig/scaffolding/name_normalizer.rbs +17 -0
- data/sig/scaffolding/page_introspector.rbs +14 -0
- data/sig/scaffolding/project_detector.rbs +14 -0
- data/sig/scaffolding/scaffold_menu.rbs +18 -0
- data/sig/scaffolding/scaffolding.rbs +55 -0
- data/sig/scaffolding/url_analyzer.rbs +28 -0
- data/sig/utilities/desktop_downloader.rbs +23 -0
- data/sig/utilities/logger.rbs +13 -0
- data/sig/utilities/logo.rbs +16 -0
- data/sig/utilities/utilities.rbs +30 -0
- data/sig/vendor/thor.rbs +34 -0
- data/sig/vendor/tty_prompt.rbs +15 -0
- data/spec/adopter/adopt_menu_spec.rb +12 -12
- data/spec/adopter/migration_plan_spec.rb +1 -1
- data/spec/adopter/migrator_spec.rb +2 -2
- data/spec/adopter/project_detector_spec.rb +1 -1
- data/spec/commands/raider_commands_spec.rb +129 -0
- data/spec/generators/generator_spec.rb +23 -0
- data/spec/integration/commands/scaffolding_commands_spec.rb +1 -1
- data/spec/integration/commands/utility_commands_spec.rb +23 -3
- data/spec/integration/content/ci_content_spec.rb +119 -0
- data/spec/integration/content/common_content_spec.rb +288 -0
- data/spec/integration/content/config_content_spec.rb +175 -0
- data/spec/integration/content/content_helper.rb +32 -0
- data/spec/integration/content/gemfile_content_spec.rb +209 -0
- data/spec/integration/content/helper_content_spec.rb +485 -0
- data/spec/integration/content/page_content_spec.rb +259 -0
- data/spec/integration/content/reporter_content_spec.rb +236 -0
- data/spec/integration/content/skip_flags_content_spec.rb +206 -0
- data/spec/integration/content/syntax_validation_spec.rb +30 -0
- data/spec/integration/content/test_content_spec.rb +266 -0
- data/spec/integration/end_to_end_features_spec.rb +690 -0
- data/spec/integration/end_to_end_spec.rb +52 -16
- data/spec/integration/generators/automation_generator_spec.rb +0 -12
- data/spec/integration/generators/axe_addon_spec.rb +150 -0
- data/spec/integration/generators/common_generator_spec.rb +12 -13
- data/spec/integration/generators/config_features_spec.rb +155 -0
- data/spec/integration/generators/debug_helper_spec.rb +68 -0
- data/spec/integration/generators/helpers_generator_spec.rb +0 -12
- data/spec/integration/generators/lighthouse_addon_spec.rb +132 -0
- data/spec/integration/generators/minitest_generator_spec.rb +0 -6
- data/spec/integration/generators/reporter_spec.rb +159 -0
- data/spec/integration/generators/skip_flags_spec.rb +134 -0
- data/spec/integration/generators/visual_addon_spec.rb +148 -0
- data/spec/integration/settings_helper.rb +0 -3
- data/spec/integration/spec_helper.rb +30 -13
- data/spec/llm/client_spec.rb +79 -0
- data/spec/llm/config_spec.rb +92 -0
- data/spec/llm/prompts_spec.rb +49 -0
- data/spec/llm/response_parser_spec.rb +92 -0
- data/spec/menus/adopter_adopt_menu_spec.rb +97 -0
- data/spec/menus/menu_generator_spec.rb +263 -0
- data/spec/scaffolding/name_normalizer_spec.rb +113 -0
- data/spec/scaffolding/page_introspector_spec.rb +82 -0
- data/spec/scaffolding/scaffold_project_detector_spec.rb +104 -0
- data/spec/scaffolding/scaffolding_features_spec.rb +311 -0
- data/spec/scaffolding/url_analyzer_spec.rb +110 -0
- data/spec/system/adopt_matrix_spec.rb +537 -0
- data/spec/system/adopt_spec.rb +225 -0
- data/spec/system/support/system_test_helper.rb +0 -2
- data/spec/utilities/desktop_downloader_spec.rb +92 -0
- metadata +150 -5
- data/lib/generators/automation/templates/visual_options.tt +0 -16
- data/lib/generators/templates/helpers/partials/axe_driver.tt +0 -10
- data/lib/generators/templates/helpers/visual_spec_helper.tt +0 -35
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/scaffolding/name_normalizer'
|
|
4
|
+
|
|
5
|
+
RSpec.describe NameNormalizer do
|
|
6
|
+
describe '.normalize' do
|
|
7
|
+
it 'strips _page suffix' do
|
|
8
|
+
expect(described_class.normalize('login_page')).to eq('login')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'strips _spec suffix' do
|
|
12
|
+
expect(described_class.normalize('login_spec')).to eq('login')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'strips _steps suffix' do
|
|
16
|
+
expect(described_class.normalize('login_steps')).to eq('login')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'strips _helper suffix' do
|
|
20
|
+
expect(described_class.normalize('login_helper')).to eq('login')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'converts CamelCase to snake_case' do
|
|
24
|
+
expect(described_class.normalize('LoginPage')).to eq('login')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'handles CamelCase without suffix' do
|
|
28
|
+
expect(described_class.normalize('Dashboard')).to eq('dashboard')
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'preserves nested paths' do
|
|
32
|
+
expect(described_class.normalize('admin/users')).to eq('admin/users')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'handles CamelCase nested paths' do
|
|
36
|
+
expect(described_class.normalize('Admin::UsersPage')).to eq('admin/users')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'leaves clean names unchanged' do
|
|
40
|
+
expect(described_class.normalize('login')).to eq('login')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe '.to_class_name' do
|
|
45
|
+
it 'converts snake_case to CamelCase' do
|
|
46
|
+
expect(described_class.to_class_name('login')).to eq('Login')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'handles underscored names' do
|
|
50
|
+
expect(described_class.to_class_name('user_profile')).to eq('UserProfile')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'strips suffixes before converting' do
|
|
54
|
+
expect(described_class.to_class_name('login_page')).to eq('Login')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'handles nested names with modules' do
|
|
58
|
+
expect(described_class.to_class_name('admin/users')).to eq('Admin::Users')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'appends optional suffix' do
|
|
62
|
+
expect(described_class.to_class_name('login', 'Page')).to eq('LoginPage')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe '.to_page_class' do
|
|
67
|
+
it 'converts to page class name' do
|
|
68
|
+
expect(described_class.to_page_class('login')).to eq('LoginPage')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'does not double the Page suffix' do
|
|
72
|
+
expect(described_class.to_page_class('login_page')).to eq('LoginPage')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'handles CamelCase input' do
|
|
76
|
+
expect(described_class.to_page_class('LoginPage')).to eq('LoginPage')
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '.nested?' do
|
|
81
|
+
it 'returns true for nested paths' do
|
|
82
|
+
expect(described_class.nested?('admin/users')).to be true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'returns false for simple names' do
|
|
86
|
+
expect(described_class.nested?('login')).to be false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '.module_parts' do
|
|
91
|
+
it 'returns module path for nested names' do
|
|
92
|
+
expect(described_class.module_parts('admin/users')).to eq(['Admin'])
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'returns empty for simple names' do
|
|
96
|
+
expect(described_class.module_parts('login')).to eq([])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'handles deep nesting' do
|
|
100
|
+
expect(described_class.module_parts('admin/settings/users')).to eq(%w[Admin Settings])
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe '.leaf_name' do
|
|
105
|
+
it 'returns last segment of nested path' do
|
|
106
|
+
expect(described_class.leaf_name('admin/users')).to eq('users')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'returns name itself for simple names' do
|
|
110
|
+
expect(described_class.leaf_name('login')).to eq('login')
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require_relative '../../lib/scaffolding/page_introspector'
|
|
5
|
+
|
|
6
|
+
RSpec.describe PageIntrospector do
|
|
7
|
+
subject(:introspector) { described_class.new(page_file) }
|
|
8
|
+
|
|
9
|
+
let(:tmp_dir) { 'tmp_introspector_test' }
|
|
10
|
+
let(:page_file) { "#{tmp_dir}/login_page.rb" }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
FileUtils.mkdir_p(tmp_dir)
|
|
14
|
+
File.write(page_file, <<~RUBY)
|
|
15
|
+
class LoginPage < Page
|
|
16
|
+
def url(_page)
|
|
17
|
+
'/login'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def login(username, password)
|
|
21
|
+
username_field.send_keys username
|
|
22
|
+
password_field.send_keys password
|
|
23
|
+
login_button.click
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def welcome_message
|
|
27
|
+
driver.find_element(css: '.welcome').text
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def username_field
|
|
33
|
+
driver.find_element(id: 'username')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def password_field
|
|
37
|
+
driver.find_element(id: 'password')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def login_button
|
|
41
|
+
driver.find_element(id: 'login-btn')
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
RUBY
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
after do
|
|
48
|
+
FileUtils.rm_rf(tmp_dir)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'extracts class name' do
|
|
52
|
+
expect(introspector.class_name).to eq('LoginPage')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'extracts public methods' do
|
|
56
|
+
method_names = introspector.methods.map { |m| m[:name] }
|
|
57
|
+
expect(method_names).to include('login')
|
|
58
|
+
expect(method_names).to include('welcome_message')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'skips url method' do
|
|
62
|
+
method_names = introspector.methods.map { |m| m[:name] }
|
|
63
|
+
expect(method_names).not_to include('url')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'skips private methods' do
|
|
67
|
+
method_names = introspector.methods.map { |m| m[:name] }
|
|
68
|
+
expect(method_names).not_to include('username_field')
|
|
69
|
+
expect(method_names).not_to include('password_field')
|
|
70
|
+
expect(method_names).not_to include('login_button')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'extracts method parameters' do
|
|
74
|
+
login_method = introspector.methods.find { |m| m[:name] == 'login' }
|
|
75
|
+
expect(login_method[:params]).to eq(%w[username password])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'handles methods with no parameters' do
|
|
79
|
+
welcome = introspector.methods.find { |m| m[:name] == 'welcome_message' }
|
|
80
|
+
expect(welcome[:params]).to eq([])
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require_relative '../../lib/scaffolding/project_detector'
|
|
5
|
+
|
|
6
|
+
RSpec.describe ScaffoldProjectDetector do
|
|
7
|
+
let(:tmp_dir) { 'tmp_project_detector_test' }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
FileUtils.mkdir_p(tmp_dir)
|
|
11
|
+
Dir.chdir(tmp_dir)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
after do
|
|
15
|
+
Dir.chdir('..')
|
|
16
|
+
FileUtils.rm_rf(tmp_dir)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context 'with selenium project' do
|
|
20
|
+
before do
|
|
21
|
+
File.write('Gemfile', "gem 'rspec'\ngem 'selenium-webdriver'\n")
|
|
22
|
+
FileUtils.mkdir_p('spec')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'detects selenium automation' do
|
|
26
|
+
expect(described_class.detect_automation).to eq('selenium')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'detects rspec framework' do
|
|
30
|
+
expect(described_class.detect_framework).to eq('rspec')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'reports selenium?' do
|
|
34
|
+
expect(described_class.selenium?).to be true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'reports not capybara?' do
|
|
38
|
+
expect(described_class.capybara?).to be false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
context 'with capybara project' do
|
|
43
|
+
before do
|
|
44
|
+
File.write('Gemfile', "gem 'capybara'\ngem 'cucumber'\n")
|
|
45
|
+
FileUtils.mkdir_p('features')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'detects capybara automation' do
|
|
49
|
+
expect(described_class.detect_automation).to eq('capybara')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'detects cucumber framework' do
|
|
53
|
+
expect(described_class.detect_framework).to eq('cucumber')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'reports capybara?' do
|
|
57
|
+
expect(described_class.capybara?).to be true
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context 'with watir project' do
|
|
62
|
+
before do
|
|
63
|
+
File.write('Gemfile', "gem 'watir'\ngem 'minitest'\n")
|
|
64
|
+
FileUtils.mkdir_p('test')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'detects watir automation' do
|
|
68
|
+
expect(described_class.detect_automation).to eq('watir')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'detects minitest framework' do
|
|
72
|
+
expect(described_class.detect_framework).to eq('minitest')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'reports watir?' do
|
|
76
|
+
expect(described_class.watir?).to be true
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context 'with no Gemfile' do
|
|
81
|
+
it 'defaults to selenium' do
|
|
82
|
+
expect(described_class.detect_automation).to eq('selenium')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'defaults to rspec' do
|
|
86
|
+
expect(described_class.detect_framework).to eq('rspec')
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe '.detect' do
|
|
91
|
+
before do
|
|
92
|
+
File.write('Gemfile', "gem 'selenium-webdriver'\ngem 'rspec'\n")
|
|
93
|
+
FileUtils.mkdir_p('spec')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'returns full detection hash' do
|
|
97
|
+
result = described_class.detect
|
|
98
|
+
expect(result[:automation]).to eq('selenium')
|
|
99
|
+
expect(result[:framework]).to eq('rspec')
|
|
100
|
+
expect(result[:has_spec]).to be true
|
|
101
|
+
expect(result[:has_features]).to be false
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require_relative '../../lib/commands/scaffolding_commands'
|
|
6
|
+
require_relative '../../lib/scaffolding/scaffolding'
|
|
7
|
+
|
|
8
|
+
RSpec.describe 'Scaffolding features' do
|
|
9
|
+
let(:scaffold) { ScaffoldingCommands }
|
|
10
|
+
let(:tmp_dir) { 'tmp_scaffold_features_test' }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
FileUtils.mkdir_p("#{tmp_dir}/page_objects/pages")
|
|
14
|
+
FileUtils.mkdir_p("#{tmp_dir}/page_objects/abstract")
|
|
15
|
+
FileUtils.mkdir_p("#{tmp_dir}/page_objects/components")
|
|
16
|
+
FileUtils.mkdir_p("#{tmp_dir}/spec")
|
|
17
|
+
FileUtils.mkdir_p("#{tmp_dir}/helpers")
|
|
18
|
+
FileUtils.mkdir_p("#{tmp_dir}/config")
|
|
19
|
+
FileUtils.mkdir_p("#{tmp_dir}/models/data")
|
|
20
|
+
|
|
21
|
+
File.write("#{tmp_dir}/Gemfile", "gem 'rspec'\ngem 'selenium-webdriver'\n")
|
|
22
|
+
File.write("#{tmp_dir}/config/config.yml", "browser: chrome\nurl: http://localhost:3000\n")
|
|
23
|
+
File.write("#{tmp_dir}/page_objects/abstract/page.rb", "class Page; end\n")
|
|
24
|
+
File.write("#{tmp_dir}/page_objects/abstract/component.rb", "class Component; end\n")
|
|
25
|
+
|
|
26
|
+
Dir.chdir(tmp_dir)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
after do
|
|
30
|
+
Dir.chdir('..')
|
|
31
|
+
FileUtils.rm_rf(tmp_dir)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# --- Feature 7: Better name handling ---
|
|
35
|
+
|
|
36
|
+
describe 'name normalization' do
|
|
37
|
+
it 'strips _page suffix from page names' do
|
|
38
|
+
scaffold.new.invoke(:page, nil, %w[login_page])
|
|
39
|
+
expect(Pathname.new('page_objects/pages/login.rb')).to be_file
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'handles CamelCase input' do
|
|
43
|
+
scaffold.new.invoke(:page, nil, %w[UserProfile])
|
|
44
|
+
expect(Pathname.new('page_objects/pages/user_profile.rb')).to be_file
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'handles CamelCase with Page suffix' do
|
|
48
|
+
scaffold.new.invoke(:page, nil, %w[LoginPage])
|
|
49
|
+
expect(Pathname.new('page_objects/pages/login.rb')).to be_file
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# --- Feature 6: Dry run ---
|
|
54
|
+
|
|
55
|
+
describe 'dry run' do
|
|
56
|
+
it 'does not create files with --dry-run' do
|
|
57
|
+
expect do
|
|
58
|
+
scaffold.new.invoke(:page, nil, %w[checkout --dry-run])
|
|
59
|
+
end.to output(/checkout/).to_stdout
|
|
60
|
+
|
|
61
|
+
expect(Pathname.new('page_objects/pages/checkout.rb')).not_to be_file
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'shows planned file path' do
|
|
65
|
+
expect do
|
|
66
|
+
scaffold.new.invoke(:spec, nil, %w[checkout --dry-run])
|
|
67
|
+
end.to output(/checkout/).to_stdout
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# --- Feature 8: Component scaffolding ---
|
|
72
|
+
|
|
73
|
+
describe 'component scaffolding' do
|
|
74
|
+
it 'creates a component file' do
|
|
75
|
+
scaffold.new.invoke(:component, nil, %w[sidebar])
|
|
76
|
+
expect(Pathname.new('page_objects/components/sidebar.rb')).to be_file
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'component inherits from Component' do
|
|
80
|
+
scaffold.new.invoke(:component, nil, %w[sidebar])
|
|
81
|
+
content = File.read('page_objects/components/sidebar.rb')
|
|
82
|
+
expect(content).to include('class Sidebar < Component')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'component has frozen_string_literal' do
|
|
86
|
+
scaffold.new.invoke(:component, nil, %w[sidebar])
|
|
87
|
+
content = File.read('page_objects/components/sidebar.rb')
|
|
88
|
+
expect(content).to include('# frozen_string_literal: true')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'deletes a component' do
|
|
92
|
+
scaffold.new.invoke(:component, nil, %w[sidebar])
|
|
93
|
+
scaffold.new.invoke(:component, nil, %w[sidebar --delete])
|
|
94
|
+
expect(Pathname.new('page_objects/components/sidebar.rb')).not_to be_file
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# --- Feature 10: Config-aware templates ---
|
|
99
|
+
|
|
100
|
+
describe 'config-aware templates' do
|
|
101
|
+
it 'page object includes url method when config has url' do
|
|
102
|
+
scaffold.new.invoke(:page, nil, %w[checkout])
|
|
103
|
+
content = File.read('page_objects/pages/checkout.rb')
|
|
104
|
+
expect(content).to include('def url(_page)')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'page object includes selenium-specific comments' do
|
|
108
|
+
scaffold.new.invoke(:page, nil, %w[checkout])
|
|
109
|
+
content = File.read('page_objects/pages/checkout.rb')
|
|
110
|
+
expect(content).to include('driver.find_element')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'spec includes let for page with driver' do
|
|
114
|
+
scaffold.new.invoke(:spec, nil, %w[checkout])
|
|
115
|
+
content = File.read('spec/checkout_page_spec.rb')
|
|
116
|
+
expect(content).to include('CheckoutPage.new(driver)')
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# --- Feature 9: Relationships ---
|
|
121
|
+
|
|
122
|
+
describe 'relationships (--uses)' do
|
|
123
|
+
before do
|
|
124
|
+
File.write('page_objects/pages/login.rb', "class LoginPage < Page; end\n")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it 'adds require_relative for dependent pages' do
|
|
128
|
+
scaffold.new.invoke(:page, nil, %w[dashboard --uses login])
|
|
129
|
+
content = File.read('page_objects/pages/dashboard.rb')
|
|
130
|
+
expect(content).to include("require_relative 'login'")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'adds uses to spec as let declarations' do
|
|
134
|
+
scaffold.new.invoke(:spec, nil, %w[dashboard --uses login])
|
|
135
|
+
content = File.read('spec/dashboard_page_spec.rb')
|
|
136
|
+
expect(content).to include('LoginPage')
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# --- Feature 1: Batch scaffold ---
|
|
141
|
+
|
|
142
|
+
describe 'batch scaffolding' do
|
|
143
|
+
it 'generates multiple scaffolds' do
|
|
144
|
+
scaffold.new.invoke(:scaffold, nil, %w[login dashboard])
|
|
145
|
+
expect(Pathname.new('page_objects/pages/login.rb')).to be_file
|
|
146
|
+
expect(Pathname.new('page_objects/pages/dashboard.rb')).to be_file
|
|
147
|
+
expect(Pathname.new('spec/login_page_spec.rb')).to be_file
|
|
148
|
+
expect(Pathname.new('spec/dashboard_page_spec.rb')).to be_file
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# --- Feature 1: Selective --with ---
|
|
153
|
+
|
|
154
|
+
describe 'selective scaffolding (--with)' do
|
|
155
|
+
it 'generates only selected components' do
|
|
156
|
+
scaffold.new.invoke(:scaffold, nil, %w[checkout --with page])
|
|
157
|
+
expect(Pathname.new('page_objects/pages/checkout.rb')).to be_file
|
|
158
|
+
expect(Pathname.new('spec/checkout_page_spec.rb')).not_to be_file
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'generates page and helper with --with' do
|
|
162
|
+
scaffold.new.invoke(:scaffold, nil, %w[checkout --with page helper])
|
|
163
|
+
expect(Pathname.new('page_objects/pages/checkout.rb')).to be_file
|
|
164
|
+
expect(Pathname.new('helpers/checkout_helper.rb')).to be_file
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'generates model data with --with model' do
|
|
168
|
+
scaffold.new.invoke(:scaffold, nil, %w[user --with model])
|
|
169
|
+
expect(Pathname.new('models/data/user.yml')).to be_file
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# --- Feature 5: CRUD ---
|
|
174
|
+
|
|
175
|
+
describe 'CRUD scaffolding' do
|
|
176
|
+
it 'generates CRUD pages' do
|
|
177
|
+
scaffold.new.invoke(:scaffold, nil, %w[user --crud])
|
|
178
|
+
%w[user_list user_create user_detail user_edit].each do |page|
|
|
179
|
+
expect(Pathname.new("page_objects/pages/#{page}.rb")).to be_file
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'generates CRUD specs' do
|
|
184
|
+
scaffold.new.invoke(:scaffold, nil, %w[user --crud])
|
|
185
|
+
%w[user_list user_create user_detail user_edit].each do |page|
|
|
186
|
+
expect(Pathname.new("spec/#{page}_page_spec.rb")).to be_file
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'generates model data file' do
|
|
191
|
+
scaffold.new.invoke(:scaffold, nil, %w[user --crud])
|
|
192
|
+
expect(Pathname.new('models/data/user.yml')).to be_file
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'model data has expected structure' do
|
|
196
|
+
scaffold.new.invoke(:scaffold, nil, %w[user --crud])
|
|
197
|
+
content = File.read('models/data/user.yml')
|
|
198
|
+
expect(content).to include('default:')
|
|
199
|
+
expect(content).to include('valid:')
|
|
200
|
+
expect(content).to include('invalid:')
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# --- Feature: Destroy command ---
|
|
205
|
+
|
|
206
|
+
describe 'destroy command' do
|
|
207
|
+
it 'removes page and spec files' do
|
|
208
|
+
scaffold.new.invoke(:scaffold, nil, %w[login])
|
|
209
|
+
expect(Pathname.new('page_objects/pages/login.rb')).to be_file
|
|
210
|
+
expect(Pathname.new('spec/login_page_spec.rb')).to be_file
|
|
211
|
+
|
|
212
|
+
scaffold.new.invoke(:destroy, nil, %w[login])
|
|
213
|
+
expect(Pathname.new('page_objects/pages/login.rb')).not_to be_file
|
|
214
|
+
expect(Pathname.new('spec/login_page_spec.rb')).not_to be_file
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'removes only specified components with --with' do # rubocop:disable RSpec/MultipleExpectations
|
|
218
|
+
scaffold.new.invoke(:scaffold, nil, %w[checkout --with page spec helper])
|
|
219
|
+
expect(Pathname.new('page_objects/pages/checkout.rb')).to be_file
|
|
220
|
+
expect(Pathname.new('spec/checkout_page_spec.rb')).to be_file
|
|
221
|
+
expect(Pathname.new('helpers/checkout_helper.rb')).to be_file
|
|
222
|
+
|
|
223
|
+
scaffold.new.invoke(:destroy, nil, %w[checkout --with page helper])
|
|
224
|
+
expect(Pathname.new('page_objects/pages/checkout.rb')).not_to be_file
|
|
225
|
+
expect(Pathname.new('helpers/checkout_helper.rb')).not_to be_file
|
|
226
|
+
expect(Pathname.new('spec/checkout_page_spec.rb')).to be_file
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'destroys multiple names at once' do
|
|
230
|
+
scaffold.new.invoke(:scaffold, nil, %w[login dashboard])
|
|
231
|
+
scaffold.new.invoke(:destroy, nil, %w[login dashboard])
|
|
232
|
+
expect(Pathname.new('page_objects/pages/login.rb')).not_to be_file
|
|
233
|
+
expect(Pathname.new('page_objects/pages/dashboard.rb')).not_to be_file
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# --- Feature: Template overrides ---
|
|
238
|
+
|
|
239
|
+
describe 'template overrides' do
|
|
240
|
+
before do
|
|
241
|
+
FileUtils.mkdir_p('.ruby_raider/templates')
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
after do
|
|
245
|
+
FileUtils.rm_rf('.ruby_raider')
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'uses override template when present' do
|
|
249
|
+
File.write('.ruby_raider/templates/page_object.tt', <<~ERB)
|
|
250
|
+
# Custom page for <%= class_name %>
|
|
251
|
+
class <%= page_class_name %> < Page
|
|
252
|
+
# CUSTOM OVERRIDE
|
|
253
|
+
end
|
|
254
|
+
ERB
|
|
255
|
+
|
|
256
|
+
Scaffolding.new(%w[widget]).generate_page
|
|
257
|
+
content = File.read('page_objects/pages/widget.rb')
|
|
258
|
+
expect(content).to include('CUSTOM OVERRIDE')
|
|
259
|
+
expect(content).to include('class WidgetPage < Page')
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it 'falls back to default template when no override exists' do
|
|
263
|
+
Scaffolding.new(%w[gadget]).generate_page
|
|
264
|
+
content = File.read('page_objects/pages/gadget.rb')
|
|
265
|
+
expect(content).not_to include('CUSTOM OVERRIDE')
|
|
266
|
+
expect(content).to include('class GadgetPage < Page')
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# --- Feature 3: Spec from page ---
|
|
271
|
+
|
|
272
|
+
describe 'spec from page (--from)' do
|
|
273
|
+
before do
|
|
274
|
+
File.write('page_objects/pages/login.rb', <<~RUBY)
|
|
275
|
+
class LoginPage < Page
|
|
276
|
+
def login(username, password)
|
|
277
|
+
username_field.send_keys username
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def welcome_message
|
|
281
|
+
driver.find_element(css: '.welcome').text
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
private
|
|
285
|
+
|
|
286
|
+
def username_field
|
|
287
|
+
driver.find_element(id: 'username')
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
RUBY
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
it 'generates spec with method stubs' do
|
|
294
|
+
scaffold.new.invoke(:spec, nil, %w[login --from page_objects/pages/login.rb])
|
|
295
|
+
expect(Pathname.new('spec/login_spec.rb')).to be_file
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it 'spec includes describe blocks for each public method' do
|
|
299
|
+
scaffold.new.invoke(:spec, nil, %w[login --from page_objects/pages/login.rb])
|
|
300
|
+
content = File.read('spec/login_spec.rb')
|
|
301
|
+
expect(content).to include("describe '#login'")
|
|
302
|
+
expect(content).to include("describe '#welcome_message'")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
it 'spec does not include private methods' do
|
|
306
|
+
scaffold.new.invoke(:spec, nil, %w[login --from page_objects/pages/login.rb])
|
|
307
|
+
content = File.read('spec/login_spec.rb')
|
|
308
|
+
expect(content).not_to include('username_field')
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|