ruby_raider 1.1.4 → 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/integration.yml +4 -6
- data/.github/workflows/reek.yml +6 -5
- data/.github/workflows/release.yml +175 -0
- data/.github/workflows/rubocop.yml +7 -6
- data/.github/workflows/steep.yml +21 -0
- data/.github/workflows/system_tests.yml +83 -0
- data/.gitignore +1 -1
- data/.reek.yml +46 -4
- data/.rubocop.yml +24 -0
- data/.ruby-version +1 -1
- data/README.md +140 -77
- data/RELEASE.md +412 -0
- data/RELEASE_QUICK_GUIDE.md +77 -0
- data/Steepfile +22 -0
- data/assets/ruby_raider_logo.svg +51 -0
- data/bin/release +186 -0
- data/lib/adopter/adopt_menu.rb +146 -0
- data/lib/adopter/converters/base_converter.rb +84 -0
- data/lib/adopter/converters/identity_converter.rb +53 -0
- data/lib/adopter/migration_plan.rb +74 -0
- data/lib/adopter/migrator.rb +96 -0
- data/lib/adopter/plan_builder.rb +275 -0
- data/lib/adopter/project_analyzer.rb +252 -0
- data/lib/adopter/project_detector.rb +157 -0
- data/lib/commands/adopt_commands.rb +42 -0
- 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/account.tt +9 -5
- data/lib/generators/automation/templates/appium_caps.tt +60 -6
- data/lib/generators/automation/templates/home.tt +4 -4
- data/lib/generators/automation/templates/login.tt +61 -4
- data/lib/generators/automation/templates/page.tt +13 -7
- data/lib/generators/automation/templates/partials/element.tt +1 -1
- data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -4
- data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -8
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -4
- data/lib/generators/automation/templates/partials/url_methods.tt +0 -1
- data/lib/generators/automation/templates/partials/visit_method.tt +11 -1
- data/lib/generators/automation/templates/pdp.tt +1 -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/env.tt +6 -4
- 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 +38 -0
- data/lib/generators/cucumber/templates/partials/capybara_world.tt +6 -0
- data/lib/generators/cucumber/templates/partials/driver_world.tt +1 -4
- data/lib/generators/cucumber/templates/partials/mobile_steps.tt +2 -2
- 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 +10 -15
- 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/steps.tt +2 -2
- data/lib/generators/cucumber/templates/visual_feature.tt +5 -0
- data/lib/generators/cucumber/templates/visual_steps.tt +19 -0
- data/lib/generators/cucumber/templates/world.tt +5 -3
- data/lib/generators/generator.rb +50 -7
- data/lib/generators/helper_generator.rb +39 -9
- data/lib/generators/infrastructure/github_generator.rb +6 -0
- data/lib/generators/infrastructure/templates/github.tt +12 -8
- data/lib/generators/infrastructure/templates/github_appium.tt +108 -0
- data/lib/generators/infrastructure/templates/gitlab.tt +6 -3
- data/lib/generators/invoke_generators.rb +43 -9
- data/lib/generators/menu_generator.rb +122 -11
- data/lib/generators/minitest/minitest_generator.rb +35 -0
- 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 +64 -0
- 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 +13 -41
- data/lib/generators/rspec/templates/visual_spec.tt +20 -0
- data/lib/generators/template_renderer/partial_cache.rb +126 -0
- data/lib/generators/template_renderer/partial_resolver.rb +110 -0
- data/lib/generators/template_renderer/template_error.rb +50 -0
- data/lib/generators/template_renderer.rb +106 -0
- data/lib/generators/templates/common/config.tt +2 -2
- data/lib/generators/templates/common/gemfile.tt +36 -9
- 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 +17 -8
- data/lib/generators/templates/common/rakefile.tt +36 -0
- data/lib/generators/templates/common/read_me.tt +43 -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 +13 -2
- data/lib/generators/templates/helpers/browser_helper.tt +13 -2
- data/lib/generators/templates/helpers/capybara_helper.tt +32 -0
- data/lib/generators/templates/helpers/debug_helper.tt +190 -0
- data/lib/generators/templates/helpers/driver_helper.tt +3 -11
- data/lib/generators/templates/helpers/partials/allure_imports.tt +3 -1
- data/lib/generators/templates/helpers/partials/allure_requirements.tt +3 -1
- data/lib/generators/templates/helpers/partials/appium_driver.tt +44 -0
- data/lib/generators/templates/helpers/partials/browserstack_config.tt +13 -0
- 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 +5 -115
- data/lib/generators/templates/helpers/partials/quit_driver.tt +3 -1
- data/lib/generators/templates/helpers/partials/screenshot.tt +3 -1
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +26 -0
- 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 +72 -10
- data/lib/generators/templates/helpers/test_helper.tt +94 -0
- 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 +51 -11
- 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 +176 -0
- data/spec/adopter/converters/identity_converter_spec.rb +145 -0
- data/spec/adopter/migration_plan_spec.rb +113 -0
- data/spec/adopter/migrator_spec.rb +277 -0
- data/spec/adopter/plan_builder_spec.rb +298 -0
- data/spec/adopter/project_analyzer_spec.rb +337 -0
- data/spec/adopter/project_detector_spec.rb +295 -0
- data/spec/commands/raider_commands_spec.rb +129 -0
- data/spec/generators/fixtures/templates/test.tt +1 -0
- data/spec/generators/fixtures/templates/test_partial.tt +1 -0
- data/spec/generators/generator_spec.rb +23 -0
- data/spec/generators/template_renderer_spec.rb +298 -0
- data/spec/integration/commands/scaffolding_commands_spec.rb +2 -2
- data/spec/integration/commands/utility_commands_spec.rb +24 -4
- 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 +361 -0
- data/spec/integration/generators/automation_generator_spec.rb +9 -21
- data/spec/integration/generators/axe_addon_spec.rb +150 -0
- data/spec/integration/generators/common_generator_spec.rb +48 -49
- data/spec/integration/generators/config_features_spec.rb +155 -0
- data/spec/integration/generators/cucumber_generator_spec.rb +7 -7
- data/spec/integration/generators/debug_helper_spec.rb +68 -0
- data/spec/integration/generators/github_generator_spec.rb +8 -8
- data/spec/integration/generators/gitlab_generator_spec.rb +8 -8
- data/spec/integration/generators/helpers_generator_spec.rb +70 -44
- data/spec/integration/generators/lighthouse_addon_spec.rb +132 -0
- data/spec/integration/generators/minitest_generator_spec.rb +64 -0
- data/spec/integration/generators/reporter_spec.rb +159 -0
- data/spec/integration/generators/rspec_generator_spec.rb +7 -7
- 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 +1 -4
- data/spec/integration/spec_helper.rb +46 -11
- 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/capybara_spec.rb +42 -0
- data/spec/system/selenium_spec.rb +19 -17
- data/spec/system/support/system_test_helper.rb +33 -0
- data/spec/system/watir_spec.rb +19 -17
- data/spec/utilities/desktop_downloader_spec.rb +92 -0
- metadata +193 -18
- data/.github/workflows/push_gem.yml +0 -37
- data/.github/workflows/selenium.yml +0 -22
- data/.github/workflows/watir.yml +0 -22
- data/lib/generators/automation/templates/partials/android_caps.tt +0 -17
- data/lib/generators/automation/templates/partials/cross_platform_caps.tt +0 -25
- data/lib/generators/automation/templates/partials/ios_caps.tt +0 -18
- data/lib/generators/automation/templates/partials/selenium_account.tt +0 -9
- data/lib/generators/automation/templates/partials/selenium_login.tt +0 -34
- data/lib/generators/automation/templates/partials/watir_account.tt +0 -7
- data/lib/generators/automation/templates/partials/watir_login.tt +0 -32
- data/lib/generators/automation/templates/visual_options.tt +0 -16
- data/lib/generators/templates/helpers/visual_spec_helper.tt +0 -35
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
require 'tty-prompt'
|
|
4
4
|
require_relative '../generators/invoke_generators'
|
|
5
5
|
|
|
6
|
-
# :reek:FeatureEnvy { enabled: false }
|
|
7
|
-
# :reek:UtilityFunction { enabled: false }
|
|
8
6
|
class MenuGenerator
|
|
9
7
|
attr_reader :prompt, :name
|
|
10
8
|
|
|
@@ -32,10 +30,16 @@ class MenuGenerator
|
|
|
32
30
|
automation: options[:automation],
|
|
33
31
|
framework: options[:framework],
|
|
34
32
|
ci_platform: options[:ci_platform],
|
|
33
|
+
reporter: options[:reporter],
|
|
34
|
+
accessibility: options[:accessibility],
|
|
35
|
+
visual: options[:visual],
|
|
36
|
+
performance: options[:performance],
|
|
37
|
+
ruby_version: options[:ruby_version],
|
|
35
38
|
name: @name
|
|
36
39
|
}
|
|
37
40
|
generate_framework(structure)
|
|
38
41
|
system "cd #{name} && gem install bundler && bundle install"
|
|
42
|
+
offer_desktop_download
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def choose_mobile_platform
|
|
@@ -49,29 +53,137 @@ class MenuGenerator
|
|
|
49
53
|
|
|
50
54
|
private
|
|
51
55
|
|
|
56
|
+
def offer_desktop_download
|
|
57
|
+
return unless prompt.yes?('Would you like to download Raider Desktop (GUI companion app)?')
|
|
58
|
+
|
|
59
|
+
require_relative '../utilities/desktop_downloader'
|
|
60
|
+
version = DesktopDownloader.latest_version
|
|
61
|
+
unless version
|
|
62
|
+
prompt.warn('Could not reach GitHub releases. You can download it later with: raider u desktop')
|
|
63
|
+
return
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
prompt.say("Downloading Raider Desktop v#{version} for #{DesktopDownloader.platform_display_name}...")
|
|
67
|
+
result = DesktopDownloader.download
|
|
68
|
+
if result
|
|
69
|
+
prompt.ok("Downloaded to: #{result}")
|
|
70
|
+
else
|
|
71
|
+
prompt.warn('No release found for your platform. Visit: https://github.com/RaiderHQ/raider_desktop/releases')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
52
75
|
def select_test_framework(automation)
|
|
53
76
|
prompt.select('Please select your test framework') do |menu|
|
|
54
77
|
menu.choice :Cucumber, -> { select_ci_platform('Cucumber', automation) }
|
|
55
78
|
menu.choice :Rspec, -> { select_ci_platform('Rspec', automation) }
|
|
79
|
+
menu.choice :Minitest, -> { select_ci_platform('Minitest', automation) }
|
|
56
80
|
menu.choice :Quit, -> { exit }
|
|
57
81
|
end
|
|
58
82
|
end
|
|
59
83
|
|
|
60
|
-
FrameworkOptions = Struct.new(:automation, :framework, :ci_platform
|
|
84
|
+
FrameworkOptions = Struct.new(:automation, :framework, :ci_platform, :reporter, :accessibility, :visual, :performance,
|
|
85
|
+
:ruby_version)
|
|
61
86
|
|
|
62
87
|
def create_framework_options(params)
|
|
63
|
-
FrameworkOptions.new(params[:automation], params[:framework], params[:ci_platform]
|
|
88
|
+
FrameworkOptions.new(params[:automation], params[:framework], params[:ci_platform], params[:reporter],
|
|
89
|
+
params[:accessibility], params[:visual], params[:performance], params[:ruby_version])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def select_reporter(framework, automation_type, ci_platform = nil)
|
|
93
|
+
prompt.select('Select your test reporter') do |menu|
|
|
94
|
+
menu.choice :Allure, -> { select_accessibility(framework, automation_type, ci_platform, 'allure') }
|
|
95
|
+
menu.choice :JUnit, -> { select_accessibility(framework, automation_type, ci_platform, 'junit') }
|
|
96
|
+
menu.choice :JSON, -> { select_accessibility(framework, automation_type, ci_platform, 'json') }
|
|
97
|
+
menu.choice :Both, -> { select_accessibility(framework, automation_type, ci_platform, 'both') }
|
|
98
|
+
menu.choice :All, -> { select_accessibility(framework, automation_type, ci_platform, 'all') }
|
|
99
|
+
menu.choice :None, -> { select_accessibility(framework, automation_type, ci_platform, 'none') }
|
|
100
|
+
menu.choice :Quit, -> { exit }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def select_accessibility(framework, automation_type, ci_platform = nil, reporter = nil)
|
|
105
|
+
return create_framework(framework, automation_type, ci_platform:, reporter:) if mobile_automation?(automation_type)
|
|
106
|
+
|
|
107
|
+
prompt.select('Add accessibility testing (axe)?') do |menu|
|
|
108
|
+
menu.choice :Yes, -> { select_visual(framework, automation_type, ci_platform, reporter, accessibility: true) }
|
|
109
|
+
menu.choice :No, -> { select_visual(framework, automation_type, ci_platform, reporter) }
|
|
110
|
+
menu.choice :Quit, -> { exit }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def select_visual(framework, automation_type, ci_platform = nil, reporter = nil, accessibility: false)
|
|
115
|
+
prompt.select('Add visual regression testing?') do |menu|
|
|
116
|
+
menu.choice :Yes, lambda {
|
|
117
|
+
select_performance(framework, automation_type, ci_platform, reporter, accessibility:, visual: true)
|
|
118
|
+
}
|
|
119
|
+
menu.choice :No, lambda {
|
|
120
|
+
select_performance(framework, automation_type, ci_platform, reporter, accessibility:)
|
|
121
|
+
}
|
|
122
|
+
menu.choice :Quit, -> { exit }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def select_performance(framework, automation_type, ci_platform = nil, reporter = nil, accessibility: false,
|
|
127
|
+
visual: false)
|
|
128
|
+
prompt.select('Add Lighthouse performance auditing?') do |menu|
|
|
129
|
+
menu.choice :Yes, lambda {
|
|
130
|
+
select_ruby_version(framework, automation_type, ci_platform:, reporter:, accessibility:, visual:,
|
|
131
|
+
performance: true)
|
|
132
|
+
}
|
|
133
|
+
menu.choice :No, lambda {
|
|
134
|
+
select_ruby_version(framework, automation_type, ci_platform:, reporter:, accessibility:, visual:)
|
|
135
|
+
}
|
|
136
|
+
menu.choice :Quit, -> { exit }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def select_ruby_version(framework, automation_type, ci_platform: nil, reporter: nil, accessibility: false,
|
|
141
|
+
visual: false, performance: false)
|
|
142
|
+
prompt.select('Select Ruby version for your project') do |menu|
|
|
143
|
+
menu.choice :'3.4 (latest)', lambda {
|
|
144
|
+
create_framework(framework, automation_type, ci_platform:, reporter:, accessibility:, visual:, performance:,
|
|
145
|
+
ruby_version: '3.4')
|
|
146
|
+
}
|
|
147
|
+
menu.choice :'3.3', lambda {
|
|
148
|
+
create_framework(framework, automation_type, ci_platform:, reporter:, accessibility:, visual:, performance:,
|
|
149
|
+
ruby_version: '3.3')
|
|
150
|
+
}
|
|
151
|
+
menu.choice :'3.2', lambda {
|
|
152
|
+
create_framework(framework, automation_type, ci_platform:, reporter:, accessibility:, visual:, performance:,
|
|
153
|
+
ruby_version: '3.2')
|
|
154
|
+
}
|
|
155
|
+
menu.choice :'3.1', lambda {
|
|
156
|
+
create_framework(framework, automation_type, ci_platform:, reporter:, accessibility:, visual:, performance:,
|
|
157
|
+
ruby_version: '3.1')
|
|
158
|
+
}
|
|
159
|
+
menu.choice :Quit, -> { exit }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def mobile_automation?(automation_type)
|
|
164
|
+
%w[ios android cross_platform].include?(automation_type)
|
|
64
165
|
end
|
|
65
166
|
|
|
66
|
-
def create_framework(framework, automation_type, ci_platform
|
|
167
|
+
def create_framework(framework, automation_type, ci_platform: nil, reporter: nil, accessibility: false,
|
|
168
|
+
visual: false, performance: false, ruby_version: nil)
|
|
67
169
|
options = create_framework_options(automation: automation_type,
|
|
68
170
|
framework: framework.downcase,
|
|
69
|
-
ci_platform
|
|
171
|
+
ci_platform:,
|
|
172
|
+
reporter:,
|
|
173
|
+
accessibility:,
|
|
174
|
+
visual:,
|
|
175
|
+
performance:,
|
|
176
|
+
ruby_version:)
|
|
70
177
|
|
|
71
178
|
puts 'Chosen Options:'
|
|
72
179
|
puts " Automation Type: #{options[:automation]}"
|
|
73
180
|
puts " Framework: #{options[:framework]}"
|
|
74
181
|
puts " CI Platform: #{options[:ci_platform]}" if options[:ci_platform]
|
|
182
|
+
puts " Reporter: #{options[:reporter]}" if options[:reporter]
|
|
183
|
+
puts ' Accessibility: enabled' if options[:accessibility]
|
|
184
|
+
puts ' Visual Testing: enabled' if options[:visual]
|
|
185
|
+
puts ' Performance Auditing: enabled' if options[:performance]
|
|
186
|
+
puts " Ruby Version: #{options[:ruby_version]}" if options[:ruby_version]
|
|
75
187
|
|
|
76
188
|
set_up_framework(options)
|
|
77
189
|
prompt.say("You have chosen to use #{framework} with #{automation_type}")
|
|
@@ -92,17 +204,16 @@ class MenuGenerator
|
|
|
92
204
|
|
|
93
205
|
def automation_options(menu)
|
|
94
206
|
menu.choice :Selenium, -> { choose_test_framework('selenium') }
|
|
207
|
+
menu.choice :Capybara, -> { choose_test_framework('capybara') }
|
|
95
208
|
menu.choice :Appium, -> { choose_test_framework('appium') }
|
|
96
209
|
menu.choice :Watir, -> { choose_test_framework('watir') }
|
|
97
|
-
menu.choice :Applitools, -> { choose_test_framework('applitools') }
|
|
98
|
-
menu.choice :Axe, -> { choose_test_framework('axe') }
|
|
99
210
|
end
|
|
100
211
|
|
|
101
212
|
def select_ci_platform(framework, automation)
|
|
102
213
|
prompt.select('Would you like to configure CI?') do |menu|
|
|
103
|
-
menu.choice :'Github Actions', -> {
|
|
104
|
-
menu.choice :'Gitlab CI/CD', -> {
|
|
105
|
-
menu.choice :No, -> {
|
|
214
|
+
menu.choice :'Github Actions', -> { select_reporter(framework, automation, 'github') }
|
|
215
|
+
menu.choice :'Gitlab CI/CD', -> { select_reporter(framework, automation, 'gitlab') }
|
|
216
|
+
menu.choice :No, -> { select_reporter(framework, automation) }
|
|
106
217
|
menu.choice :Quit, -> { exit }
|
|
107
218
|
end
|
|
108
219
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../generator'
|
|
4
|
+
|
|
5
|
+
class MinitestGenerator < Generator
|
|
6
|
+
def generate_login_test
|
|
7
|
+
return unless web?
|
|
8
|
+
|
|
9
|
+
template('test.tt', "#{name}/test/test_login_page.rb")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate_pdp_test
|
|
13
|
+
return unless mobile?
|
|
14
|
+
|
|
15
|
+
template('test.tt', "#{name}/test/test_pdp_page.rb")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate_visual_test
|
|
19
|
+
return unless visual_addon? && web?
|
|
20
|
+
|
|
21
|
+
template('visual_test.tt', "#{name}/test/test_visual.rb")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def generate_accessibility_test
|
|
25
|
+
return unless axe_addon? && web?
|
|
26
|
+
|
|
27
|
+
template('accessibility_test.tt', "#{name}/test/test_accessibility.rb")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def generate_performance_test
|
|
31
|
+
return unless lighthouse_addon? && web?
|
|
32
|
+
|
|
33
|
+
template('performance_test.tt', "#{name}/test/test_performance.rb")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/test_helper'
|
|
4
|
+
|
|
5
|
+
class TestAccessibility < Minitest::Test
|
|
6
|
+
def setup
|
|
7
|
+
super
|
|
8
|
+
<% if capybara? -%>
|
|
9
|
+
visit '/'
|
|
10
|
+
<% elsif watir? -%>
|
|
11
|
+
browser.goto 'https://raider-test-site.onrender.com/'
|
|
12
|
+
<% else -%>
|
|
13
|
+
driver.get 'https://raider-test-site.onrender.com/'
|
|
14
|
+
<% end -%>
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_no_accessibility_violations_on_page
|
|
18
|
+
<% if capybara? -%>
|
|
19
|
+
assert_axe_clean page
|
|
20
|
+
<% elsif watir? -%>
|
|
21
|
+
assert_axe_clean browser.driver
|
|
22
|
+
<% else -%>
|
|
23
|
+
assert_axe_clean driver
|
|
24
|
+
<% end -%>
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/test_helper'
|
|
4
|
+
require_relative '../helpers/performance_helper'
|
|
5
|
+
|
|
6
|
+
class TestPerformance < Minitest::Test
|
|
7
|
+
include PerformanceHelper
|
|
8
|
+
|
|
9
|
+
def test_meets_performance_threshold
|
|
10
|
+
result = assert_performance_above('https://raider-test-site.onrender.com/', threshold: 0.7)
|
|
11
|
+
|
|
12
|
+
refute_equal :error, result[:status],
|
|
13
|
+
"Lighthouse error: #{result[:message]}"
|
|
14
|
+
|
|
15
|
+
assert result[:passed],
|
|
16
|
+
"Performance below threshold (#{result[:threshold]}): #{result[:scores]}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<%- if selenium_based? || watir? || capybara? -%>
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative '../helpers/test_helper'
|
|
5
|
+
require_relative '../models/model_factory'
|
|
6
|
+
require_relative '../page_objects/pages/account'
|
|
7
|
+
require_relative '../page_objects/pages/login'
|
|
8
|
+
|
|
9
|
+
# Uncomment for interactive debugging:
|
|
10
|
+
# require 'debug' # Ruby's built-in debugger (binding.break)
|
|
11
|
+
# require 'pry' # Pry debugger (binding.pry)
|
|
12
|
+
|
|
13
|
+
<%- if selenium_based? || watir? || capybara? -%>
|
|
14
|
+
class TestLogin < Minitest::Test
|
|
15
|
+
def setup
|
|
16
|
+
@user = ModelFactory.for('users')['registered user']
|
|
17
|
+
@login_page = <% if capybara? -%>Login.new<% elsif watir? -%>Login.new(browser)<% else -%>Login.new(driver)<% end %>
|
|
18
|
+
@account_page = <% if capybara? -%>Account.new<% elsif watir? -%>Account.new(browser)<% else -%>Account.new(driver)<% end %>
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def teardown
|
|
22
|
+
<%= partial('quit_driver', strip: true) %>
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_login_with_right_credentials
|
|
26
|
+
# binding.break # Uncomment to pause here and inspect state
|
|
27
|
+
@login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
28
|
+
@login_page.login(@user['username'], @user['password'])
|
|
29
|
+
@account_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
30
|
+
assert_equal "Welcome back #{@user['name']}", @account_page.header.customer_name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_login_with_wrong_credentials
|
|
34
|
+
@login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
35
|
+
@login_page.login(@user['username'], 'wrongPassword')
|
|
36
|
+
@login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
37
|
+
assert_equal 'Login or register', @account_page.header.customer_name
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
<%- end -%>
|
|
41
|
+
<%- else -%>
|
|
42
|
+
<% if cross_platform? -%>
|
|
43
|
+
require_relative '../helpers/appium_helper'
|
|
44
|
+
<%- end -%>
|
|
45
|
+
require_relative '../helpers/test_helper'
|
|
46
|
+
require_relative '../page_objects/pages/home'
|
|
47
|
+
require_relative '../page_objects/pages/pdp'
|
|
48
|
+
|
|
49
|
+
class TestPdp < Minitest::Test
|
|
50
|
+
def setup
|
|
51
|
+
@home_page = Home.new(driver)
|
|
52
|
+
@pdp_page = Pdp.new(driver)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def teardown
|
|
56
|
+
driver.quit_driver
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_shows_add_to_cart_button
|
|
60
|
+
@home_page.go_to_product_detail
|
|
61
|
+
assert_equal 'Add to Cart', @pdp_page.add_to_cart_text
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
<%- end -%>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/test_helper'
|
|
4
|
+
require_relative '../helpers/visual_helper'
|
|
5
|
+
require_relative '../page_objects/pages/login'
|
|
6
|
+
|
|
7
|
+
class TestVisualRegression < Minitest::Test
|
|
8
|
+
include VisualHelper
|
|
9
|
+
|
|
10
|
+
def setup
|
|
11
|
+
super
|
|
12
|
+
@login_page = <% if capybara? -%>Login.new<% elsif watir? -%>Login.new(browser)<% else -%>Login.new(driver)<% end %>
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_login_page_matches_baseline
|
|
16
|
+
@login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
17
|
+
screenshot_path = <% if capybara? -%>page.save_screenshot('tmp/visual_login.png')<% elsif watir? -%>browser.screenshot.save('tmp/visual_login.png')<% else -%>driver.save_screenshot('tmp/visual_login.png')<% end %>
|
|
18
|
+
result = compare_screenshot('login_page', screenshot_path)
|
|
19
|
+
|
|
20
|
+
refute_equal :mismatch, result[:status],
|
|
21
|
+
"Visual diff: #{(result[:diff].to_f * 100).round(2)}% difference. See: #{result[:diff_path]}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -4,7 +4,7 @@ require_relative '../generator'
|
|
|
4
4
|
|
|
5
5
|
class RspecGenerator < Generator
|
|
6
6
|
def generate_login_spec
|
|
7
|
-
return unless web?
|
|
7
|
+
return unless web?
|
|
8
8
|
|
|
9
9
|
template('spec.tt', "#{name}/spec/login_page_spec.rb")
|
|
10
10
|
end
|
|
@@ -15,9 +15,21 @@ class RspecGenerator < Generator
|
|
|
15
15
|
template('spec.tt', "#{name}/spec/pdp_page_spec.rb")
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def
|
|
19
|
-
return unless
|
|
18
|
+
def generate_visual_spec
|
|
19
|
+
return unless visual_addon? && web?
|
|
20
20
|
|
|
21
|
-
template('
|
|
21
|
+
template('visual_spec.tt', "#{name}/spec/visual_spec.rb")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def generate_accessibility_spec
|
|
25
|
+
return unless axe_addon? && web?
|
|
26
|
+
|
|
27
|
+
template('accessibility_spec.tt', "#{name}/spec/accessibility_spec.rb")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def generate_performance_spec
|
|
31
|
+
return unless lighthouse_addon? && web?
|
|
32
|
+
|
|
33
|
+
template('performance_spec.tt', "#{name}/spec/performance_spec.rb")
|
|
22
34
|
end
|
|
23
35
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/spec_helper'
|
|
4
|
+
|
|
5
|
+
describe 'Accessibility' do
|
|
6
|
+
before do
|
|
7
|
+
<% if capybara? -%>
|
|
8
|
+
visit '/'
|
|
9
|
+
<% elsif watir? -%>
|
|
10
|
+
browser.goto 'https://raider-test-site.onrender.com/'
|
|
11
|
+
<% else -%>
|
|
12
|
+
driver.get 'https://raider-test-site.onrender.com/'
|
|
13
|
+
<% end -%>
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'has no accessibility violations on the page' do
|
|
17
|
+
<% if capybara? -%>
|
|
18
|
+
expect(page).to be_axe_clean
|
|
19
|
+
<% elsif watir? -%>
|
|
20
|
+
expect(browser.driver).to be_axe_clean
|
|
21
|
+
<% else -%>
|
|
22
|
+
expect(driver).to be_axe_clean
|
|
23
|
+
<% end -%>
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/spec_helper'
|
|
4
|
+
require_relative '../helpers/performance_helper'
|
|
5
|
+
|
|
6
|
+
describe 'Performance Audit' do
|
|
7
|
+
include PerformanceHelper
|
|
8
|
+
|
|
9
|
+
it 'meets performance threshold' do
|
|
10
|
+
result = assert_performance_above('https://raider-test-site.onrender.com/', threshold: 0.7)
|
|
11
|
+
|
|
12
|
+
expect(result[:status]).not_to eq(:error),
|
|
13
|
+
"Lighthouse error: #{result[:message]}"
|
|
14
|
+
|
|
15
|
+
expect(result[:passed]).to be(true),
|
|
16
|
+
"Performance below threshold (#{result[:threshold]}): #{result[:scores]}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%- if selenium_based? || watir? -%>
|
|
1
|
+
<%- if selenium_based? || watir? || capybara? -%>
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require_relative '../helpers/spec_helper'
|
|
@@ -6,43 +6,20 @@ require_relative '../models/model_factory'
|
|
|
6
6
|
require_relative '../page_objects/pages/account'
|
|
7
7
|
require_relative '../page_objects/pages/login'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
let(:account) { Account.new(driver) }
|
|
13
|
-
let(:user) { ModelFactory.for('users')['registered user'] }
|
|
14
|
-
|
|
15
|
-
before do
|
|
16
|
-
login.visit
|
|
17
|
-
login.log_as(user['username'], user['password'])
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
context 'with a login user on the account page' do
|
|
21
|
-
it 'no accessibility errors are present on the page' do
|
|
22
|
-
expect(account.page).to be_axe_clean
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it 'no accessibility errors are present on the transaction history' do
|
|
26
|
-
transaction_history = '.dash-tile.dash-tile-balloon.clearfix'
|
|
27
|
-
expect(account.page).to be_axe_clean.within transaction_history
|
|
28
|
-
end
|
|
9
|
+
# Uncomment for interactive debugging:
|
|
10
|
+
# require 'debug' # Ruby's built-in debugger (binding.break)
|
|
11
|
+
# require 'pry' # Pry debugger (binding.pry)
|
|
29
12
|
|
|
30
|
-
|
|
31
|
-
heading = '.maintext'
|
|
32
|
-
expect(account.page).to be_axe_clean.within heading
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
<%- elsif selenium_based? || watir? -%>
|
|
13
|
+
<%- if selenium_based? || watir? || capybara? -%>
|
|
37
14
|
describe 'Login' do
|
|
38
15
|
subject(:header) { account_page.header.customer_name }
|
|
39
16
|
|
|
40
17
|
let(:user) { ModelFactory.for('users')['registered user'] }
|
|
41
|
-
let(:login_page) { Login.new
|
|
42
|
-
let(:account_page) { Account.new
|
|
18
|
+
let(:login_page) { <% if capybara? -%>Login.new<% elsif watir? -%>Login.new(browser)<% else -%>Login.new(driver)<% end -%> }
|
|
19
|
+
let(:account_page) { <% if capybara? -%>Account.new<% elsif watir? -%>Account.new(browser)<% else -%>Account.new(driver)<% end -%> }
|
|
43
20
|
|
|
44
21
|
before do
|
|
45
|
-
login_page
|
|
22
|
+
login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
46
23
|
login_page.login(user['username'], password)
|
|
47
24
|
end
|
|
48
25
|
|
|
@@ -50,11 +27,9 @@ describe 'Login' do
|
|
|
50
27
|
let(:password) { user['password'] }
|
|
51
28
|
|
|
52
29
|
it 'can successfully log in' do
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<%- else -%>
|
|
30
|
+
# binding.break # Uncomment to pause here and inspect state
|
|
31
|
+
account_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
56
32
|
expect(header).to eq "Welcome back #{user['name']}"
|
|
57
|
-
<%- end -%>
|
|
58
33
|
end
|
|
59
34
|
end
|
|
60
35
|
|
|
@@ -62,11 +37,8 @@ describe 'Login' do
|
|
|
62
37
|
let(:password) { 'wrongPassword' }
|
|
63
38
|
|
|
64
39
|
it 'cannot log in' do
|
|
65
|
-
|
|
66
|
-
check_page login_page
|
|
67
|
-
<%- else -%>
|
|
40
|
+
login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
68
41
|
expect(header).to eq 'Login or register'
|
|
69
|
-
<%- end -%>
|
|
70
42
|
end
|
|
71
43
|
end
|
|
72
44
|
end
|
|
@@ -86,8 +58,8 @@ class PdpSpec
|
|
|
86
58
|
let(:pdp_page) { Pdp.new(driver) }
|
|
87
59
|
|
|
88
60
|
it 'shows add to cart button' do
|
|
89
|
-
home_page.
|
|
90
|
-
expect(pdp_page.add_to_cart_text).to eq 'Add
|
|
61
|
+
home_page.go_to_product_detail
|
|
62
|
+
expect(pdp_page.add_to_cart_text).to eq 'Add to Cart'
|
|
91
63
|
end
|
|
92
64
|
end
|
|
93
65
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/spec_helper'
|
|
4
|
+
require_relative '../helpers/visual_helper'
|
|
5
|
+
require_relative '../page_objects/pages/login'
|
|
6
|
+
|
|
7
|
+
describe 'Visual Regression' do
|
|
8
|
+
include VisualHelper
|
|
9
|
+
|
|
10
|
+
let(:login_page) { <% if capybara? -%>Login.new<% elsif watir? -%>Login.new(browser)<% else -%>Login.new(driver)<% end -%> }
|
|
11
|
+
|
|
12
|
+
it 'login page matches baseline' do
|
|
13
|
+
login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
|
|
14
|
+
screenshot_path = <% if capybara? -%>page.save_screenshot('tmp/visual_login.png')<% elsif watir? -%>browser.screenshot.save('tmp/visual_login.png')<% else -%>driver.save_screenshot('tmp/visual_login.png')<% end %>
|
|
15
|
+
result = compare_screenshot('login_page', screenshot_path)
|
|
16
|
+
|
|
17
|
+
expect(result[:status]).not_to eq(:mismatch),
|
|
18
|
+
"Visual diff: #{(result[:diff].to_f * 100).round(2)}% difference. See: #{result[:diff_path]}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
require_relative 'partial_resolver'
|
|
5
|
+
require_relative 'template_error'
|
|
6
|
+
|
|
7
|
+
module TemplateRenderer
|
|
8
|
+
# Caches compiled ERB template objects with mtime-based invalidation
|
|
9
|
+
#
|
|
10
|
+
# Cache structure:
|
|
11
|
+
# cache_key => { erb: ERB_object, mtime: Time, path: String }
|
|
12
|
+
#
|
|
13
|
+
# Cache keys include trim_mode to support different whitespace handling:
|
|
14
|
+
# "screenshot:-" => ERB object with trim_mode: '-'
|
|
15
|
+
# "screenshot:" => ERB object with no trim_mode
|
|
16
|
+
#
|
|
17
|
+
# Performance: ~10x speedup on cached renders (135ms → ~13.5ms)
|
|
18
|
+
class PartialCache
|
|
19
|
+
# Set to true during batch generation to skip File.mtime syscalls on cache hits
|
|
20
|
+
attr_accessor :batch_mode
|
|
21
|
+
|
|
22
|
+
def initialize(generator_class)
|
|
23
|
+
@cache = {}
|
|
24
|
+
@resolver = PartialResolver.new(generator_class)
|
|
25
|
+
@batch_mode = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Render a partial with caching
|
|
29
|
+
#
|
|
30
|
+
# @param name [String] Partial name (without .tt extension)
|
|
31
|
+
# @param binding [Binding] Binding context for ERB evaluation
|
|
32
|
+
# @param options [Hash] Rendering options
|
|
33
|
+
# @option options [String, nil] :trim_mode ERB trim mode ('-', '<>', etc.)
|
|
34
|
+
# @return [String] Rendered template content
|
|
35
|
+
# @raise [TemplateNotFoundError] If partial not found
|
|
36
|
+
# @raise [TemplateRenderError] If rendering fails
|
|
37
|
+
def render_partial(name, binding, options = {})
|
|
38
|
+
trim_mode = options[:trim_mode]
|
|
39
|
+
cache_key = build_cache_key(name, trim_mode)
|
|
40
|
+
|
|
41
|
+
# Resolve the partial path
|
|
42
|
+
path = @resolver.resolve(name, binding)
|
|
43
|
+
|
|
44
|
+
# Get from cache or compile
|
|
45
|
+
erb = get_or_compile(cache_key, path, trim_mode)
|
|
46
|
+
|
|
47
|
+
# Render with provided binding
|
|
48
|
+
erb.result(binding)
|
|
49
|
+
rescue Errno::ENOENT => e
|
|
50
|
+
raise TemplateNotFoundError.new(
|
|
51
|
+
"Partial '#{name}' not found",
|
|
52
|
+
partial_name: name,
|
|
53
|
+
searched_paths: @resolver.search_paths(name, binding),
|
|
54
|
+
original_error: e
|
|
55
|
+
)
|
|
56
|
+
rescue TemplateError
|
|
57
|
+
raise
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
# Catch ERB syntax errors or other rendering issues
|
|
60
|
+
raise TemplateRenderError.new(
|
|
61
|
+
e.message,
|
|
62
|
+
partial_name: name,
|
|
63
|
+
original_error: e
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Clear the entire cache (useful for testing)
|
|
68
|
+
def clear
|
|
69
|
+
@cache.clear
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get cache statistics (useful for debugging/monitoring)
|
|
73
|
+
def stats
|
|
74
|
+
{
|
|
75
|
+
size: @cache.size,
|
|
76
|
+
entries: @cache.keys,
|
|
77
|
+
memory_estimate: estimate_cache_size
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
# Build cache key that includes trim_mode
|
|
84
|
+
def build_cache_key(name, trim_mode)
|
|
85
|
+
"#{name}:#{trim_mode}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Get cached ERB or compile and cache it.
|
|
89
|
+
# In batch_mode, skip File.mtime syscalls on cache hits since templates
|
|
90
|
+
# don't change mid-generation (~2-5ms saved per partial render).
|
|
91
|
+
def get_or_compile(cache_key, path, trim_mode)
|
|
92
|
+
cached = @cache[cache_key]
|
|
93
|
+
|
|
94
|
+
# Fast path: batch mode cache hit (skip mtime check)
|
|
95
|
+
return cached[:erb] if cached && @batch_mode
|
|
96
|
+
|
|
97
|
+
current_mtime = File.mtime(path)
|
|
98
|
+
|
|
99
|
+
# Cache miss or stale cache (file modified)
|
|
100
|
+
if cached.nil? || cached[:mtime] < current_mtime
|
|
101
|
+
erb = compile_template(path, trim_mode)
|
|
102
|
+
@cache[cache_key] = {
|
|
103
|
+
erb:,
|
|
104
|
+
mtime: current_mtime,
|
|
105
|
+
path:
|
|
106
|
+
}
|
|
107
|
+
return erb
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Cache hit
|
|
111
|
+
cached[:erb]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Compile an ERB template
|
|
115
|
+
def compile_template(path, trim_mode)
|
|
116
|
+
content = File.read(path)
|
|
117
|
+
ERB.new(content, trim_mode:)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Rough estimate of cache memory usage
|
|
121
|
+
# Each entry: ~2-10 KB (ERB object + metadata)
|
|
122
|
+
def estimate_cache_size
|
|
123
|
+
@cache.size * 5 * 1024 # Rough estimate: 5 KB per entry
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|