ruby_raider 2.0.0 → 3.0.1
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 +246 -38
- data/lib/commands/utility_commands.rb +92 -3
- 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 +13 -3
- data/lib/generators/templates/helpers/capybara_helper.tt +6 -2
- 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 +9 -8
- 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 +96 -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 +61 -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/commands/scaffolding_commands_spec.rb +22 -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 +29 -9
- 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/scaffolding_e2e_spec.rb +775 -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 +142 -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
- data/spec/utilities/headless_config_spec.rb +89 -0
- data/spec/utilities/utilities_spec.rb +105 -0
- metadata +154 -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
data/lib/ruby_raider.rb
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
# :reek:FeatureEnvy { enabled: false }
|
|
11
|
-
# :reek:UtilityFunction { enabled: false }
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
# Lazy-load top-level command classes to reduce CLI startup time.
|
|
6
|
+
autoload :AdoptCommands, File.expand_path('commands/adopt_commands', __dir__)
|
|
7
|
+
autoload :ScaffoldingCommands, File.expand_path('commands/scaffolding_commands', __dir__)
|
|
8
|
+
autoload :UtilityCommands, File.expand_path('commands/utility_commands', __dir__)
|
|
9
|
+
|
|
12
10
|
module RubyRaider
|
|
11
|
+
# Lazy-load namespaced classes within the correct module scope.
|
|
12
|
+
autoload :Plugin, File.expand_path('plugin/plugin', __dir__)
|
|
13
|
+
autoload :PluginCommands, File.expand_path('commands/plugin_commands', __dir__)
|
|
14
|
+
autoload :LoadedCommands, File.expand_path('commands/loaded_commands', __dir__)
|
|
15
|
+
|
|
13
16
|
class Raider < Thor
|
|
14
17
|
no_tasks do
|
|
15
18
|
def self.plugin_commands?
|
|
16
|
-
File.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
@plugin_commands ||= File.read(
|
|
20
|
+
File.expand_path('commands/loaded_commands.rb', __dir__)
|
|
21
|
+
).include?('subcommand')
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
def current_version = File.read(File.expand_path('version', __dir__)).strip
|
|
@@ -24,12 +27,31 @@ module RubyRaider
|
|
|
24
27
|
desc 'new [PROJECT_NAME]', 'Creates a new framework based on settings picked'
|
|
25
28
|
option :parameters,
|
|
26
29
|
type: :hash, required: false, desc: 'Parameters to avoid using the menu', aliases: 'p'
|
|
30
|
+
option :skip_ci,
|
|
31
|
+
type: :boolean, required: false, desc: 'Skip CI/CD configuration generation'
|
|
32
|
+
option :skip_allure,
|
|
33
|
+
type: :boolean, required: false, desc: 'Skip Allure reporting setup'
|
|
34
|
+
option :skip_video,
|
|
35
|
+
type: :boolean, required: false, desc: 'Skip video recording setup'
|
|
36
|
+
option :reporter,
|
|
37
|
+
type: :string, required: false, desc: 'Reporter: allure, junit, json, both, all, none', aliases: '-r'
|
|
38
|
+
option :accessibility,
|
|
39
|
+
type: :boolean, required: false, desc: 'Add axe accessibility testing'
|
|
40
|
+
option :visual,
|
|
41
|
+
type: :boolean, required: false, desc: 'Add visual regression testing'
|
|
42
|
+
option :performance,
|
|
43
|
+
type: :boolean, required: false, desc: 'Add Lighthouse performance auditing'
|
|
44
|
+
option :ruby_version,
|
|
45
|
+
type: :string, required: false, desc: 'Ruby version for generated project (e.g. 3.4, 3.3)'
|
|
27
46
|
|
|
28
47
|
def new(project_name)
|
|
48
|
+
require_relative 'utilities/logo'
|
|
49
|
+
RubyRaider::Logo.display
|
|
29
50
|
params = options[:parameters]
|
|
30
51
|
if params
|
|
31
52
|
params[:name] = project_name
|
|
32
53
|
parsed_hash = params.transform_keys(&:to_sym)
|
|
54
|
+
merge_skip_flags(parsed_hash)
|
|
33
55
|
return InvokeGenerators.generate_framework(parsed_hash)
|
|
34
56
|
end
|
|
35
57
|
|
|
@@ -67,5 +89,18 @@ module RubyRaider
|
|
|
67
89
|
subcommand 'plugins', LoadedCommands
|
|
68
90
|
map 'ps' => 'plugins'
|
|
69
91
|
end
|
|
92
|
+
|
|
93
|
+
no_tasks do
|
|
94
|
+
def merge_skip_flags(params)
|
|
95
|
+
%i[skip_ci skip_allure skip_video].each do |flag|
|
|
96
|
+
params[flag] = true if options[flag]
|
|
97
|
+
end
|
|
98
|
+
params[:reporter] = options[:reporter] if options[:reporter]
|
|
99
|
+
params[:accessibility] = true if options[:accessibility]
|
|
100
|
+
params[:visual] = true if options[:visual]
|
|
101
|
+
params[:performance] = true if options[:performance]
|
|
102
|
+
params[:ruby_version] = options[:ruby_version] if options[:ruby_version]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
70
105
|
end
|
|
71
106
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'name_normalizer'
|
|
4
|
+
|
|
5
|
+
class CrudGenerator
|
|
6
|
+
ACTIONS = %w[list create detail edit].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(base_name, scaffolding_class, config_loader)
|
|
9
|
+
@base_name = NameNormalizer.normalize(base_name)
|
|
10
|
+
@scaffolding_class = scaffolding_class
|
|
11
|
+
@config_loader = config_loader
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate
|
|
15
|
+
generated = []
|
|
16
|
+
ACTIONS.each do |action|
|
|
17
|
+
name = "#{@base_name}_#{action}"
|
|
18
|
+
generate_page(name)
|
|
19
|
+
generate_test(name)
|
|
20
|
+
generated << name
|
|
21
|
+
end
|
|
22
|
+
generate_model
|
|
23
|
+
generated
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def planned_files
|
|
27
|
+
files = []
|
|
28
|
+
ACTIONS.each do |action|
|
|
29
|
+
name = "#{@base_name}_#{action}"
|
|
30
|
+
files << "page_objects/pages/#{name}.rb"
|
|
31
|
+
files << test_path(name)
|
|
32
|
+
end
|
|
33
|
+
files << "models/data/#{@base_name}.yml"
|
|
34
|
+
files
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def generate_page(name)
|
|
40
|
+
path = @config_loader.call('page')
|
|
41
|
+
@scaffolding_class.new([name, path]).generate_page
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def generate_test(name)
|
|
45
|
+
if Dir.exist?('features')
|
|
46
|
+
path = @config_loader.call('feature')
|
|
47
|
+
@scaffolding_class.new([name, path]).generate_feature
|
|
48
|
+
path = @config_loader.call('steps')
|
|
49
|
+
@scaffolding_class.new([name, path]).generate_steps
|
|
50
|
+
elsif Dir.exist?('test')
|
|
51
|
+
path = @config_loader.call('spec')
|
|
52
|
+
@scaffolding_class.new([name, path]).generate_spec
|
|
53
|
+
else
|
|
54
|
+
path = @config_loader.call('spec')
|
|
55
|
+
@scaffolding_class.new([name, path]).generate_spec
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def generate_model
|
|
60
|
+
model_path = "models/data/#{@base_name}.yml"
|
|
61
|
+
return if File.exist?(model_path)
|
|
62
|
+
|
|
63
|
+
FileUtils.mkdir_p(File.dirname(model_path))
|
|
64
|
+
File.write(model_path, model_content)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def model_content
|
|
68
|
+
<<~YAML
|
|
69
|
+
# Data model for #{@base_name}
|
|
70
|
+
# Used with ModelFactory for test data generation
|
|
71
|
+
default:
|
|
72
|
+
name: 'Test #{@base_name.capitalize}'
|
|
73
|
+
email: 'test@example.com'
|
|
74
|
+
|
|
75
|
+
valid:
|
|
76
|
+
name: 'Valid #{@base_name.capitalize}'
|
|
77
|
+
email: 'valid@example.com'
|
|
78
|
+
|
|
79
|
+
invalid:
|
|
80
|
+
name: ''
|
|
81
|
+
email: 'invalid'
|
|
82
|
+
YAML
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_path(name)
|
|
86
|
+
if Dir.exist?('features')
|
|
87
|
+
"features/#{name}.feature"
|
|
88
|
+
elsif Dir.exist?('test')
|
|
89
|
+
"test/test_#{name}.rb"
|
|
90
|
+
else
|
|
91
|
+
"spec/#{name}_page_spec.rb"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DryRunPresenter
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def preview(planned_files)
|
|
7
|
+
return [] if planned_files.empty?
|
|
8
|
+
|
|
9
|
+
puts '[dry-run] Would create:'
|
|
10
|
+
planned_files.each do |file|
|
|
11
|
+
puts " #{file}"
|
|
12
|
+
end
|
|
13
|
+
puts ''
|
|
14
|
+
planned_files
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NameNormalizer
|
|
4
|
+
SUFFIXES = %w[_page _spec _steps _helper _test _feature].freeze
|
|
5
|
+
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# Normalize raw user input to a clean base name
|
|
9
|
+
# 'LoginPage' -> 'login', 'login_page' -> 'login', 'admin/users' -> 'admin/users'
|
|
10
|
+
def normalize(input)
|
|
11
|
+
name = input.to_s.strip
|
|
12
|
+
name = camel_to_snake(name) if name.match?(/[A-Z]/)
|
|
13
|
+
strip_suffixes(name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Convert to class name: 'login' -> 'LoginPage', 'admin/users' -> 'Admin::UsersPage'
|
|
17
|
+
def to_class_name(input, suffix = '')
|
|
18
|
+
normalized = normalize(input)
|
|
19
|
+
parts = normalized.split('/')
|
|
20
|
+
parts.map { |part| part.split('_').map(&:capitalize).join }.join('::') + suffix
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Convert to page class: 'login' -> 'LoginPage'
|
|
24
|
+
def to_page_class(input)
|
|
25
|
+
to_class_name(input, 'Page')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Convert to file name: 'LoginPage' -> 'login', 'admin/users' -> 'admin/users'
|
|
29
|
+
def to_file_name(input)
|
|
30
|
+
normalize(input)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Check if the input contains a nested path
|
|
34
|
+
def nested?(input)
|
|
35
|
+
normalize(input).include?('/')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get the module path for nested names: 'admin/users' -> ['Admin']
|
|
39
|
+
def module_parts(input)
|
|
40
|
+
parts = normalize(input).split('/')
|
|
41
|
+
parts[0..-2].map { |p| p.split('_').map(&:capitalize).join }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get the leaf name: 'admin/users' -> 'users'
|
|
45
|
+
def leaf_name(input)
|
|
46
|
+
normalize(input).split('/').last
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def camel_to_snake(str)
|
|
50
|
+
str.gsub(/::/, '/')
|
|
51
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
52
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
53
|
+
.downcase
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def strip_suffixes(name)
|
|
57
|
+
result = name.dup
|
|
58
|
+
SUFFIXES.each do |suffix|
|
|
59
|
+
result = result.delete_suffix(suffix) if result.end_with?(suffix)
|
|
60
|
+
end
|
|
61
|
+
result
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ripper'
|
|
4
|
+
|
|
5
|
+
class PageIntrospector
|
|
6
|
+
SKIP_METHODS = %w[initialize url to_s inspect].freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :class_name, :methods
|
|
9
|
+
|
|
10
|
+
def initialize(file_path)
|
|
11
|
+
@source = File.read(file_path)
|
|
12
|
+
@class_name = extract_class_name
|
|
13
|
+
@methods = extract_public_methods
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def extract_class_name
|
|
19
|
+
match = @source.match(/class\s+(\w+)/)
|
|
20
|
+
match ? match[1] : 'UnknownPage'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def extract_public_methods
|
|
24
|
+
in_private = false
|
|
25
|
+
results = []
|
|
26
|
+
|
|
27
|
+
@source.each_line do |line|
|
|
28
|
+
stripped = line.strip
|
|
29
|
+
in_private = true if stripped.match?(/^\s*(private|protected)\s*$/)
|
|
30
|
+
next if in_private
|
|
31
|
+
|
|
32
|
+
match = stripped.match(/^\s*def\s+(\w+)(\(([^)]*)\))?/)
|
|
33
|
+
next unless match
|
|
34
|
+
|
|
35
|
+
method_name = match[1]
|
|
36
|
+
next if SKIP_METHODS.include?(method_name)
|
|
37
|
+
next if method_name.start_with?('_')
|
|
38
|
+
|
|
39
|
+
params = match[3]&.split(',')&.map(&:strip) || []
|
|
40
|
+
results << { name: method_name, params: }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
results
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module ScaffoldProjectDetector
|
|
6
|
+
AUTOMATION_GEMS = {
|
|
7
|
+
'capybara' => 'capybara',
|
|
8
|
+
'watir' => 'watir',
|
|
9
|
+
'selenium-webdriver' => 'selenium',
|
|
10
|
+
'appium_lib' => 'appium',
|
|
11
|
+
'eyes_selenium' => 'selenium',
|
|
12
|
+
'axe-core-rspec' => 'selenium',
|
|
13
|
+
'axe-core-cucumber' => 'selenium'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
FRAMEWORK_GEMS = {
|
|
17
|
+
'rspec' => 'rspec',
|
|
18
|
+
'cucumber' => 'cucumber',
|
|
19
|
+
'minitest' => 'minitest'
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
CONFIG_PATH = 'config/config.yml'
|
|
23
|
+
|
|
24
|
+
class Error < StandardError; end
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
def detect
|
|
29
|
+
gemfile = read_gemfile
|
|
30
|
+
{
|
|
31
|
+
automation: detect_automation(gemfile),
|
|
32
|
+
framework: detect_framework(gemfile),
|
|
33
|
+
has_spec: Dir.exist?('spec'),
|
|
34
|
+
has_features: Dir.exist?('features'),
|
|
35
|
+
has_test: Dir.exist?('test')
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def detect_automation(gemfile = read_gemfile)
|
|
40
|
+
AUTOMATION_GEMS.each do |gem_name, automation|
|
|
41
|
+
return automation if gemfile.include?("'#{gem_name}'") || gemfile.include?("\"#{gem_name}\"")
|
|
42
|
+
end
|
|
43
|
+
'selenium'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def detect_framework(gemfile = read_gemfile)
|
|
47
|
+
FRAMEWORK_GEMS.each do |gem_name, framework|
|
|
48
|
+
return framework if gemfile.include?("'#{gem_name}'") || gemfile.include?("\"#{gem_name}\"")
|
|
49
|
+
end
|
|
50
|
+
'rspec'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def read_gemfile
|
|
54
|
+
return '' unless File.exist?('Gemfile')
|
|
55
|
+
|
|
56
|
+
File.read('Gemfile')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def selenium?
|
|
60
|
+
detect_automation == 'selenium'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def capybara?
|
|
64
|
+
detect_automation == 'capybara'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def watir?
|
|
68
|
+
detect_automation == 'watir'
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Validates that the current directory looks like a Ruby Raider project.
|
|
72
|
+
# Returns an array of warning messages (empty = valid project).
|
|
73
|
+
def validate_project
|
|
74
|
+
warnings = []
|
|
75
|
+
warnings << 'Gemfile not found. Are you in a Ruby Raider project directory?' unless File.exist?('Gemfile')
|
|
76
|
+
unless File.exist?(CONFIG_PATH)
|
|
77
|
+
warnings << "#{CONFIG_PATH} not found. Scaffolded pages won't include URL navigation methods."
|
|
78
|
+
end
|
|
79
|
+
warnings
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Loads and parses config/config.yml. Returns a hash.
|
|
83
|
+
# Raises ScaffoldProjectDetector::Error on malformed YAML.
|
|
84
|
+
def config
|
|
85
|
+
return {} unless File.exist?(CONFIG_PATH)
|
|
86
|
+
|
|
87
|
+
YAML.safe_load(File.read(CONFIG_PATH), permitted_classes: [Symbol]) || {}
|
|
88
|
+
rescue Psych::SyntaxError => e
|
|
89
|
+
raise Error, "#{CONFIG_PATH} has invalid YAML syntax: #{e.message}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns the custom path for a scaffold type from config, or nil.
|
|
93
|
+
def config_path(type)
|
|
94
|
+
config["#{type}_path"]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tty-prompt'
|
|
4
|
+
require_relative 'project_detector'
|
|
5
|
+
|
|
6
|
+
class ScaffoldMenu
|
|
7
|
+
COMPONENTS = {
|
|
8
|
+
'Page object' => :page,
|
|
9
|
+
'Spec (RSpec)' => :spec,
|
|
10
|
+
'Feature (Cucumber)' => :feature,
|
|
11
|
+
'Steps (Cucumber)' => :steps,
|
|
12
|
+
'Helper' => :helper,
|
|
13
|
+
'Component' => :component,
|
|
14
|
+
'Model data' => :model
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@prompt = TTY::Prompt.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
names = ask_names
|
|
23
|
+
components = ask_components
|
|
24
|
+
uses = ask_relationships
|
|
25
|
+
preview_and_confirm(names, components, uses)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Programmatic entry point for raider_desktop
|
|
29
|
+
def self.build_options(names:, components:, uses: [])
|
|
30
|
+
{ names: Array(names), components: Array(components), uses: Array(uses) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def ask_names
|
|
36
|
+
input = @prompt.ask('Enter scaffold name(s), comma-separated:') do |q|
|
|
37
|
+
q.required true
|
|
38
|
+
end
|
|
39
|
+
input.split(',').map(&:strip).reject(&:empty?)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ask_components
|
|
43
|
+
project = ScaffoldProjectDetector.detect
|
|
44
|
+
defaults = default_components(project)
|
|
45
|
+
|
|
46
|
+
@prompt.multi_select('Select components to generate:', default: defaults) do |menu|
|
|
47
|
+
COMPONENTS.each do |label, value|
|
|
48
|
+
menu.choice label, value
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def ask_relationships
|
|
54
|
+
return [] unless @prompt.yes?('Add page dependencies (--uses)?', default: false)
|
|
55
|
+
|
|
56
|
+
pages = Dir.glob('page_objects/pages/*.rb').map { |f| File.basename(f, '.rb') }
|
|
57
|
+
return [] if pages.empty?
|
|
58
|
+
|
|
59
|
+
@prompt.multi_select('Select dependent pages:', pages)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def preview_and_confirm(names, components, uses)
|
|
63
|
+
files = planned_files(names, components)
|
|
64
|
+
|
|
65
|
+
@prompt.say("\nWill create:")
|
|
66
|
+
files.each { |f| @prompt.say(" #{f}") }
|
|
67
|
+
@prompt.say('')
|
|
68
|
+
|
|
69
|
+
return nil unless @prompt.yes?('Proceed?')
|
|
70
|
+
|
|
71
|
+
{ names:, components:, uses: }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def planned_files(names, components)
|
|
75
|
+
names.flat_map do |name|
|
|
76
|
+
components.map { |comp| file_for(name, comp) }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def file_for(name, component)
|
|
81
|
+
case component
|
|
82
|
+
when :page then "page_objects/pages/#{name}.rb"
|
|
83
|
+
when :spec then "spec/#{name}_page_spec.rb"
|
|
84
|
+
when :feature then "features/#{name}.feature"
|
|
85
|
+
when :steps then "features/step_definitions/#{name}_steps.rb"
|
|
86
|
+
when :helper then "helpers/#{name}_helper.rb"
|
|
87
|
+
when :component then "page_objects/components/#{name}.rb"
|
|
88
|
+
when :model then "models/data/#{name}.yml"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def default_components(project)
|
|
93
|
+
defaults = [:page]
|
|
94
|
+
if project[:has_features]
|
|
95
|
+
defaults += %i[feature steps]
|
|
96
|
+
elsif project[:has_test]
|
|
97
|
+
defaults << :spec
|
|
98
|
+
else
|
|
99
|
+
defaults << :spec
|
|
100
|
+
end
|
|
101
|
+
defaults.map { |d| COMPONENTS.key(d) }.compact
|
|
102
|
+
end
|
|
103
|
+
end
|