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
|
@@ -5,7 +5,7 @@ require 'rspec'
|
|
|
5
5
|
require_relative '../../lib/generators/invoke_generators'
|
|
6
6
|
require_relative 'settings_helper'
|
|
7
7
|
|
|
8
|
-
AUTOMATION_TYPES = %w[android ios selenium watir cross_platform
|
|
8
|
+
AUTOMATION_TYPES = %w[android ios selenium watir cross_platform capybara].freeze
|
|
9
9
|
FRAMEWORKS = %w[cucumber rspec minitest].freeze
|
|
10
10
|
CI_PLATFORMS = [nil, 'github', 'gitlab'].freeze
|
|
11
11
|
|
|
@@ -22,36 +22,53 @@ module AutomationIndex
|
|
|
22
22
|
SELENIUM = AUTOMATION_TYPES[2]
|
|
23
23
|
WATIR = AUTOMATION_TYPES[3]
|
|
24
24
|
CROSS_PLATFORM = AUTOMATION_TYPES[4]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
CAPYBARA = AUTOMATION_TYPES[5]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Lazy project generation — only generates a project the first time it's needed.
|
|
29
|
+
# Replaces the old before(:all) that generated all 54 combinations upfront.
|
|
30
|
+
module LazyProjectGenerator
|
|
31
|
+
GENERATED = Set.new
|
|
32
|
+
MUTEX = Mutex.new
|
|
33
|
+
|
|
34
|
+
def ensure_project(framework:, automation:, ci_platform: nil)
|
|
35
|
+
settings = create_settings(framework:, automation:, ci_platform:)
|
|
36
|
+
project_name = settings[:name]
|
|
37
|
+
|
|
38
|
+
MUTEX.synchronize do
|
|
39
|
+
unless GENERATED.include?(project_name)
|
|
40
|
+
generate_framework(settings)
|
|
41
|
+
GENERATED.add(project_name)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
project_name
|
|
46
|
+
end
|
|
28
47
|
end
|
|
29
48
|
|
|
30
49
|
RSpec.configure do |config|
|
|
31
50
|
config.include(InvokeGenerators)
|
|
32
51
|
config.include(SettingsHelper)
|
|
52
|
+
config.include(LazyProjectGenerator)
|
|
33
53
|
|
|
34
54
|
# rubocop:disable RSpec/BeforeAfterAll
|
|
35
55
|
config.before(:all) do
|
|
56
|
+
# Generate all projects upfront (preserves existing behavior).
|
|
57
|
+
# Individual specs can also call ensure_project() for on-demand generation.
|
|
36
58
|
FRAMEWORKS.each do |framework|
|
|
37
59
|
AUTOMATION_TYPES.each do |automation|
|
|
38
60
|
CI_PLATFORMS.each do |ci_platform|
|
|
39
|
-
|
|
40
|
-
generate_framework(settings)
|
|
61
|
+
ensure_project(framework:, automation:, ci_platform:)
|
|
41
62
|
end
|
|
42
63
|
end
|
|
43
64
|
end
|
|
44
65
|
end
|
|
45
66
|
|
|
46
67
|
config.after(:all) do
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
CI_PLATFORMS.each do |ci_platform|
|
|
50
|
-
settings = create_settings(framework:, automation:, ci_platform:)
|
|
51
|
-
FileUtils.rm_rf(settings[:name])
|
|
52
|
-
end
|
|
53
|
-
end
|
|
68
|
+
LazyProjectGenerator::GENERATED.each do |project_name|
|
|
69
|
+
FileUtils.rm_rf(project_name)
|
|
54
70
|
end
|
|
71
|
+
LazyProjectGenerator::GENERATED.clear
|
|
55
72
|
end
|
|
56
73
|
# rubocop:enable RSpec/BeforeAfterAll
|
|
57
74
|
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/llm/client'
|
|
4
|
+
require_relative '../../lib/llm/provider'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Llm::Client do
|
|
7
|
+
let(:mock_provider) { instance_double(Llm::Provider, available?: true) }
|
|
8
|
+
let(:mock_config) { instance_double(Llm::Config, configured?: true, provider_name: 'ollama', model: 'llama3.2') }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
allow(Llm::Config).to receive(:new).and_return(mock_config)
|
|
12
|
+
allow(mock_config).to receive(:build_provider).and_return(mock_provider)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe '.complete' do
|
|
16
|
+
it 'delegates to the provider' do
|
|
17
|
+
allow(mock_provider).to receive(:complete).and_return('AI response')
|
|
18
|
+
result = described_class.complete('test prompt')
|
|
19
|
+
expect(result).to eq('AI response')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'returns nil when not configured' do
|
|
23
|
+
allow(mock_config).to receive(:configured?).and_return(false)
|
|
24
|
+
expect(described_class.complete('test prompt')).to be_nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'returns nil when provider is not available' do
|
|
28
|
+
allow(mock_provider).to receive(:available?).and_return(false)
|
|
29
|
+
expect(described_class.complete('test prompt')).to be_nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'retries on failure and returns nil after max retries' do
|
|
33
|
+
allow(mock_provider).to receive(:complete).and_raise(StandardError, 'network error')
|
|
34
|
+
allow(described_class).to receive(:sleep) # skip actual sleep
|
|
35
|
+
expect(described_class.complete('test prompt')).to be_nil
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'passes system_prompt to provider' do
|
|
39
|
+
allow(mock_provider).to receive(:complete).and_return('ok')
|
|
40
|
+
result = described_class.complete('prompt', system_prompt: 'system')
|
|
41
|
+
expect(result).to eq('ok')
|
|
42
|
+
expect(mock_provider).to have_received(:complete).with('prompt', system_prompt: 'system')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe '.available?' do
|
|
47
|
+
it 'returns true when provider is configured and available' do
|
|
48
|
+
expect(described_class).to be_available
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'returns false when not configured' do
|
|
52
|
+
allow(mock_config).to receive(:configured?).and_return(false)
|
|
53
|
+
expect(described_class).not_to be_available
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'returns false when provider is not available' do
|
|
57
|
+
allow(mock_provider).to receive(:available?).and_return(false)
|
|
58
|
+
expect(described_class).not_to be_available
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe '.status' do
|
|
63
|
+
it 'returns configured status with details' do
|
|
64
|
+
allow(mock_provider).to receive(:available?).and_return(true)
|
|
65
|
+
status = described_class.status
|
|
66
|
+
expect(status[:configured]).to be true
|
|
67
|
+
expect(status[:provider]).to eq('ollama')
|
|
68
|
+
expect(status[:model]).to eq('llama3.2')
|
|
69
|
+
expect(status[:available]).to be true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'returns unconfigured status' do
|
|
73
|
+
allow(mock_config).to receive(:configured?).and_return(false)
|
|
74
|
+
status = described_class.status
|
|
75
|
+
expect(status[:configured]).to be false
|
|
76
|
+
expect(status[:provider]).to be_nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/llm/config'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Llm::Config do
|
|
6
|
+
before do
|
|
7
|
+
allow(ENV).to receive(:fetch).and_call_original
|
|
8
|
+
%w[RUBY_RAIDER_LLM_PROVIDER RUBY_RAIDER_LLM_API_KEY RUBY_RAIDER_LLM_MODEL RUBY_RAIDER_LLM_URL].each do |key|
|
|
9
|
+
allow(ENV).to receive(:fetch).with(key, nil).and_return(nil)
|
|
10
|
+
end
|
|
11
|
+
allow(File).to receive(:exist?).with('config/config.yml').and_return(false)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '#configured?' do
|
|
15
|
+
it 'returns false when no provider is set' do
|
|
16
|
+
expect(described_class.new).not_to be_configured
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'returns false for unknown provider' do
|
|
20
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('unknown')
|
|
21
|
+
expect(described_class.new).not_to be_configured
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'returns true for ollama without api key' do
|
|
25
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('ollama')
|
|
26
|
+
expect(described_class.new).to be_configured
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'returns false for openai without api key' do
|
|
30
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('openai')
|
|
31
|
+
expect(described_class.new).not_to be_configured
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'returns true for openai with api key' do
|
|
35
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('openai')
|
|
36
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_API_KEY', nil).and_return('sk-test')
|
|
37
|
+
expect(described_class.new).to be_configured
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'returns true for anthropic with api key' do
|
|
41
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('anthropic')
|
|
42
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_API_KEY', nil).and_return('sk-ant-test')
|
|
43
|
+
expect(described_class.new).to be_configured
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#build_provider' do
|
|
48
|
+
it 'returns nil when not configured' do
|
|
49
|
+
expect(described_class.new.build_provider).to be_nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'builds OpenaiProvider for openai' do
|
|
53
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('openai')
|
|
54
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_API_KEY', nil).and_return('sk-test')
|
|
55
|
+
provider = described_class.new.build_provider
|
|
56
|
+
expect(provider).to be_a(Llm::Providers::OpenaiProvider)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'builds AnthropicProvider for anthropic' do
|
|
60
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('anthropic')
|
|
61
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_API_KEY', nil).and_return('sk-ant-test')
|
|
62
|
+
provider = described_class.new.build_provider
|
|
63
|
+
expect(provider).to be_a(Llm::Providers::AnthropicProvider)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'builds OllamaProvider for ollama' do
|
|
67
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('ollama')
|
|
68
|
+
provider = described_class.new.build_provider
|
|
69
|
+
expect(provider).to be_a(Llm::Providers::OllamaProvider)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe 'config file fallback' do
|
|
74
|
+
it 'reads from config/config.yml when env vars are not set' do
|
|
75
|
+
allow(File).to receive(:exist?).with('config/config.yml').and_return(true)
|
|
76
|
+
allow(YAML).to receive(:load_file).with('config/config.yml').and_return(
|
|
77
|
+
'llm_provider' => 'ollama',
|
|
78
|
+
'llm_model' => 'codellama'
|
|
79
|
+
)
|
|
80
|
+
config = described_class.new
|
|
81
|
+
expect(config.provider_name).to eq('ollama')
|
|
82
|
+
expect(config.model).to eq('codellama')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'env vars take precedence over config file' do
|
|
86
|
+
allow(ENV).to receive(:fetch).with('RUBY_RAIDER_LLM_PROVIDER', nil).and_return('anthropic')
|
|
87
|
+
allow(File).to receive(:exist?).with('config/config.yml').and_return(true)
|
|
88
|
+
allow(YAML).to receive(:load_file).with('config/config.yml').and_return('llm_provider' => 'ollama')
|
|
89
|
+
expect(described_class.new.provider_name).to eq('anthropic')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/llm/prompts'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Llm::Prompts do
|
|
6
|
+
describe '.analyze_page' do
|
|
7
|
+
it 'includes URL and HTML in prompt' do
|
|
8
|
+
prompt = described_class.analyze_page('<html>test</html>', 'https://example.com')
|
|
9
|
+
expect(prompt).to include('https://example.com')
|
|
10
|
+
expect(prompt).to include('<html>test</html>')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'truncates HTML at 8000 characters' do
|
|
14
|
+
long_html = 'x' * 10_000
|
|
15
|
+
prompt = described_class.analyze_page(long_html, 'https://example.com')
|
|
16
|
+
expect(prompt.length).to be < 10_000
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'requests JSON output' do
|
|
20
|
+
prompt = described_class.analyze_page('<html></html>', 'https://example.com')
|
|
21
|
+
expect(prompt).to include('JSON')
|
|
22
|
+
expect(prompt).to include('elements')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '.generate_test_scenarios' do
|
|
27
|
+
let(:methods) { [{ name: 'login', params: %w[user pass] }, { name: 'logout', params: [] }] }
|
|
28
|
+
|
|
29
|
+
it 'includes class and method info' do
|
|
30
|
+
prompt = described_class.generate_test_scenarios('LoginPage', methods, 'selenium', 'rspec')
|
|
31
|
+
expect(prompt).to include('LoginPage')
|
|
32
|
+
expect(prompt).to include('login(user, pass)')
|
|
33
|
+
expect(prompt).to include('logout')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'includes automation and framework context' do
|
|
37
|
+
prompt = described_class.generate_test_scenarios('LoginPage', methods, 'selenium', 'rspec')
|
|
38
|
+
expect(prompt).to include('selenium')
|
|
39
|
+
expect(prompt).to include('rspec')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '.system_prompt' do
|
|
44
|
+
it 'returns a non-empty string' do
|
|
45
|
+
expect(described_class.system_prompt).to be_a(String)
|
|
46
|
+
expect(described_class.system_prompt).not_to be_empty
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../lib/llm/response_parser'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Llm::ResponseParser do
|
|
6
|
+
describe '.parse_json' do
|
|
7
|
+
it 'parses raw JSON' do
|
|
8
|
+
result = described_class.parse_json('{"key": "value"}')
|
|
9
|
+
expect(result).to eq(key: 'value')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'parses JSON from markdown code block' do
|
|
13
|
+
response = "```json\n{\"key\": \"value\"}\n```"
|
|
14
|
+
result = described_class.parse_json(response)
|
|
15
|
+
expect(result).to eq(key: 'value')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'extracts JSON from surrounding text' do
|
|
19
|
+
response = "Here is the result:\n{\"key\": \"value\"}\nDone."
|
|
20
|
+
result = described_class.parse_json(response)
|
|
21
|
+
expect(result).to eq(key: 'value')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'returns nil for nil input' do
|
|
25
|
+
expect(described_class.parse_json(nil)).to be_nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'returns nil for empty input' do
|
|
29
|
+
expect(described_class.parse_json('')).to be_nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'returns nil for invalid JSON' do
|
|
33
|
+
expect(described_class.parse_json('not json at all')).to be_nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'returns nil for JSON arrays (not hashes)' do
|
|
37
|
+
expect(described_class.parse_json('[1, 2, 3]')).to be_nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '.extract_elements' do
|
|
42
|
+
let(:valid_response) do
|
|
43
|
+
{
|
|
44
|
+
elements: [
|
|
45
|
+
{ name: 'email_field', type: 'input', locator: { type: 'id', value: 'email' }, purpose: 'Email input' },
|
|
46
|
+
{ name: 'submit_btn', type: 'button', locator: { type: 'id', value: 'submit' }, text: 'Submit' }
|
|
47
|
+
]
|
|
48
|
+
}.to_json
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'extracts and normalizes elements' do
|
|
52
|
+
elements = described_class.extract_elements(valid_response)
|
|
53
|
+
expect(elements.length).to eq(2)
|
|
54
|
+
expect(elements.first[:name]).to eq('email_field')
|
|
55
|
+
expect(elements.first[:locator][:type]).to eq(:id)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'skips elements without required fields' do
|
|
59
|
+
response = { elements: [{ name: 'incomplete' }] }.to_json
|
|
60
|
+
elements = described_class.extract_elements(response)
|
|
61
|
+
expect(elements).to be_empty
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'returns nil for non-element response' do
|
|
65
|
+
expect(described_class.extract_elements('{"other": "data"}')).to be_nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'returns nil for nil response' do
|
|
69
|
+
expect(described_class.extract_elements(nil)).to be_nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe '.extract_scenarios' do
|
|
74
|
+
let(:valid_response) do
|
|
75
|
+
{
|
|
76
|
+
scenarios: [
|
|
77
|
+
{ method: 'login', description: 'logs in with valid credentials', assertion_hint: 'expect redirect' }
|
|
78
|
+
]
|
|
79
|
+
}.to_json
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'extracts scenarios' do
|
|
83
|
+
scenarios = described_class.extract_scenarios(valid_response)
|
|
84
|
+
expect(scenarios.length).to eq(1)
|
|
85
|
+
expect(scenarios.first[:method]).to eq('login')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'returns nil for non-scenario response' do
|
|
89
|
+
expect(described_class.extract_scenarios('{"other": "data"}')).to be_nil
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'rspec'
|
|
5
|
+
require_relative '../../lib/adopter/adopt_menu'
|
|
6
|
+
|
|
7
|
+
RSpec.describe Adopter::AdoptMenu do # rubocop:disable RSpec/FilePath
|
|
8
|
+
let(:prompt) { instance_double(TTY::Prompt) }
|
|
9
|
+
let(:source_dir) { 'tmp_adopt_interactive_source' }
|
|
10
|
+
let(:output_dir) { 'tmp_adopt_interactive_output' }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
allow(TTY::Prompt).to receive(:new).and_return(prompt)
|
|
14
|
+
allow(prompt).to receive(:say)
|
|
15
|
+
allow(prompt).to receive(:warn)
|
|
16
|
+
allow(prompt).to receive(:error)
|
|
17
|
+
|
|
18
|
+
FileUtils.mkdir_p(source_dir)
|
|
19
|
+
File.write("#{source_dir}/Gemfile", "gem 'rspec'\ngem 'selenium-webdriver'\n")
|
|
20
|
+
FileUtils.mkdir_p("#{source_dir}/spec")
|
|
21
|
+
File.write("#{source_dir}/spec/login_spec.rb", "describe 'Login' do\n it('works') { expect(true).to be true }\nend\n")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
after do
|
|
25
|
+
FileUtils.rm_rf(source_dir)
|
|
26
|
+
FileUtils.rm_rf(output_dir)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe '#run' do
|
|
30
|
+
before do
|
|
31
|
+
allow(prompt).to receive(:ask).and_return(source_dir, output_dir)
|
|
32
|
+
allow(prompt).to receive(:select)
|
|
33
|
+
.with('Select target automation framework:', anything, anything).and_return('Selenium')
|
|
34
|
+
allow(prompt).to receive(:select)
|
|
35
|
+
.with('Select target test framework:', anything, anything).and_return('Rspec')
|
|
36
|
+
allow(prompt).to receive(:select)
|
|
37
|
+
.with('Configure CI/CD?', anything, anything).and_return(nil)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'shows detection preview' do
|
|
41
|
+
described_class.new.run
|
|
42
|
+
|
|
43
|
+
expect(prompt).to have_received(:say).with(match(/Detected project settings/))
|
|
44
|
+
expect(prompt).to have_received(:say).with(match(/Automation: selenium/))
|
|
45
|
+
expect(prompt).to have_received(:say).with(match(/Framework:.*rspec/))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'executes adoption and shows results' do
|
|
49
|
+
described_class.new.run
|
|
50
|
+
|
|
51
|
+
expect(prompt).to have_received(:say).with(match(/Adoption complete/))
|
|
52
|
+
expect(prompt).to have_received(:say).with(match(/Pages converted/))
|
|
53
|
+
expect(prompt).to have_received(:say).with(match(/Tests converted/))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'generates the output project' do
|
|
57
|
+
described_class.new.run
|
|
58
|
+
|
|
59
|
+
expect(File).to exist("#{output_dir}/Gemfile")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'shows output path and bundle install instruction' do
|
|
63
|
+
described_class.new.run
|
|
64
|
+
|
|
65
|
+
expect(prompt).to have_received(:say).with(match(/Output: #{output_dir}/))
|
|
66
|
+
expect(prompt).to have_received(:say).with(match(/bundle install/))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe '#run with mobile source project' do
|
|
71
|
+
before do
|
|
72
|
+
File.write("#{source_dir}/Gemfile", "gem 'rspec'\ngem 'appium_lib'\n")
|
|
73
|
+
allow(prompt).to receive(:ask).and_return(source_dir, output_dir)
|
|
74
|
+
allow(prompt).to receive(:select).and_return('Selenium', 'Rspec', nil)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'shows MobileProjectError message' do
|
|
78
|
+
described_class.new.run
|
|
79
|
+
|
|
80
|
+
expect(prompt).to have_received(:error).with(match(/Mobile.*Appium.*cannot be adopted/))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe 'validation constants' do
|
|
85
|
+
it 'defines web automations' do
|
|
86
|
+
expect(described_class::WEB_AUTOMATIONS).to contain_exactly('selenium', 'capybara', 'watir')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'defines test frameworks' do
|
|
90
|
+
expect(described_class::TEST_FRAMEWORKS).to contain_exactly('rspec', 'cucumber', 'minitest')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'defines CI platforms' do
|
|
94
|
+
expect(described_class::CI_PLATFORMS).to contain_exactly('github', 'gitlab')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|