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.
Files changed (278) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +58 -0
  3. data/.github/workflows/integration.yml +4 -6
  4. data/.github/workflows/reek.yml +6 -5
  5. data/.github/workflows/release.yml +175 -0
  6. data/.github/workflows/rubocop.yml +7 -6
  7. data/.github/workflows/steep.yml +21 -0
  8. data/.github/workflows/system_tests.yml +83 -0
  9. data/.gitignore +1 -1
  10. data/.reek.yml +46 -4
  11. data/.rubocop.yml +24 -0
  12. data/.ruby-version +1 -1
  13. data/README.md +140 -77
  14. data/RELEASE.md +412 -0
  15. data/RELEASE_QUICK_GUIDE.md +77 -0
  16. data/Steepfile +22 -0
  17. data/assets/ruby_raider_logo.svg +51 -0
  18. data/bin/release +186 -0
  19. data/lib/adopter/adopt_menu.rb +146 -0
  20. data/lib/adopter/converters/base_converter.rb +84 -0
  21. data/lib/adopter/converters/identity_converter.rb +53 -0
  22. data/lib/adopter/migration_plan.rb +74 -0
  23. data/lib/adopter/migrator.rb +96 -0
  24. data/lib/adopter/plan_builder.rb +275 -0
  25. data/lib/adopter/project_analyzer.rb +252 -0
  26. data/lib/adopter/project_detector.rb +157 -0
  27. data/lib/commands/adopt_commands.rb +42 -0
  28. data/lib/commands/plugin_commands.rb +0 -2
  29. data/lib/commands/scaffolding_commands.rb +220 -37
  30. data/lib/commands/utility_commands.rb +82 -2
  31. data/lib/generators/automation/automation_generator.rb +0 -7
  32. data/lib/generators/automation/templates/account.tt +9 -5
  33. data/lib/generators/automation/templates/appium_caps.tt +60 -6
  34. data/lib/generators/automation/templates/home.tt +4 -4
  35. data/lib/generators/automation/templates/login.tt +61 -4
  36. data/lib/generators/automation/templates/page.tt +13 -7
  37. data/lib/generators/automation/templates/partials/element.tt +1 -1
  38. data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -4
  39. data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -8
  40. data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -4
  41. data/lib/generators/automation/templates/partials/url_methods.tt +0 -1
  42. data/lib/generators/automation/templates/partials/visit_method.tt +11 -1
  43. data/lib/generators/automation/templates/pdp.tt +1 -1
  44. data/lib/generators/common_generator.rb +12 -0
  45. data/lib/generators/cucumber/cucumber_generator.rb +36 -0
  46. data/lib/generators/cucumber/templates/accessibility_feature.tt +5 -0
  47. data/lib/generators/cucumber/templates/accessibility_steps.tt +21 -0
  48. data/lib/generators/cucumber/templates/cucumber.tt +8 -1
  49. data/lib/generators/cucumber/templates/env.tt +6 -4
  50. data/lib/generators/cucumber/templates/feature.tt +0 -4
  51. data/lib/generators/cucumber/templates/partials/appium_env.tt +5 -0
  52. data/lib/generators/cucumber/templates/partials/capybara_env.tt +38 -0
  53. data/lib/generators/cucumber/templates/partials/capybara_world.tt +6 -0
  54. data/lib/generators/cucumber/templates/partials/driver_world.tt +1 -4
  55. data/lib/generators/cucumber/templates/partials/mobile_steps.tt +2 -2
  56. data/lib/generators/cucumber/templates/partials/selenium_env.tt +22 -35
  57. data/lib/generators/cucumber/templates/partials/watir_env.tt +20 -1
  58. data/lib/generators/cucumber/templates/partials/web_steps.tt +10 -15
  59. data/lib/generators/cucumber/templates/performance_feature.tt +5 -0
  60. data/lib/generators/cucumber/templates/performance_steps.tt +17 -0
  61. data/lib/generators/cucumber/templates/steps.tt +2 -2
  62. data/lib/generators/cucumber/templates/visual_feature.tt +5 -0
  63. data/lib/generators/cucumber/templates/visual_steps.tt +19 -0
  64. data/lib/generators/cucumber/templates/world.tt +5 -3
  65. data/lib/generators/generator.rb +50 -7
  66. data/lib/generators/helper_generator.rb +39 -9
  67. data/lib/generators/infrastructure/github_generator.rb +6 -0
  68. data/lib/generators/infrastructure/templates/github.tt +12 -8
  69. data/lib/generators/infrastructure/templates/github_appium.tt +108 -0
  70. data/lib/generators/infrastructure/templates/gitlab.tt +6 -3
  71. data/lib/generators/invoke_generators.rb +43 -9
  72. data/lib/generators/menu_generator.rb +122 -11
  73. data/lib/generators/minitest/minitest_generator.rb +35 -0
  74. data/lib/generators/minitest/templates/accessibility_test.tt +26 -0
  75. data/lib/generators/minitest/templates/performance_test.tt +18 -0
  76. data/lib/generators/minitest/templates/test.tt +64 -0
  77. data/lib/generators/minitest/templates/visual_test.tt +23 -0
  78. data/lib/generators/rspec/rspec_generator.rb +16 -4
  79. data/lib/generators/rspec/templates/accessibility_spec.tt +25 -0
  80. data/lib/generators/rspec/templates/performance_spec.tt +18 -0
  81. data/lib/generators/rspec/templates/spec.tt +13 -41
  82. data/lib/generators/rspec/templates/visual_spec.tt +20 -0
  83. data/lib/generators/template_renderer/partial_cache.rb +126 -0
  84. data/lib/generators/template_renderer/partial_resolver.rb +110 -0
  85. data/lib/generators/template_renderer/template_error.rb +50 -0
  86. data/lib/generators/template_renderer.rb +106 -0
  87. data/lib/generators/templates/common/config.tt +2 -2
  88. data/lib/generators/templates/common/gemfile.tt +36 -9
  89. data/lib/generators/templates/common/git_ignore.tt +6 -1
  90. data/lib/generators/templates/common/partials/mobile_config.tt +5 -1
  91. data/lib/generators/templates/common/partials/web_config.tt +17 -8
  92. data/lib/generators/templates/common/rakefile.tt +36 -0
  93. data/lib/generators/templates/common/read_me.tt +43 -91
  94. data/lib/generators/templates/common/rspec.tt +3 -0
  95. data/lib/generators/templates/common/ruby_version.tt +1 -0
  96. data/lib/generators/templates/helpers/allure_helper.tt +13 -2
  97. data/lib/generators/templates/helpers/browser_helper.tt +13 -2
  98. data/lib/generators/templates/helpers/capybara_helper.tt +32 -0
  99. data/lib/generators/templates/helpers/debug_helper.tt +190 -0
  100. data/lib/generators/templates/helpers/driver_helper.tt +3 -11
  101. data/lib/generators/templates/helpers/partials/allure_imports.tt +3 -1
  102. data/lib/generators/templates/helpers/partials/allure_requirements.tt +3 -1
  103. data/lib/generators/templates/helpers/partials/appium_driver.tt +44 -0
  104. data/lib/generators/templates/helpers/partials/browserstack_config.tt +13 -0
  105. data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +7 -0
  106. data/lib/generators/templates/helpers/partials/debug_start.tt +7 -0
  107. data/lib/generators/templates/helpers/partials/driver_and_options.tt +5 -115
  108. data/lib/generators/templates/helpers/partials/quit_driver.tt +3 -1
  109. data/lib/generators/templates/helpers/partials/screenshot.tt +3 -1
  110. data/lib/generators/templates/helpers/partials/selenium_driver.tt +26 -0
  111. data/lib/generators/templates/helpers/partials/video_start.tt +9 -0
  112. data/lib/generators/templates/helpers/partials/video_stop.tt +4 -0
  113. data/lib/generators/templates/helpers/performance_helper.tt +57 -0
  114. data/lib/generators/templates/helpers/spec_helper.tt +72 -10
  115. data/lib/generators/templates/helpers/test_helper.tt +94 -0
  116. data/lib/generators/templates/helpers/video_helper.tt +270 -0
  117. data/lib/generators/templates/helpers/visual_helper.tt +39 -46
  118. data/lib/llm/client.rb +79 -0
  119. data/lib/llm/config.rb +57 -0
  120. data/lib/llm/prompts.rb +84 -0
  121. data/lib/llm/provider.rb +27 -0
  122. data/lib/llm/providers/anthropic_provider.rb +43 -0
  123. data/lib/llm/providers/ollama_provider.rb +56 -0
  124. data/lib/llm/providers/openai_provider.rb +42 -0
  125. data/lib/llm/response_parser.rb +67 -0
  126. data/lib/plugin/plugin.rb +22 -20
  127. data/lib/plugin/plugin_exposer.rb +16 -38
  128. data/lib/ruby_raider.rb +51 -11
  129. data/lib/scaffolding/crud_generator.rb +94 -0
  130. data/lib/scaffolding/dry_run_presenter.rb +16 -0
  131. data/lib/scaffolding/name_normalizer.rb +63 -0
  132. data/lib/scaffolding/page_introspector.rb +45 -0
  133. data/lib/scaffolding/project_detector.rb +72 -0
  134. data/lib/scaffolding/scaffold_menu.rb +103 -0
  135. data/lib/scaffolding/scaffolding.rb +158 -11
  136. data/lib/scaffolding/templates/component.tt +30 -0
  137. data/lib/scaffolding/templates/feature.tt +4 -4
  138. data/lib/scaffolding/templates/helper.tt +15 -1
  139. data/lib/scaffolding/templates/page_from_url.tt +75 -0
  140. data/lib/scaffolding/templates/page_object.tt +50 -1
  141. data/lib/scaffolding/templates/spec.tt +33 -2
  142. data/lib/scaffolding/templates/spec_from_page.tt +31 -0
  143. data/lib/scaffolding/templates/spec_from_url.tt +46 -0
  144. data/lib/scaffolding/templates/steps.tt +17 -5
  145. data/lib/scaffolding/url_analyzer.rb +179 -0
  146. data/lib/utilities/desktop_downloader.rb +177 -0
  147. data/lib/utilities/logo.rb +83 -0
  148. data/lib/utilities/utilities.rb +53 -20
  149. data/lib/version +1 -1
  150. data/ruby_raider.gemspec +1 -0
  151. data/sig/adopter/adopt_menu.rbs +25 -0
  152. data/sig/adopter/converters/base_converter.rbs +23 -0
  153. data/sig/adopter/converters/identity_converter.rbs +16 -0
  154. data/sig/adopter/migration_plan.rbs +34 -0
  155. data/sig/adopter/migrator.rbs +21 -0
  156. data/sig/adopter/plan_builder.rbs +38 -0
  157. data/sig/adopter/project_analyzer.rbs +39 -0
  158. data/sig/adopter/project_detector.rbs +26 -0
  159. data/sig/commands/adopt_commands.rbs +8 -0
  160. data/sig/commands/loaded_commands.rbs +5 -0
  161. data/sig/commands/plugin_commands.rbs +9 -0
  162. data/sig/commands/scaffolding_commands.rbs +28 -0
  163. data/sig/commands/utility_commands.rbs +21 -0
  164. data/sig/generators/automation/automation_generator.rbs +20 -0
  165. data/sig/generators/common_generator.rbs +12 -0
  166. data/sig/generators/cucumber/cucumber_generator.rbs +16 -0
  167. data/sig/generators/generator.rbs +40 -0
  168. data/sig/generators/helper_generator.rbs +18 -0
  169. data/sig/generators/infrastructure/github_generator.rbs +5 -0
  170. data/sig/generators/infrastructure/gitlab_generator.rbs +4 -0
  171. data/sig/generators/invoke_generators.rbs +10 -0
  172. data/sig/generators/menu_generator.rbs +29 -0
  173. data/sig/generators/minitest/minitest_generator.rbs +8 -0
  174. data/sig/generators/rspec/rspec_generator.rbs +8 -0
  175. data/sig/generators/template_renderer/partial_cache.rbs +20 -0
  176. data/sig/generators/template_renderer/partial_resolver.rbs +20 -0
  177. data/sig/generators/template_renderer/template_error.rbs +19 -0
  178. data/sig/generators/template_renderer.rbs +10 -0
  179. data/sig/llm/client.rbs +15 -0
  180. data/sig/llm/config.rbs +20 -0
  181. data/sig/llm/prompts.rbs +8 -0
  182. data/sig/llm/provider.rbs +12 -0
  183. data/sig/llm/providers/anthropic_provider.rbs +16 -0
  184. data/sig/llm/providers/ollama_provider.rbs +18 -0
  185. data/sig/llm/providers/openai_provider.rbs +16 -0
  186. data/sig/llm/response_parser.rbs +13 -0
  187. data/sig/plugin/plugin.rbs +24 -0
  188. data/sig/plugin/plugin_exposer.rbs +20 -0
  189. data/sig/ruby_raider.rbs +15 -0
  190. data/sig/scaffolding/crud_generator.rbs +16 -0
  191. data/sig/scaffolding/dry_run_presenter.rbs +4 -0
  192. data/sig/scaffolding/name_normalizer.rbs +17 -0
  193. data/sig/scaffolding/page_introspector.rbs +14 -0
  194. data/sig/scaffolding/project_detector.rbs +14 -0
  195. data/sig/scaffolding/scaffold_menu.rbs +18 -0
  196. data/sig/scaffolding/scaffolding.rbs +55 -0
  197. data/sig/scaffolding/url_analyzer.rbs +28 -0
  198. data/sig/utilities/desktop_downloader.rbs +23 -0
  199. data/sig/utilities/logger.rbs +13 -0
  200. data/sig/utilities/logo.rbs +16 -0
  201. data/sig/utilities/utilities.rbs +30 -0
  202. data/sig/vendor/thor.rbs +34 -0
  203. data/sig/vendor/tty_prompt.rbs +15 -0
  204. data/spec/adopter/adopt_menu_spec.rb +176 -0
  205. data/spec/adopter/converters/identity_converter_spec.rb +145 -0
  206. data/spec/adopter/migration_plan_spec.rb +113 -0
  207. data/spec/adopter/migrator_spec.rb +277 -0
  208. data/spec/adopter/plan_builder_spec.rb +298 -0
  209. data/spec/adopter/project_analyzer_spec.rb +337 -0
  210. data/spec/adopter/project_detector_spec.rb +295 -0
  211. data/spec/commands/raider_commands_spec.rb +129 -0
  212. data/spec/generators/fixtures/templates/test.tt +1 -0
  213. data/spec/generators/fixtures/templates/test_partial.tt +1 -0
  214. data/spec/generators/generator_spec.rb +23 -0
  215. data/spec/generators/template_renderer_spec.rb +298 -0
  216. data/spec/integration/commands/scaffolding_commands_spec.rb +2 -2
  217. data/spec/integration/commands/utility_commands_spec.rb +24 -4
  218. data/spec/integration/content/ci_content_spec.rb +119 -0
  219. data/spec/integration/content/common_content_spec.rb +288 -0
  220. data/spec/integration/content/config_content_spec.rb +175 -0
  221. data/spec/integration/content/content_helper.rb +32 -0
  222. data/spec/integration/content/gemfile_content_spec.rb +209 -0
  223. data/spec/integration/content/helper_content_spec.rb +485 -0
  224. data/spec/integration/content/page_content_spec.rb +259 -0
  225. data/spec/integration/content/reporter_content_spec.rb +236 -0
  226. data/spec/integration/content/skip_flags_content_spec.rb +206 -0
  227. data/spec/integration/content/syntax_validation_spec.rb +30 -0
  228. data/spec/integration/content/test_content_spec.rb +266 -0
  229. data/spec/integration/end_to_end_features_spec.rb +690 -0
  230. data/spec/integration/end_to_end_spec.rb +361 -0
  231. data/spec/integration/generators/automation_generator_spec.rb +9 -21
  232. data/spec/integration/generators/axe_addon_spec.rb +150 -0
  233. data/spec/integration/generators/common_generator_spec.rb +48 -49
  234. data/spec/integration/generators/config_features_spec.rb +155 -0
  235. data/spec/integration/generators/cucumber_generator_spec.rb +7 -7
  236. data/spec/integration/generators/debug_helper_spec.rb +68 -0
  237. data/spec/integration/generators/github_generator_spec.rb +8 -8
  238. data/spec/integration/generators/gitlab_generator_spec.rb +8 -8
  239. data/spec/integration/generators/helpers_generator_spec.rb +70 -44
  240. data/spec/integration/generators/lighthouse_addon_spec.rb +132 -0
  241. data/spec/integration/generators/minitest_generator_spec.rb +64 -0
  242. data/spec/integration/generators/reporter_spec.rb +159 -0
  243. data/spec/integration/generators/rspec_generator_spec.rb +7 -7
  244. data/spec/integration/generators/skip_flags_spec.rb +134 -0
  245. data/spec/integration/generators/visual_addon_spec.rb +148 -0
  246. data/spec/integration/settings_helper.rb +1 -4
  247. data/spec/integration/spec_helper.rb +46 -11
  248. data/spec/llm/client_spec.rb +79 -0
  249. data/spec/llm/config_spec.rb +92 -0
  250. data/spec/llm/prompts_spec.rb +49 -0
  251. data/spec/llm/response_parser_spec.rb +92 -0
  252. data/spec/menus/adopter_adopt_menu_spec.rb +97 -0
  253. data/spec/menus/menu_generator_spec.rb +263 -0
  254. data/spec/scaffolding/name_normalizer_spec.rb +113 -0
  255. data/spec/scaffolding/page_introspector_spec.rb +82 -0
  256. data/spec/scaffolding/scaffold_project_detector_spec.rb +104 -0
  257. data/spec/scaffolding/scaffolding_features_spec.rb +311 -0
  258. data/spec/scaffolding/url_analyzer_spec.rb +110 -0
  259. data/spec/system/adopt_matrix_spec.rb +537 -0
  260. data/spec/system/adopt_spec.rb +225 -0
  261. data/spec/system/capybara_spec.rb +42 -0
  262. data/spec/system/selenium_spec.rb +19 -17
  263. data/spec/system/support/system_test_helper.rb +33 -0
  264. data/spec/system/watir_spec.rb +19 -17
  265. data/spec/utilities/desktop_downloader_spec.rb +92 -0
  266. metadata +193 -18
  267. data/.github/workflows/push_gem.yml +0 -37
  268. data/.github/workflows/selenium.yml +0 -22
  269. data/.github/workflows/watir.yml +0 -22
  270. data/lib/generators/automation/templates/partials/android_caps.tt +0 -17
  271. data/lib/generators/automation/templates/partials/cross_platform_caps.tt +0 -25
  272. data/lib/generators/automation/templates/partials/ios_caps.tt +0 -18
  273. data/lib/generators/automation/templates/partials/selenium_account.tt +0 -9
  274. data/lib/generators/automation/templates/partials/selenium_login.tt +0 -34
  275. data/lib/generators/automation/templates/partials/watir_account.tt +0 -7
  276. data/lib/generators/automation/templates/partials/watir_login.tt +0 -32
  277. data/lib/generators/automation/templates/visual_options.tt +0 -16
  278. 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 = nil)
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', -> { create_framework(framework, automation, 'github') }
104
- menu.choice :'Gitlab CI/CD', -> { create_framework(framework, automation, 'gitlab') }
105
- 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) }
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? && !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
@@ -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
- <%- 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
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
- it 'no accessibility errors are present on the heading' do
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(<% if watir? -%>browser<% else -%>driver<% end -%>) }
42
- let(:account_page) { Account.new(<% if watir? -%>browser<% else -%>driver<% end -%>) }
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.visit
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
- <%- if visual? -%>
54
- check_page account_page
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
- <%- if visual? -%>
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.go_to_backpack_pdp
90
- expect(pdp_page.add_to_cart_text).to eq 'Add To Cart'
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