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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ Given('I am on the home page') do
4
+ <% if capybara? -%>
5
+ visit '/'
6
+ <% elsif watir? -%>
7
+ browser.goto 'https://raider-test-site.onrender.com/'
8
+ <% else -%>
9
+ driver.get 'https://raider-test-site.onrender.com/'
10
+ <% end -%>
11
+ end
12
+
13
+ Then('the page should be accessible') do
14
+ <% if capybara? -%>
15
+ expect(page).to be_axe_clean
16
+ <% elsif watir? -%>
17
+ expect(browser.driver).to be_axe_clean
18
+ <% else -%>
19
+ expect(driver).to be_axe_clean
20
+ <% end -%>
21
+ end
@@ -1 +1,8 @@
1
- default: --format AllureCucumber::CucumberFormatter --out allure-results --publish-quiet
1
+ <%- if allure_reporter? -%>
2
+ default: --format AllureCucumber::CucumberFormatter --out allure-results --publish-quiet --retry 0
3
+ <%- else -%>
4
+ default: --publish-quiet --retry 0
5
+ <%- end -%>
6
+ <%- if json_reporter? -%>
7
+ json: --format json --out results/results.json --publish-quiet
8
+ <%- end -%>
@@ -4,11 +4,7 @@ Feature: Login Page
4
4
  Scenario: A user can login
5
5
  Given I'm a registered user on the login page
6
6
  When I login with my credentials
7
- <%- if axe? -%>
8
- Then the page should be axe clean
9
- <%- else -%>
10
7
  Then I'm logged in
11
- <% end -%>
12
8
 
13
9
  <%- else -%>
14
10
  Feature: Home Page
@@ -1,12 +1,17 @@
1
1
  require 'tmpdir'
2
2
  require_relative '../../helpers/allure_helper'
3
+ require_relative '../../helpers/video_helper'
3
4
 
4
5
  Before do
5
6
  AllureHelper.configure
6
7
  driver.start_driver
8
+ @video_recorder = VideoHelper.recorder_for(driver)
9
+ @video_recorder&.start(self.class.to_s)
7
10
  end
8
11
 
9
12
  After do |scenario|
13
+ video_file = @video_recorder&.stop
14
+ AllureHelper.add_video(scenario.name, video_file)
10
15
  Dir.mktmpdir do |temp_folder|
11
16
  screenshot = driver.screenshot("#{temp_folder}/#{scenario.name}.png")
12
17
  AllureHelper.add_screenshot(scenario.name, screenshot)
@@ -4,14 +4,32 @@ require 'rspec'
4
4
  require 'tmpdir'
5
5
  require_relative '../../helpers/allure_helper'
6
6
  require_relative '../../helpers/capybara_helper'
7
+ require_relative '../../helpers/video_helper'
8
+ require_relative '../../helpers/debug_helper'
7
9
 
8
10
  CapybaraHelper.configure
9
11
 
10
12
  Before do
11
- Capybara.current_session.driver.browser.manage.window.maximize
13
+ viewport = YAML.load_file('config/config.yml')['viewport']
14
+ if viewport
15
+ Capybara.current_session.driver.browser.manage.window.resize_to(viewport['width'], viewport['height'])
16
+ else
17
+ Capybara.current_session.driver.browser.manage.window.maximize
18
+ end
19
+ @video_recorder = VideoHelper.recorder_for(Capybara.current_session.driver)
20
+ @video_recorder&.start(self.class.to_s)
21
+ DebugHelper.enable_network_logging(Capybara.current_session.driver)
22
+ @debug_action_logger = DebugHelper.action_logger_for(self.class.to_s)
12
23
  end
13
24
 
14
25
  After do |scenario|
26
+ if scenario.failed?
27
+ DebugHelper.capture_failure_diagnostics(Capybara.current_session.driver, scenario.name, exception: scenario.exception)
28
+ DebugHelper.capture_network_logs(DebugHelper.resolve_selenium_driver(Capybara.current_session.driver), DebugHelper.sanitize(scenario.name))
29
+ end
30
+ @debug_action_logger&.close
31
+ video_file = @video_recorder&.stop
32
+ AllureHelper.add_video(scenario.name, video_file)
15
33
  Dir.mktmpdir do |temp_folder|
16
34
  screenshot = Capybara.page.save_screenshot("#{temp_folder}/#{scenario.name}.png")
17
35
  AllureHelper.add_screenshot(scenario.name, screenshot)
@@ -1,11 +1,8 @@
1
1
  require_relative '../../helpers/driver_helper'
2
- <%- if visual? -%>
3
- require_relative '../../helpers/visual_helper'
4
- <%- end -%>
5
2
  <%- if selenium_based? -%>
6
3
  require_relative '../../models/user_factory'
7
4
 
8
- World(DriverHelper<%- if visual? -%>, VisualHelper<%- end -%>)
5
+ World(DriverHelper)
9
6
  <%- else -%>
10
7
 
11
8
  World(DriverHelper)
@@ -1,52 +1,39 @@
1
1
  # frozen_string_literal: true
2
- <%- if visual? -%>
3
2
 
4
- require 'tmpdir'
5
- require_relative '../../helpers/allure_helper'
6
- require_relative '../../helpers/driver_helper'
7
- require_relative '../../helpers/visual_helper'
8
-
9
- include DriverHelper
10
- include VisualHelper
11
-
12
- Before do
13
- OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
14
- @grid_runner = VisualHelper.create_grid_runner
15
- @eyes = VisualHelper.create_eyes(@grid_runner)
16
- VisualHelper.configure_eyes @eyes
17
- @driver = @eyes.open(driver: driver)
18
- end
19
-
20
- After do |scenario|
21
- Dir.mktmpdir do |temp_folder|
22
- screenshot = driver.save_screenshot("#{temp_folder}/#{scenario.name}.png")
23
- AllureHelper.add_screenshot(scenario.name, screenshot)
24
- end
25
- @eyes.close
26
- @driver.quit
27
- @eyes.abort_async
28
- results = @grid_runner.get_all_test_results
29
- puts results
30
- end
31
-
32
- <%- else -%>
33
-
34
- <%- if axe? && cucumber? -%>
3
+ <%- if axe_addon? && cucumber? -%>
35
4
  require 'axe-rspec'
36
5
  <%- end -%>
37
6
  require 'rspec'
38
7
  require 'tmpdir'
8
+ require 'yaml'
39
9
  require_relative '../../helpers/allure_helper'
10
+ require_relative '../../helpers/video_helper'
11
+ require_relative '../../helpers/debug_helper'
40
12
 
41
13
  Before do
42
- driver.manage.window.maximize
14
+ viewport = YAML.load_file('config/config.yml')['viewport']
15
+ if viewport
16
+ driver.manage.window.resize_to(viewport['width'], viewport['height'])
17
+ else
18
+ driver.manage.window.maximize
19
+ end
20
+ @video_recorder = VideoHelper.recorder_for(driver)
21
+ @video_recorder&.start(self.class.to_s)
22
+ DebugHelper.enable_network_logging(driver)
23
+ @debug_action_logger = DebugHelper.action_logger_for(self.class.to_s)
43
24
  end
44
25
 
45
26
  After do |scenario|
27
+ if scenario.failed?
28
+ DebugHelper.capture_failure_diagnostics(driver, scenario.name, exception: scenario.exception)
29
+ DebugHelper.capture_network_logs(DebugHelper.resolve_selenium_driver(driver), DebugHelper.sanitize(scenario.name))
30
+ end
31
+ @debug_action_logger&.close
32
+ video_file = @video_recorder&.stop
33
+ AllureHelper.add_video(scenario.name, video_file)
46
34
  Dir.mktmpdir do |temp_folder|
47
35
  screenshot = driver.save_screenshot("#{temp_folder}/#{scenario.name}.png")
48
36
  AllureHelper.add_screenshot(scenario.name, screenshot)
49
37
  end
50
38
  driver.quit
51
- end
52
- <%- end -%>
39
+ end
@@ -1,13 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tmpdir'
4
+ require 'yaml'
4
5
  require_relative '../../helpers/allure_helper'
6
+ require_relative '../../helpers/video_helper'
7
+ require_relative '../../helpers/debug_helper'
5
8
 
6
9
  Before do
7
- browser.window.maximize
10
+ viewport = YAML.load_file('config/config.yml')['viewport']
11
+ if viewport
12
+ browser.window.resize_to(viewport['width'], viewport['height'])
13
+ else
14
+ browser.window.maximize
15
+ end
16
+ @video_recorder = VideoHelper.recorder_for(browser)
17
+ @video_recorder&.start(self.class.to_s)
18
+ DebugHelper.enable_network_logging(browser)
19
+ @debug_action_logger = DebugHelper.action_logger_for(self.class.to_s)
8
20
  end
9
21
 
10
22
  After do |scenario|
23
+ if scenario.failed?
24
+ DebugHelper.capture_failure_diagnostics(browser, scenario.name, exception: scenario.exception)
25
+ DebugHelper.capture_network_logs(DebugHelper.resolve_selenium_driver(browser), DebugHelper.sanitize(scenario.name))
26
+ end
27
+ @debug_action_logger&.close
28
+ video_file = @video_recorder&.stop
29
+ AllureHelper.add_video(scenario.name, video_file)
11
30
  Dir.mktmpdir do |temp_folder|
12
31
  screenshot = browser.screenshot.save("#{temp_folder}/#{scenario.name}.png")
13
32
  AllureHelper.add_screenshot(scenario.name, screenshot)
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Uncomment for interactive debugging:
4
+ # require 'debug' # Ruby's built-in debugger (binding.break)
5
+ # require 'pry' # Pry debugger (binding.pry)
6
+
3
7
  require_relative '../../page_objects/pages/account'
4
8
  require_relative '../../page_objects/pages/login'
5
9
 
@@ -10,22 +14,12 @@ Given("I'm a {user} on the login page") do |user|
10
14
  end
11
15
 
12
16
  When('I login with my credentials') do
17
+ # binding.break # Uncomment to pause here and inspect state
13
18
  @login_page.login(@user['username'], @user['password'])
14
19
  end
15
20
 
16
- <%- if axe? -%>
17
- Then('the page should be axe clean') do
18
- account = Account.new(driver)
19
- expect(account.page).to be_axe_clean
20
- end
21
- <%- else -%>
22
21
  Then("I'm logged in") do
23
22
  account_page = <% if capybara? %>Account.new<% elsif watir? %>Account.new(browser)<% else %>Account.new(driver)<% end %>
24
- <%- if visual? -%>
25
- check_page account_page
26
- <%- else -%>
27
23
  account_page.<% if capybara? %>visit_page<% else %>visit<% end %>
28
24
  expect(account_page.header.customer_name).to eq "Welcome back #{@user['name']}"
29
- <%- end -%>
30
- end
31
- <%- end -%>
25
+ end
@@ -0,0 +1,5 @@
1
+ Feature: Performance Audit
2
+
3
+ Scenario: Page meets performance threshold
4
+ Given I audit the home page performance
5
+ Then the performance score should be above the threshold
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../helpers/performance_helper'
4
+
5
+ include PerformanceHelper
6
+
7
+ Given('I audit the home page performance') do
8
+ @result = assert_performance_above('https://raider-test-site.onrender.com/', threshold: 0.7)
9
+ end
10
+
11
+ Then('the performance score should be above the threshold') do
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
@@ -0,0 +1,5 @@
1
+ Feature: Visual Regression
2
+
3
+ Scenario: Login page matches baseline
4
+ Given I'm on the login page
5
+ Then the page should match the visual baseline "login_page"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../helpers/visual_helper'
4
+ require_relative '../../page_objects/pages/login'
5
+
6
+ include VisualHelper
7
+
8
+ Given("I'm on the login page") do
9
+ @login_page = <% if capybara? %>Login.new<% elsif watir? %>Login.new(browser)<% else %>Login.new(driver)<% end %>
10
+ @login_page.<% if capybara? %>visit_page<% else %>visit<% end %>
11
+ end
12
+
13
+ Then('the page should match the visual baseline {string}') do |baseline_name|
14
+ screenshot_path = <% if capybara? %>page.save_screenshot("tmp/visual_#{baseline_name}.png")<% elsif watir? %>browser.screenshot.save("tmp/visual_#{baseline_name}.png")<% else %>driver.save_screenshot("tmp/visual_#{baseline_name}.png")<% end %>
15
+ result = compare_screenshot(baseline_name, 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
@@ -3,11 +3,12 @@
3
3
  require 'thor'
4
4
  require_relative 'template_renderer'
5
5
 
6
- # :reek:TooManyMethods { enabled: false }
7
6
  class Generator < Thor::Group
8
7
  include Thor::Actions
9
8
  include TemplateRenderer
10
9
 
10
+ LATEST_RUBY = '3.4'
11
+
11
12
  argument :automation
12
13
  argument :framework
13
14
  argument :name
@@ -64,8 +65,8 @@ class Generator < Thor::Group
64
65
  args.include?('capybara')
65
66
  end
66
67
 
67
- def visual?
68
- args.include?('applitools')
68
+ def visual_addon?
69
+ args.include?('visual_addon')
69
70
  end
70
71
 
71
72
  def watir?
@@ -73,15 +74,45 @@ class Generator < Thor::Group
73
74
  end
74
75
 
75
76
  def web?
76
- (args & %w[selenium watir axe applitools capybara]).count.positive?
77
+ (args & %w[selenium watir capybara]).count.positive?
78
+ end
79
+
80
+ def axe_addon?
81
+ args.include?('axe_addon')
77
82
  end
78
83
 
79
- def axe?
80
- args.include?('axe')
84
+ def lighthouse_addon?
85
+ args.include?('lighthouse_addon')
81
86
  end
82
87
 
83
88
  def selenium_based?
84
- (args & %w[selenium axe applitools]).count.positive?
89
+ args.include?('selenium')
90
+ end
91
+
92
+ def skip_allure?
93
+ args.include?('skip_allure')
94
+ end
95
+
96
+ def skip_video?
97
+ args.include?('skip_video')
98
+ end
99
+
100
+ def allure_reporter?
101
+ has_reporter = args.any? { |a| a&.start_with?('reporter_') }
102
+ has_reporter ? args.include?('reporter_allure') : !skip_allure?
103
+ end
104
+
105
+ def junit_reporter?
106
+ args.include?('reporter_junit')
107
+ end
108
+
109
+ def json_reporter?
110
+ args.include?('reporter_json')
111
+ end
112
+
113
+ def ruby_version
114
+ arg = args.find { |a| a&.start_with?('ruby_version:') }
115
+ arg ? arg.split(':', 2).last : LATEST_RUBY
85
116
  end
86
117
 
87
118
  private
@@ -9,11 +9,12 @@ class HelpersGenerator < Generator
9
9
  generate_capybara_helper
10
10
  generate_appium_helper
11
11
  generate_allure_helper
12
+ generate_video_helper
13
+ generate_debug_helper
14
+ generate_visual_helper
15
+ generate_performance_helper
12
16
 
13
- if visual?
14
- generate_visual_helper
15
- generate_visual_spec_helper
16
- elsif minitest?
17
+ if minitest?
17
18
  generate_test_helper
18
19
  else
19
20
  generate_spec_helper
@@ -23,9 +24,17 @@ class HelpersGenerator < Generator
23
24
  private
24
25
 
25
26
  def generate_allure_helper
27
+ return unless allure_reporter?
28
+
26
29
  template('helpers/allure_helper.tt', "#{name}/helpers/allure_helper.rb")
27
30
  end
28
31
 
32
+ def generate_video_helper
33
+ return if skip_video?
34
+
35
+ template('helpers/video_helper.tt', "#{name}/helpers/video_helper.rb")
36
+ end
37
+
29
38
  def generate_browser_helper
30
39
  return if selenium_based? || mobile? || capybara?
31
40
 
@@ -60,13 +69,21 @@ class HelpersGenerator < Generator
60
69
  template('helpers/test_helper.tt', "#{name}/helpers/test_helper.rb")
61
70
  end
62
71
 
72
+ def generate_debug_helper
73
+ return unless web?
74
+
75
+ template('helpers/debug_helper.tt', "#{name}/helpers/debug_helper.rb")
76
+ end
77
+
63
78
  def generate_visual_helper
79
+ return unless visual_addon? && web?
80
+
64
81
  template('helpers/visual_helper.tt', "#{name}/helpers/visual_helper.rb")
65
82
  end
66
83
 
67
- def generate_visual_spec_helper
68
- return if cucumber?
84
+ def generate_performance_helper
85
+ return unless lighthouse_addon? && web?
69
86
 
70
- template('helpers/visual_spec_helper.tt', "#{name}/helpers/spec_helper.rb")
87
+ template('helpers/performance_helper.tt', "#{name}/helpers/performance_helper.rb")
71
88
  end
72
89
  end
@@ -22,7 +22,7 @@ jobs:
22
22
  - name: Set up Ruby
23
23
  uses: ruby/setup-ruby@v1
24
24
  with:
25
- ruby-version: 3.4.0
25
+ ruby-version: <%= ruby_version %>.0
26
26
  bundler-cache: true
27
27
 
28
28
  - name: Set up Chrome
@@ -14,7 +14,7 @@ jobs:
14
14
  - uses: actions/checkout@v4
15
15
  - uses: ruby/setup-ruby@v1
16
16
  with:
17
- ruby-version: 3.4.0
17
+ ruby-version: <%= ruby_version %>.0
18
18
  bundler-cache: true
19
19
 
20
20
  - name: Download iOS app
@@ -63,7 +63,7 @@ jobs:
63
63
  - uses: actions/checkout@v4
64
64
  - uses: ruby/setup-ruby@v1
65
65
  with:
66
- ruby-version: 3.4.0
66
+ ruby-version: <%= ruby_version %>.0
67
67
  bundler-cache: true
68
68
 
69
69
  - name: Download Android app
@@ -4,7 +4,7 @@ stages:
4
4
  - report
5
5
 
6
6
  variables:
7
- RUBY_VERSION: "3.4.0"
7
+ RUBY_VERSION: "<%= ruby_version %>.0"
8
8
  HEADLESS: "true"
9
9
 
10
10
  setup_ruby:
@@ -7,8 +7,6 @@ require_relative 'helper_generator'
7
7
  require_relative 'minitest/minitest_generator'
8
8
  require_relative 'rspec/rspec_generator'
9
9
 
10
- # :reek:FeatureEnvy { enabled: false }
11
- # :reek:UtilityFunction { enabled: false }
12
10
  module InvokeGenerators
13
11
  module_function
14
12
 
@@ -16,14 +14,16 @@ module InvokeGenerators
16
14
  generators = %w[Automation Common Helpers]
17
15
  framework = structure[:framework]
18
16
  add_generator(generators, framework.capitalize)
19
- add_generator(generators, structure[:ci_platform].capitalize) if structure[:ci_platform]
17
+ add_generator(generators, structure[:ci_platform].capitalize) if !structure[:skip_ci] && (structure[:ci_platform])
18
+ extra_args = collect_skip_flags(structure)
20
19
  generators.each do |generator|
21
20
  invoke_generator({
22
21
  automation: structure[:automation],
23
22
  framework:,
24
23
  generator:,
25
24
  ci_platform: structure[:ci_platform],
26
- name: structure[:name]
25
+ name: structure[:name],
26
+ extra_args:
27
27
  })
28
28
  end
29
29
  end
@@ -32,12 +32,45 @@ module InvokeGenerators
32
32
  gens.each { |generator| generators.push generator }
33
33
  end
34
34
 
35
+ # Generator class lookup cache — avoids repeated Object.const_get string interpolation
36
+ GENERATOR_CLASSES = Hash.new { |h, k| h[k] = Object.const_get("#{k}Generator") }
37
+
35
38
  def invoke_generator(structure = {})
36
- Object.const_get("#{structure[:generator]}Generator")
37
- .new([structure[:automation],
38
- structure[:framework],
39
- structure[:name],
40
- structure[:ci_platform]]).invoke_all
39
+ args = [structure[:automation], structure[:framework], structure[:name], structure[:ci_platform]]
40
+ args.concat(structure[:extra_args] || [])
41
+ generator_class = GENERATOR_CLASSES[structure[:generator]]
42
+ generator = generator_class.new(args)
43
+ # Enable batch mode on the template cache to skip mtime checks during generation
44
+ generator.class.template_renderer.batch_mode = true if generator.class.respond_to?(:template_renderer)
45
+ generator.invoke_all
46
+ end
47
+
48
+ def collect_skip_flags(structure)
49
+ flags = []
50
+ flags << 'skip_allure' if structure[:skip_allure]
51
+ flags << 'skip_video' if structure[:skip_video]
52
+ flags << 'axe_addon' if structure[:accessibility] && !mobile_automation?(structure[:automation])
53
+ flags << 'visual_addon' if structure[:visual] && !mobile_automation?(structure[:automation])
54
+ flags << 'lighthouse_addon' if structure[:performance] && !mobile_automation?(structure[:automation])
55
+ flags << "ruby_version:#{structure[:ruby_version]}" if structure[:ruby_version]
56
+ flags.concat(reporter_flags(structure[:reporter]))
57
+ flags
58
+ end
59
+
60
+ def reporter_flags(reporter)
61
+ case reporter
62
+ when 'allure' then ['reporter_allure']
63
+ when 'junit' then ['reporter_junit']
64
+ when 'json' then ['reporter_json']
65
+ when 'both' then %w[reporter_allure reporter_junit]
66
+ when 'all' then %w[reporter_allure reporter_junit reporter_json]
67
+ when 'none' then ['reporter_none']
68
+ else []
69
+ end
70
+ end
71
+
72
+ def mobile_automation?(automation)
73
+ %w[ios android cross_platform].include?(automation)
41
74
  end
42
75
 
43
76
  def to_bool(string)