ruby_raider 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +58 -0
  3. data/.github/workflows/steep.yml +21 -0
  4. data/.gitignore +1 -1
  5. data/.reek.yml +46 -4
  6. data/.ruby-version +1 -1
  7. data/README.md +138 -77
  8. data/Steepfile +22 -0
  9. data/assets/ruby_raider_logo.svg +51 -0
  10. data/lib/adopter/adopt_menu.rb +11 -15
  11. data/lib/adopter/converters/base_converter.rb +1 -2
  12. data/lib/adopter/converters/identity_converter.rb +3 -6
  13. data/lib/adopter/migration_plan.rb +0 -1
  14. data/lib/adopter/plan_builder.rb +2 -5
  15. data/lib/adopter/project_analyzer.rb +1 -5
  16. data/lib/adopter/project_detector.rb +3 -5
  17. data/lib/commands/adopt_commands.rb +0 -1
  18. data/lib/commands/plugin_commands.rb +0 -2
  19. data/lib/commands/scaffolding_commands.rb +220 -37
  20. data/lib/commands/utility_commands.rb +82 -2
  21. data/lib/generators/automation/automation_generator.rb +0 -7
  22. data/lib/generators/automation/templates/partials/element.tt +1 -1
  23. data/lib/generators/automation/templates/partials/initialize_selector.tt +0 -7
  24. data/lib/generators/automation/templates/partials/url_methods.tt +0 -1
  25. data/lib/generators/common_generator.rb +12 -0
  26. data/lib/generators/cucumber/cucumber_generator.rb +36 -0
  27. data/lib/generators/cucumber/templates/accessibility_feature.tt +5 -0
  28. data/lib/generators/cucumber/templates/accessibility_steps.tt +21 -0
  29. data/lib/generators/cucumber/templates/cucumber.tt +8 -1
  30. data/lib/generators/cucumber/templates/feature.tt +0 -4
  31. data/lib/generators/cucumber/templates/partials/appium_env.tt +5 -0
  32. data/lib/generators/cucumber/templates/partials/capybara_env.tt +19 -1
  33. data/lib/generators/cucumber/templates/partials/driver_world.tt +1 -4
  34. data/lib/generators/cucumber/templates/partials/selenium_env.tt +22 -35
  35. data/lib/generators/cucumber/templates/partials/watir_env.tt +20 -1
  36. data/lib/generators/cucumber/templates/partials/web_steps.tt +6 -12
  37. data/lib/generators/cucumber/templates/performance_feature.tt +5 -0
  38. data/lib/generators/cucumber/templates/performance_steps.tt +17 -0
  39. data/lib/generators/cucumber/templates/visual_feature.tt +5 -0
  40. data/lib/generators/cucumber/templates/visual_steps.tt +19 -0
  41. data/lib/generators/generator.rb +38 -7
  42. data/lib/generators/helper_generator.rb +24 -7
  43. data/lib/generators/infrastructure/templates/github.tt +1 -1
  44. data/lib/generators/infrastructure/templates/github_appium.tt +2 -2
  45. data/lib/generators/infrastructure/templates/gitlab.tt +1 -1
  46. data/lib/generators/invoke_generators.rb +42 -9
  47. data/lib/generators/menu_generator.rb +120 -11
  48. data/lib/generators/minitest/minitest_generator.rb +16 -4
  49. data/lib/generators/minitest/templates/accessibility_test.tt +26 -0
  50. data/lib/generators/minitest/templates/performance_test.tt +18 -0
  51. data/lib/generators/minitest/templates/test.tt +5 -34
  52. data/lib/generators/minitest/templates/visual_test.tt +23 -0
  53. data/lib/generators/rspec/rspec_generator.rb +16 -4
  54. data/lib/generators/rspec/templates/accessibility_spec.tt +25 -0
  55. data/lib/generators/rspec/templates/performance_spec.tt +18 -0
  56. data/lib/generators/rspec/templates/spec.tt +5 -35
  57. data/lib/generators/rspec/templates/visual_spec.tt +20 -0
  58. data/lib/generators/template_renderer/partial_cache.rb +11 -1
  59. data/lib/generators/template_renderer/partial_resolver.rb +17 -10
  60. data/lib/generators/template_renderer.rb +17 -1
  61. data/lib/generators/templates/common/gemfile.tt +21 -6
  62. data/lib/generators/templates/common/git_ignore.tt +6 -1
  63. data/lib/generators/templates/common/partials/mobile_config.tt +5 -1
  64. data/lib/generators/templates/common/partials/web_config.tt +16 -7
  65. data/lib/generators/templates/common/rakefile.tt +36 -0
  66. data/lib/generators/templates/common/read_me.tt +41 -91
  67. data/lib/generators/templates/common/rspec.tt +3 -0
  68. data/lib/generators/templates/common/ruby_version.tt +1 -0
  69. data/lib/generators/templates/helpers/allure_helper.tt +11 -0
  70. data/lib/generators/templates/helpers/browser_helper.tt +12 -2
  71. data/lib/generators/templates/helpers/capybara_helper.tt +5 -1
  72. data/lib/generators/templates/helpers/debug_helper.tt +190 -0
  73. data/lib/generators/templates/helpers/driver_helper.tt +2 -10
  74. data/lib/generators/templates/helpers/partials/appium_driver.tt +0 -2
  75. data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +7 -0
  76. data/lib/generators/templates/helpers/partials/debug_start.tt +7 -0
  77. data/lib/generators/templates/helpers/partials/driver_and_options.tt +1 -3
  78. data/lib/generators/templates/helpers/partials/selenium_driver.tt +8 -7
  79. data/lib/generators/templates/helpers/partials/video_start.tt +9 -0
  80. data/lib/generators/templates/helpers/partials/video_stop.tt +4 -0
  81. data/lib/generators/templates/helpers/performance_helper.tt +57 -0
  82. data/lib/generators/templates/helpers/spec_helper.tt +57 -8
  83. data/lib/generators/templates/helpers/test_helper.tt +69 -1
  84. data/lib/generators/templates/helpers/video_helper.tt +270 -0
  85. data/lib/generators/templates/helpers/visual_helper.tt +39 -46
  86. data/lib/llm/client.rb +79 -0
  87. data/lib/llm/config.rb +57 -0
  88. data/lib/llm/prompts.rb +84 -0
  89. data/lib/llm/provider.rb +27 -0
  90. data/lib/llm/providers/anthropic_provider.rb +43 -0
  91. data/lib/llm/providers/ollama_provider.rb +56 -0
  92. data/lib/llm/providers/openai_provider.rb +42 -0
  93. data/lib/llm/response_parser.rb +67 -0
  94. data/lib/plugin/plugin.rb +22 -20
  95. data/lib/plugin/plugin_exposer.rb +16 -38
  96. data/lib/ruby_raider.rb +47 -12
  97. data/lib/scaffolding/crud_generator.rb +94 -0
  98. data/lib/scaffolding/dry_run_presenter.rb +16 -0
  99. data/lib/scaffolding/name_normalizer.rb +63 -0
  100. data/lib/scaffolding/page_introspector.rb +45 -0
  101. data/lib/scaffolding/project_detector.rb +72 -0
  102. data/lib/scaffolding/scaffold_menu.rb +103 -0
  103. data/lib/scaffolding/scaffolding.rb +158 -11
  104. data/lib/scaffolding/templates/component.tt +30 -0
  105. data/lib/scaffolding/templates/feature.tt +4 -4
  106. data/lib/scaffolding/templates/helper.tt +15 -1
  107. data/lib/scaffolding/templates/page_from_url.tt +75 -0
  108. data/lib/scaffolding/templates/page_object.tt +50 -1
  109. data/lib/scaffolding/templates/spec.tt +33 -2
  110. data/lib/scaffolding/templates/spec_from_page.tt +31 -0
  111. data/lib/scaffolding/templates/spec_from_url.tt +46 -0
  112. data/lib/scaffolding/templates/steps.tt +17 -5
  113. data/lib/scaffolding/url_analyzer.rb +179 -0
  114. data/lib/utilities/desktop_downloader.rb +177 -0
  115. data/lib/utilities/logo.rb +83 -0
  116. data/lib/utilities/utilities.rb +53 -20
  117. data/lib/version +1 -1
  118. data/ruby_raider.gemspec +1 -0
  119. data/sig/adopter/adopt_menu.rbs +25 -0
  120. data/sig/adopter/converters/base_converter.rbs +23 -0
  121. data/sig/adopter/converters/identity_converter.rbs +16 -0
  122. data/sig/adopter/migration_plan.rbs +34 -0
  123. data/sig/adopter/migrator.rbs +21 -0
  124. data/sig/adopter/plan_builder.rbs +38 -0
  125. data/sig/adopter/project_analyzer.rbs +39 -0
  126. data/sig/adopter/project_detector.rbs +26 -0
  127. data/sig/commands/adopt_commands.rbs +8 -0
  128. data/sig/commands/loaded_commands.rbs +5 -0
  129. data/sig/commands/plugin_commands.rbs +9 -0
  130. data/sig/commands/scaffolding_commands.rbs +28 -0
  131. data/sig/commands/utility_commands.rbs +21 -0
  132. data/sig/generators/automation/automation_generator.rbs +20 -0
  133. data/sig/generators/common_generator.rbs +12 -0
  134. data/sig/generators/cucumber/cucumber_generator.rbs +16 -0
  135. data/sig/generators/generator.rbs +40 -0
  136. data/sig/generators/helper_generator.rbs +18 -0
  137. data/sig/generators/infrastructure/github_generator.rbs +5 -0
  138. data/sig/generators/infrastructure/gitlab_generator.rbs +4 -0
  139. data/sig/generators/invoke_generators.rbs +10 -0
  140. data/sig/generators/menu_generator.rbs +29 -0
  141. data/sig/generators/minitest/minitest_generator.rbs +8 -0
  142. data/sig/generators/rspec/rspec_generator.rbs +8 -0
  143. data/sig/generators/template_renderer/partial_cache.rbs +20 -0
  144. data/sig/generators/template_renderer/partial_resolver.rbs +20 -0
  145. data/sig/generators/template_renderer/template_error.rbs +19 -0
  146. data/sig/generators/template_renderer.rbs +10 -0
  147. data/sig/llm/client.rbs +15 -0
  148. data/sig/llm/config.rbs +20 -0
  149. data/sig/llm/prompts.rbs +8 -0
  150. data/sig/llm/provider.rbs +12 -0
  151. data/sig/llm/providers/anthropic_provider.rbs +16 -0
  152. data/sig/llm/providers/ollama_provider.rbs +18 -0
  153. data/sig/llm/providers/openai_provider.rbs +16 -0
  154. data/sig/llm/response_parser.rbs +13 -0
  155. data/sig/plugin/plugin.rbs +24 -0
  156. data/sig/plugin/plugin_exposer.rbs +20 -0
  157. data/sig/ruby_raider.rbs +15 -0
  158. data/sig/scaffolding/crud_generator.rbs +16 -0
  159. data/sig/scaffolding/dry_run_presenter.rbs +4 -0
  160. data/sig/scaffolding/name_normalizer.rbs +17 -0
  161. data/sig/scaffolding/page_introspector.rbs +14 -0
  162. data/sig/scaffolding/project_detector.rbs +14 -0
  163. data/sig/scaffolding/scaffold_menu.rbs +18 -0
  164. data/sig/scaffolding/scaffolding.rbs +55 -0
  165. data/sig/scaffolding/url_analyzer.rbs +28 -0
  166. data/sig/utilities/desktop_downloader.rbs +23 -0
  167. data/sig/utilities/logger.rbs +13 -0
  168. data/sig/utilities/logo.rbs +16 -0
  169. data/sig/utilities/utilities.rbs +30 -0
  170. data/sig/vendor/thor.rbs +34 -0
  171. data/sig/vendor/tty_prompt.rbs +15 -0
  172. data/spec/adopter/adopt_menu_spec.rb +12 -12
  173. data/spec/adopter/migration_plan_spec.rb +1 -1
  174. data/spec/adopter/migrator_spec.rb +2 -2
  175. data/spec/adopter/project_detector_spec.rb +1 -1
  176. data/spec/commands/raider_commands_spec.rb +129 -0
  177. data/spec/generators/generator_spec.rb +23 -0
  178. data/spec/integration/commands/scaffolding_commands_spec.rb +1 -1
  179. data/spec/integration/commands/utility_commands_spec.rb +23 -3
  180. data/spec/integration/content/ci_content_spec.rb +119 -0
  181. data/spec/integration/content/common_content_spec.rb +288 -0
  182. data/spec/integration/content/config_content_spec.rb +175 -0
  183. data/spec/integration/content/content_helper.rb +32 -0
  184. data/spec/integration/content/gemfile_content_spec.rb +209 -0
  185. data/spec/integration/content/helper_content_spec.rb +485 -0
  186. data/spec/integration/content/page_content_spec.rb +259 -0
  187. data/spec/integration/content/reporter_content_spec.rb +236 -0
  188. data/spec/integration/content/skip_flags_content_spec.rb +206 -0
  189. data/spec/integration/content/syntax_validation_spec.rb +30 -0
  190. data/spec/integration/content/test_content_spec.rb +266 -0
  191. data/spec/integration/end_to_end_features_spec.rb +690 -0
  192. data/spec/integration/end_to_end_spec.rb +52 -16
  193. data/spec/integration/generators/automation_generator_spec.rb +0 -12
  194. data/spec/integration/generators/axe_addon_spec.rb +150 -0
  195. data/spec/integration/generators/common_generator_spec.rb +12 -13
  196. data/spec/integration/generators/config_features_spec.rb +155 -0
  197. data/spec/integration/generators/debug_helper_spec.rb +68 -0
  198. data/spec/integration/generators/helpers_generator_spec.rb +0 -12
  199. data/spec/integration/generators/lighthouse_addon_spec.rb +132 -0
  200. data/spec/integration/generators/minitest_generator_spec.rb +0 -6
  201. data/spec/integration/generators/reporter_spec.rb +159 -0
  202. data/spec/integration/generators/skip_flags_spec.rb +134 -0
  203. data/spec/integration/generators/visual_addon_spec.rb +148 -0
  204. data/spec/integration/settings_helper.rb +0 -3
  205. data/spec/integration/spec_helper.rb +30 -13
  206. data/spec/llm/client_spec.rb +79 -0
  207. data/spec/llm/config_spec.rb +92 -0
  208. data/spec/llm/prompts_spec.rb +49 -0
  209. data/spec/llm/response_parser_spec.rb +92 -0
  210. data/spec/menus/adopter_adopt_menu_spec.rb +97 -0
  211. data/spec/menus/menu_generator_spec.rb +263 -0
  212. data/spec/scaffolding/name_normalizer_spec.rb +113 -0
  213. data/spec/scaffolding/page_introspector_spec.rb +82 -0
  214. data/spec/scaffolding/scaffold_project_detector_spec.rb +104 -0
  215. data/spec/scaffolding/scaffolding_features_spec.rb +311 -0
  216. data/spec/scaffolding/url_analyzer_spec.rb +110 -0
  217. data/spec/system/adopt_matrix_spec.rb +537 -0
  218. data/spec/system/adopt_spec.rb +225 -0
  219. data/spec/system/support/system_test_helper.rb +0 -2
  220. data/spec/utilities/desktop_downloader_spec.rb +92 -0
  221. metadata +150 -5
  222. data/lib/generators/automation/templates/visual_options.tt +0 -16
  223. data/lib/generators/templates/helpers/partials/axe_driver.tt +0 -10
  224. 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,6 +53,25 @@ 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) }
@@ -58,21 +81,109 @@ class MenuGenerator
58
81
  end
59
82
  end
60
83
 
61
- FrameworkOptions = Struct.new(:automation, :framework, :ci_platform)
84
+ FrameworkOptions = Struct.new(:automation, :framework, :ci_platform, :reporter, :accessibility, :visual, :performance,
85
+ :ruby_version)
62
86
 
63
87
  def create_framework_options(params)
64
- 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)
65
165
  end
66
166
 
67
- def create_framework(framework, automation_type, ci_platform = nil)
167
+ def create_framework(framework, automation_type, ci_platform: nil, reporter: nil, accessibility: false,
168
+ visual: false, performance: false, ruby_version: nil)
68
169
  options = create_framework_options(automation: automation_type,
69
170
  framework: framework.downcase,
70
- ci_platform:)
171
+ ci_platform:,
172
+ reporter:,
173
+ accessibility:,
174
+ visual:,
175
+ performance:,
176
+ ruby_version:)
71
177
 
72
178
  puts 'Chosen Options:'
73
179
  puts " Automation Type: #{options[:automation]}"
74
180
  puts " Framework: #{options[:framework]}"
75
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]
76
187
 
77
188
  set_up_framework(options)
78
189
  prompt.say("You have chosen to use #{framework} with #{automation_type}")
@@ -96,15 +207,13 @@ class MenuGenerator
96
207
  menu.choice :Capybara, -> { choose_test_framework('capybara') }
97
208
  menu.choice :Appium, -> { choose_test_framework('appium') }
98
209
  menu.choice :Watir, -> { choose_test_framework('watir') }
99
- menu.choice :Applitools, -> { choose_test_framework('applitools') }
100
- menu.choice :Axe, -> { choose_test_framework('axe') }
101
210
  end
102
211
 
103
212
  def select_ci_platform(framework, automation)
104
213
  prompt.select('Would you like to configure CI?') do |menu|
105
- menu.choice :'Github Actions', -> { create_framework(framework, automation, 'github') }
106
- menu.choice :'Gitlab CI/CD', -> { create_framework(framework, automation, 'gitlab') }
107
- menu.choice :No, -> { create_framework(framework, automation) }
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) }
108
217
  menu.choice :Quit, -> { exit }
109
218
  end
110
219
  end
@@ -4,7 +4,7 @@ require_relative '../generator'
4
4
 
5
5
  class MinitestGenerator < Generator
6
6
  def generate_login_test
7
- return unless web? && !visual?
7
+ return unless web?
8
8
 
9
9
  template('test.tt', "#{name}/test/test_login_page.rb")
10
10
  end
@@ -15,9 +15,21 @@ class MinitestGenerator < Generator
15
15
  template('test.tt', "#{name}/test/test_pdp_page.rb")
16
16
  end
17
17
 
18
- def generate_account_test
19
- return unless visual?
18
+ def generate_visual_test
19
+ return unless visual_addon? && web?
20
20
 
21
- template('test.tt', "#{name}/test/test_account_page.rb")
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")
22
34
  end
23
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
@@ -6,33 +6,11 @@ require_relative '../models/model_factory'
6
6
  require_relative '../page_objects/pages/account'
7
7
  require_relative '../page_objects/pages/login'
8
8
 
9
- <%- if axe? %>
10
- class TestLogin < Minitest::Test
11
- def setup
12
- @login = Login.new(driver)
13
- @account = Account.new(driver)
14
- @user = ModelFactory.for('users')['registered user']
15
- @login.visit
16
- @login.log_as(@user['username'], @user['password'])
17
- end
18
-
19
- def teardown
20
- driver.quit
21
- end
9
+ # Uncomment for interactive debugging:
10
+ # require 'debug' # Ruby's built-in debugger (binding.break)
11
+ # require 'pry' # Pry debugger (binding.pry)
22
12
 
23
- def test_no_accessibility_errors_on_page
24
- assert_axe_clean @account.page
25
- end
26
-
27
- def test_no_accessibility_errors_on_transaction_history
28
- assert_axe_clean @account.page, within: '.account-card'
29
- end
30
-
31
- def test_no_accessibility_errors_on_heading
32
- assert_axe_clean @account.page, within: '.page-title'
33
- end
34
- end
35
- <%- elsif selenium_based? || watir? || capybara? -%>
13
+ <%- if selenium_based? || watir? || capybara? -%>
36
14
  class TestLogin < Minitest::Test
37
15
  def setup
38
16
  @user = ModelFactory.for('users')['registered user']
@@ -45,25 +23,18 @@ class TestLogin < Minitest::Test
45
23
  end
46
24
 
47
25
  def test_login_with_right_credentials
26
+ # binding.break # Uncomment to pause here and inspect state
48
27
  @login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
49
28
  @login_page.login(@user['username'], @user['password'])
50
- <%- if visual? -%>
51
- check_page @account_page
52
- <%- else -%>
53
29
  @account_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
54
30
  assert_equal "Welcome back #{@user['name']}", @account_page.header.customer_name
55
- <%- end -%>
56
31
  end
57
32
 
58
33
  def test_login_with_wrong_credentials
59
34
  @login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
60
35
  @login_page.login(@user['username'], 'wrongPassword')
61
- <%- if visual? -%>
62
- check_page @login_page
63
- <%- else -%>
64
36
  @login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
65
37
  assert_equal 'Login or register', @account_page.header.customer_name
66
- <%- end -%>
67
38
  end
68
39
  end
69
40
  <%- 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? && !visual?
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 generate_account_spec
19
- return unless visual?
18
+ def generate_visual_spec
19
+ return unless visual_addon? && web?
20
20
 
21
- template('spec.tt', "#{name}/spec/account_page_spec.rb")
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
@@ -6,34 +6,11 @@ require_relative '../models/model_factory'
6
6
  require_relative '../page_objects/pages/account'
7
7
  require_relative '../page_objects/pages/login'
8
8
 
9
- <%- if axe? %>
10
- describe 'Login' do
11
- let(:login) { Login.new(driver) }
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
9
+ # Uncomment for interactive debugging:
10
+ # require 'debug' # Ruby's built-in debugger (binding.break)
11
+ # require 'pry' # Pry debugger (binding.pry)
24
12
 
25
- it 'no accessibility errors are present on the transaction history' do
26
- account_details = '.account-card'
27
- expect(account.page).to be_axe_clean.within account_details
28
- end
29
-
30
- it 'no accessibility errors are present on the heading' do
31
- heading = '.page-title'
32
- expect(account.page).to be_axe_clean.within heading
33
- end
34
- end
35
- end
36
- <%- elsif selenium_based? || watir? || capybara? -%>
13
+ <%- if selenium_based? || watir? || capybara? -%>
37
14
  describe 'Login' do
38
15
  subject(:header) { account_page.header.customer_name }
39
16
 
@@ -50,12 +27,9 @@ describe 'Login' do
50
27
  let(:password) { user['password'] }
51
28
 
52
29
  it 'can successfully log in' do
53
- <%- if visual? -%>
54
- check_page account_page
55
- <%- else -%>
30
+ # binding.break # Uncomment to pause here and inspect state
56
31
  account_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
57
32
  expect(header).to eq "Welcome back #{user['name']}"
58
- <%- end -%>
59
33
  end
60
34
  end
61
35
 
@@ -63,12 +37,8 @@ describe 'Login' do
63
37
  let(:password) { 'wrongPassword' }
64
38
 
65
39
  it 'cannot log in' do
66
- <%- if visual? -%>
67
- check_page login_page
68
- <%- else -%>
69
40
  login_page.<% if capybara? -%>visit_page<% else -%>visit<% end %>
70
41
  expect(header).to eq 'Login or register'
71
- <%- end -%>
72
42
  end
73
43
  end
74
44
  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
@@ -16,9 +16,13 @@ module TemplateRenderer
16
16
  #
17
17
  # Performance: ~10x speedup on cached renders (135ms → ~13.5ms)
18
18
  class PartialCache
19
+ # Set to true during batch generation to skip File.mtime syscalls on cache hits
20
+ attr_accessor :batch_mode
21
+
19
22
  def initialize(generator_class)
20
23
  @cache = {}
21
24
  @resolver = PartialResolver.new(generator_class)
25
+ @batch_mode = false
22
26
  end
23
27
 
24
28
  # Render a partial with caching
@@ -81,9 +85,15 @@ module TemplateRenderer
81
85
  "#{name}:#{trim_mode}"
82
86
  end
83
87
 
84
- # Get cached ERB or compile and cache it
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).
85
91
  def get_or_compile(cache_key, path, trim_mode)
86
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
+
87
97
  current_mtime = File.mtime(path)
88
98
 
89
99
  # Cache miss or stale cache (file modified)
@@ -78,18 +78,13 @@ module TemplateRenderer
78
78
  nil
79
79
  end
80
80
 
81
- # Search all Generator.source_paths for the partial
82
- # Checks both source_path/partials/ and source_path/*/partials/
81
+ # Search all Generator.source_paths for the partial.
82
+ # Uses a cached directory index to avoid repeated Dir.glob calls.
83
83
  def search_source_paths(partial_filename)
84
- source_paths.each do |source_path|
85
- # First try direct partials directory
86
- full_path = File.join(source_path, PARTIALS_DIR, partial_filename)
84
+ # Check the pre-built index first (fast path)
85
+ partial_dirs.each do |dir|
86
+ full_path = File.join(dir, partial_filename)
87
87
  return { found: File.expand_path(full_path), searched: [full_path] } if File.exist?(full_path)
88
-
89
- # Then try subdirectories (e.g., templates/common/partials/, templates/helpers/partials/)
90
- Dir.glob(File.join(source_path, '*', PARTIALS_DIR, partial_filename)).each do |path|
91
- return { found: File.expand_path(path), searched: [path] } if File.exist?(path)
92
- end
93
88
  end
94
89
 
95
90
  { found: nil, searched: [] }
@@ -99,5 +94,17 @@ module TemplateRenderer
99
94
  def source_paths
100
95
  @source_paths ||= @generator_class.source_paths || []
101
96
  end
97
+
98
+ # Pre-built index of all directories that could contain partials.
99
+ # Scanned once via Dir.glob, then reused for every partial lookup.
100
+ def partial_dirs
101
+ @partial_dirs ||= source_paths.flat_map do |source_path|
102
+ dirs = []
103
+ direct = File.join(source_path, PARTIALS_DIR)
104
+ dirs << direct if File.directory?(direct)
105
+ Dir.glob(File.join(source_path, '*', PARTIALS_DIR)).each { |d| dirs << d }
106
+ dirs
107
+ end
108
+ end
102
109
  end
103
110
  end
@@ -77,14 +77,30 @@ module TemplateRenderer
77
77
  end
78
78
 
79
79
  # Clear the template cache (useful for testing)
80
+ # Clears cache for this class and all subclasses
80
81
  def clear_template_cache
81
82
  @template_renderer&.clear
82
83
  @template_renderer = nil
84
+ subclasses.each do |subclass|
85
+ subclass.clear_template_cache if subclass.respond_to?(:clear_template_cache)
86
+ end
83
87
  end
84
88
 
85
89
  # Get cache statistics (useful for debugging)
90
+ # Aggregates stats across this class and all subclasses
86
91
  def template_cache_stats
87
- @template_renderer&.stats || { size: 0, entries: [], memory_estimate: 0 }
92
+ own = @template_renderer&.stats || { size: 0, entries: [], memory_estimate: 0 }
93
+ subclasses.each do |subclass|
94
+ next unless subclass.respond_to?(:template_cache_stats)
95
+
96
+ sub_stats = subclass.instance_variable_get(:@template_renderer)&.stats
97
+ next unless sub_stats
98
+
99
+ own[:size] += sub_stats[:size]
100
+ own[:entries].concat(sub_stats[:entries])
101
+ own[:memory_estimate] += sub_stats[:memory_estimate]
102
+ end
103
+ own
88
104
  end
89
105
  end
90
106
  end