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
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Adopter
4
+ module ProjectDetector
5
+ GEM_AUTOMATION_MAP = {
6
+ 'site_prism' => 'capybara',
7
+ 'capybara' => 'capybara',
8
+ 'selenium-webdriver' => 'selenium',
9
+ 'watir' => 'watir',
10
+ 'appium_lib' => 'appium',
11
+ 'eyes_selenium' => 'selenium',
12
+ 'axe-core-selenium' => 'selenium',
13
+ 'axe-core-rspec' => 'selenium'
14
+ }.freeze
15
+
16
+ GEM_FRAMEWORK_MAP = {
17
+ 'cucumber' => 'cucumber',
18
+ 'rspec' => 'rspec',
19
+ 'minitest' => 'minitest'
20
+ }.freeze
21
+
22
+ BROWSERS = %w[chrome firefox safari edge].freeze
23
+
24
+ module_function
25
+
26
+ def detect(path = '.')
27
+ {
28
+ automation: detect_automation(path),
29
+ framework: detect_framework(path),
30
+ page_path: detect_page_path(path),
31
+ spec_path: detect_spec_path(path),
32
+ feature_path: detect_feature_path(path),
33
+ helper_path: detect_helper_path(path),
34
+ browser: detect_browser(path),
35
+ url: detect_url(path),
36
+ ci_platform: detect_ci_platform(path)
37
+ }.compact
38
+ end
39
+
40
+ def detect_automation(path)
41
+ gems = parse_gemfile(path)
42
+ GEM_AUTOMATION_MAP.each do |gem_name, automation|
43
+ return automation if gems.include?(gem_name)
44
+ end
45
+ detect_automation_from_requires(path)
46
+ end
47
+
48
+ def detect_framework(path)
49
+ gems = parse_gemfile(path)
50
+ GEM_FRAMEWORK_MAP.each do |gem_name, framework|
51
+ return framework if gems.include?(gem_name)
52
+ end
53
+ return 'rspec' if Dir.exist?(File.join(path, 'spec'))
54
+ return 'cucumber' if Dir.exist?(File.join(path, 'features'))
55
+ return 'minitest' if Dir.exist?(File.join(path, 'test'))
56
+
57
+ nil
58
+ end
59
+
60
+ def detect_page_path(path)
61
+ candidates = %w[page_objects/pages page_objects pages page]
62
+ find_existing_dir(path, candidates)
63
+ end
64
+
65
+ def detect_spec_path(path)
66
+ candidates = %w[spec spec/features spec/tests test tests]
67
+ find_existing_dir(path, candidates)
68
+ end
69
+
70
+ def detect_feature_path(path)
71
+ candidates = %w[features features/scenarios]
72
+ find_existing_dir(path, candidates)
73
+ end
74
+
75
+ def detect_helper_path(path)
76
+ candidates = %w[helpers support spec/support features/support]
77
+ find_existing_dir(path, candidates)
78
+ end
79
+
80
+ def detect_browser(path)
81
+ config_files = helper_and_config_files(path)
82
+ config_files.each do |file|
83
+ next unless File.exist?(file)
84
+
85
+ content = File.read(file)
86
+ BROWSERS.each do |browser|
87
+ return browser if content.match?(/(?:browser|driver)\s*[:=]\s*[:'"]?#{browser}\b/i)
88
+ end
89
+ end
90
+ nil
91
+ end
92
+
93
+ def detect_url(path)
94
+ config_files = helper_and_config_files(path)
95
+ config_files.each do |file|
96
+ next unless File.exist?(file)
97
+
98
+ content = File.read(file)
99
+ match = content.match(%r{(?:base_url|url|app_host)\s*[:=]\s*['"]?(https?://[^\s'"]+)})
100
+ return match[1] if match
101
+ end
102
+ nil
103
+ end
104
+
105
+ def detect_ci_platform(path)
106
+ return 'github' if Dir.exist?(File.join(path, '.github', 'workflows'))
107
+ return 'gitlab' if File.exist?(File.join(path, '.gitlab-ci.yml'))
108
+
109
+ nil
110
+ end
111
+
112
+ def parse_gemfile(path)
113
+ gemfile = File.join(path, 'Gemfile')
114
+ return [] unless File.exist?(gemfile)
115
+
116
+ File.readlines(gemfile).filter_map do |line|
117
+ match = line.match(/^\s*gem\s+['"]([^'"]+)['"]/)
118
+ match[1] if match
119
+ end
120
+ end
121
+
122
+ def detect_automation_from_requires(path)
123
+ ruby_files = Dir.glob(File.join(path, '**', '*.rb'))
124
+ ruby_files.first(50).each do |file|
125
+ content = File.read(file)
126
+ return 'capybara' if content.include?("require 'capybara'") || content.include?("require 'site_prism'")
127
+ return 'selenium' if content.include?("require 'selenium-webdriver'")
128
+ return 'watir' if content.include?("require 'watir'")
129
+ return 'appium' if content.include?("require 'appium_lib'")
130
+ end
131
+ nil
132
+ end
133
+
134
+ def find_existing_dir(path, candidates)
135
+ candidates.each do |candidate|
136
+ return candidate if Dir.exist?(File.join(path, candidate))
137
+ end
138
+ nil
139
+ end
140
+
141
+ def helper_and_config_files(path)
142
+ explicit = [
143
+ File.join(path, 'config', 'config.yml'),
144
+ File.join(path, 'spec', 'spec_helper.rb'),
145
+ File.join(path, 'test', 'test_helper.rb'),
146
+ File.join(path, 'features', 'support', 'env.rb'),
147
+ File.join(path, 'support', 'env.rb'),
148
+ File.join(path, '.env')
149
+ ]
150
+ helpers = Dir.glob(File.join(path, '**', '*helper*.rb')).first(10)
151
+ explicit + helpers
152
+ end
153
+
154
+ private_class_method :detect_automation_from_requires, :find_existing_dir,
155
+ :helper_and_config_files, :parse_gemfile
156
+ end
157
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require_relative '../adopter/adopt_menu'
5
+
6
+ class AdoptCommands < Thor
7
+ desc 'project [SOURCE_PATH]', 'Adopts an existing test project into Ruby Raider conventions'
8
+ option :parameters,
9
+ type: :hash, required: false,
10
+ desc: 'Parameters to bypass the menu (output_path, target_automation, target_framework, ci_platform)',
11
+ aliases: 'p'
12
+
13
+ def project(source_path)
14
+ params = options[:parameters]
15
+ if params
16
+ parsed = params.transform_keys(&:to_sym)
17
+ parsed[:source_path] = source_path
18
+ result = Adopter::AdoptMenu.adopt(parsed)
19
+ print_programmatic_results(result)
20
+ else
21
+ Adopter::AdoptMenu.new.run
22
+ end
23
+ rescue Adopter::MobileProjectError => e
24
+ puts "Error: #{e.message}"
25
+ exit 1
26
+ rescue ArgumentError => e
27
+ puts "Invalid parameters: #{e.message}"
28
+ exit 1
29
+ end
30
+
31
+ private
32
+
33
+ def print_programmatic_results(result)
34
+ plan = result[:plan]
35
+ results = result[:results]
36
+ puts "Adoption complete: #{results[:pages]} pages, #{results[:tests]} tests, " \
37
+ "#{results[:features]} features, #{results[:steps]} steps"
38
+ puts "Warnings: #{plan.warnings.join('; ')}" unless plan.warnings.empty?
39
+ puts "Errors: #{results[:errors].join('; ')}" unless results[:errors].empty?
40
+ puts "Output: #{plan.output_path}"
41
+ end
42
+ end
@@ -4,8 +4,6 @@ require 'thor'
4
4
  require_relative '../plugin/plugin'
5
5
 
6
6
  module RubyRaider
7
- # :reek:FeatureEnvy { enabled: false }
8
- # :reek:UtilityFunction { enabled: false }
9
7
  class PluginCommands < Thor
10
8
  desc 'add [NAME]', 'Adds a plugin to your project'
11
9
 
@@ -1,86 +1,166 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
+ require 'fileutils'
4
5
  require_relative '../generators/menu_generator'
5
6
  require_relative '../scaffolding/scaffolding'
7
+ require_relative '../scaffolding/name_normalizer'
8
+ require_relative '../scaffolding/dry_run_presenter'
9
+ require_relative '../scaffolding/project_detector'
6
10
  require_relative '../commands/utility_commands'
7
11
 
8
- # :reek:FeatureEnvy { enabled: false }
9
- # :reek:UtilityFunction { enabled: false }
10
- # :reek:RepeatedConditional { enabled: false }
11
12
  class ScaffoldingCommands < Thor
13
+ class_option :dry_run, type: :boolean, default: false,
14
+ desc: 'Preview files without creating them', banner: ''
15
+
12
16
  desc 'page [PAGE_NAME]', 'Creates a new page object'
13
- option :path,
14
- type: :string, required: false, desc: 'The path where your page will be created', aliases: '-p'
15
- option :delete,
16
- type: :boolean, required: false, desc: 'This will delete the selected page', aliases: '-d'
17
+ option :path, type: :string, required: false,
18
+ desc: 'The path where your page will be created', aliases: '-p'
19
+ option :delete, type: :boolean, required: false,
20
+ desc: 'This will delete the selected page', aliases: '-d'
21
+ option :uses, type: :array, required: false,
22
+ desc: 'Dependent pages to require', aliases: '-u'
17
23
 
18
24
  def page(name)
19
25
  return delete_scaffolding(name, 'page') if options[:delete]
26
+ return dry_run_preview(name, 'page') if options[:dry_run]
20
27
 
21
- generate_scaffolding(name, 'page', options[:path])
28
+ generate_scaffolding(name, 'page', options[:path], uses: options[:uses])
22
29
  end
23
30
 
24
31
  desc 'feature [NAME]', 'Creates a new feature'
25
- option :path,
26
- type: :string,
27
- required: false, desc: 'The path where your feature will be created', aliases: '-p'
28
- option :delete,
29
- type: :boolean,
30
- required: false, desc: 'This will delete the selected feature', aliases: '-d'
32
+ option :path, type: :string, required: false,
33
+ desc: 'The path where your feature will be created', aliases: '-p'
34
+ option :delete, type: :boolean, required: false,
35
+ desc: 'This will delete the selected feature', aliases: '-d'
31
36
 
32
37
  def feature(name)
33
38
  return delete_scaffolding(name, 'feature') if options[:delete]
39
+ return dry_run_preview(name, 'feature') if options[:dry_run]
34
40
 
35
41
  generate_scaffolding(name, 'feature', options[:path])
36
42
  end
37
43
 
38
44
  desc 'spec [SPEC_NAME]', 'Creates a new spec'
39
- option :path,
40
- type: :string, required: false, desc: 'The path where your spec will be created', aliases: '-p'
41
- option :delete,
42
- type: :boolean, required: false, desc: 'This will delete the selected spec', aliases: '-d'
45
+ option :path, type: :string, required: false,
46
+ desc: 'The path where your spec will be created', aliases: '-p'
47
+ option :delete, type: :boolean, required: false,
48
+ desc: 'This will delete the selected spec', aliases: '-d'
49
+ option :from, type: :string, required: false,
50
+ desc: 'Generate spec stubs from an existing page object file', aliases: '-f'
51
+ option :uses, type: :array, required: false,
52
+ desc: 'Dependent pages to require', aliases: '-u'
53
+ option :ai, type: :boolean, default: false,
54
+ desc: 'Use LLM to generate meaningful test scenarios'
43
55
 
44
56
  def spec(name)
45
57
  return delete_scaffolding(name, 'spec') if options[:delete]
58
+ return dry_run_preview(name, 'spec') if options[:dry_run]
59
+ return generate_spec_from_page(name, options[:from], ai: options[:ai]) if options[:from]
46
60
 
47
- generate_scaffolding(name, 'spec', options[:path])
61
+ generate_scaffolding(name, 'spec', options[:path], uses: options[:uses])
48
62
  end
49
63
 
50
64
  desc 'helper [HELPER_NAME]', 'Creates a new helper'
51
- option :path,
52
- type: :string, required: false, desc: 'The path where your helper will be created', aliases: '-p'
53
- option :delete,
54
- type: :boolean, required: false, desc: 'This will delete the selected helper', aliases: '-d'
65
+ option :path, type: :string, required: false,
66
+ desc: 'The path where your helper will be created', aliases: '-p'
67
+ option :delete, type: :boolean, required: false,
68
+ desc: 'This will delete the selected helper', aliases: '-d'
55
69
 
56
70
  def helper(name)
57
71
  return delete_scaffolding(name, 'helper') if options[:delete]
72
+ return dry_run_preview(name, 'helper') if options[:dry_run]
58
73
 
59
74
  generate_scaffolding(name, 'helper', options[:path])
60
75
  end
61
76
 
62
77
  desc 'steps [STEPS_NAME]', 'Creates a new steps definition'
63
- option :path,
64
- type: :string, required: false, desc: 'The path where your steps will be created', aliases: '-p'
65
- option :delete,
66
- type: :boolean, required: false, desc: 'This will delete the selected steps', aliases: '-d'
78
+ option :path, type: :string, required: false,
79
+ desc: 'The path where your steps will be created', aliases: '-p'
80
+ option :delete, type: :boolean, required: false,
81
+ desc: 'This will delete the selected steps', aliases: '-d'
67
82
 
68
83
  def steps(name)
69
84
  return delete_scaffolding(name, 'steps') if options[:delete]
85
+ return dry_run_preview(name, 'steps') if options[:dry_run]
70
86
 
71
87
  generate_scaffolding(name, 'steps', options[:path])
72
88
  end
73
89
 
74
- desc 'scaffold [SCAFFOLD_NAME]', 'It generates everything needed to start automating'
90
+ desc 'component [NAME]', 'Creates a component inheriting from Component'
91
+ option :path, type: :string, required: false,
92
+ desc: 'The path where your component will be created', aliases: '-p'
93
+ option :delete, type: :boolean, required: false,
94
+ desc: 'This will delete the selected component', aliases: '-d'
95
+
96
+ def component(name)
97
+ return delete_scaffolding(name, 'component') if options[:delete]
98
+ return dry_run_preview(name, 'component') if options[:dry_run]
99
+
100
+ generate_scaffolding(name, 'component', options[:path])
101
+ end
102
+
103
+ desc 'scaffold [NAMES...]', 'Generates pages, specs/features, and helpers for one or more names'
104
+ option :with, type: :array, required: false,
105
+ desc: 'Components to generate (page,spec,feature,steps,helper,component,model)', aliases: '-w'
106
+ option :crud, type: :boolean, required: false,
107
+ desc: 'Generate CRUD pages (list, create, detail, edit) + tests + model'
108
+ option :uses, type: :array, required: false,
109
+ desc: 'Dependent pages to require', aliases: '-u'
110
+
111
+ def scaffold(*names)
112
+ return interactive_scaffold if names.empty?
113
+
114
+ names.each do |name|
115
+ if options[:crud]
116
+ generate_crud(name)
117
+ elsif options[:with]
118
+ generate_selected_components(name, options[:with])
119
+ else
120
+ generate_default_scaffold(name)
121
+ end
122
+ end
123
+ end
124
+
125
+ desc 'destroy [NAMES...]', 'Removes all scaffolded files for the given names (page, spec/feature, steps)'
126
+ option :with, type: :array, required: false,
127
+ desc: 'Components to destroy (page,spec,feature,steps,helper,component)', aliases: '-w'
128
+
129
+ def destroy(*names)
130
+ if names.empty?
131
+ say 'Please provide at least one name to destroy', :red
132
+ return
133
+ end
134
+
135
+ names.each { |name| destroy_scaffold(name, options[:with]) }
136
+ end
137
+
138
+ map 'd' => 'destroy'
139
+
140
+ desc 'from_url [URL]', 'Generates page object and spec from a live URL'
141
+ option :name, type: :string, required: false,
142
+ desc: 'Override the page object name', aliases: '-n'
143
+ option :ai, type: :boolean, default: false,
144
+ desc: 'Use LLM for intelligent page analysis'
145
+
146
+ def from_url(url)
147
+ require_relative '../scaffolding/url_analyzer'
148
+ analyzer = UrlAnalyzer.new(url, name_override: options[:name], ai: options[:ai])
149
+ analysis = analyzer.analyze.to_h
75
150
 
76
- def scaffold(name)
77
- if Pathname.new('spec').exist? && !Pathname.new('features').exist?
78
- Scaffolding.new([name, load_config_path('spec')]).generate_spec
79
- else
80
- Scaffolding.new([name, load_config_path('feature')]).generate_feature
81
- Scaffolding.new([name, load_config_path('steps')]).generate_steps
151
+ page_name = analysis[:page_name]
152
+
153
+ if options[:dry_run]
154
+ DryRunPresenter.preview([
155
+ "page_objects/pages/#{page_name}.rb",
156
+ "spec/#{page_name}_spec.rb"
157
+ ])
158
+ return
82
159
  end
83
- Scaffolding.new([name, load_config_path('page')]).generate_page
160
+
161
+ Scaffolding.new([page_name]).generate_page_from_url(analysis)
162
+ Scaffolding.new([page_name]).generate_spec_from_url(analysis)
163
+ say "Generated page object and spec for #{url}"
84
164
  end
85
165
 
86
166
  no_commands do
@@ -92,9 +172,112 @@ class ScaffoldingCommands < Thor
92
172
  Scaffolding.new([name]).send("delete_#{type}")
93
173
  end
94
174
 
95
- def generate_scaffolding(name, type, path)
175
+ def generate_scaffolding(name, type, path, uses: nil)
96
176
  path ||= load_config_path(type)
97
- Scaffolding.new([name, path]).send("generate_#{type}")
177
+ scaffolding = Scaffolding.new([name, path])
178
+ scaffolding.uses = Array(uses) if uses
179
+ scaffolding.send("generate_#{type}")
180
+ end
181
+
182
+ def dry_run_preview(name, type)
183
+ path = options[:path] || load_config_path(type)
184
+ file = Scaffolding.planned_path(name, type, path)
185
+ DryRunPresenter.preview([file])
186
+ end
187
+
188
+ def generate_default_scaffold(name)
189
+ uses = options[:uses]
190
+ if Pathname.new('spec').exist? && !Pathname.new('features').exist?
191
+ generate_scaffolding(name, 'spec', load_config_path('spec'), uses:)
192
+ else
193
+ generate_scaffolding(name, 'feature', load_config_path('feature'))
194
+ generate_scaffolding(name, 'steps', load_config_path('steps'), uses:)
195
+ end
196
+ generate_scaffolding(name, 'page', load_config_path('page'), uses:)
197
+ end
198
+
199
+ def generate_selected_components(name, components)
200
+ uses = options[:uses]
201
+ components.each do |comp|
202
+ comp = comp.downcase.strip
203
+ case comp
204
+ when 'model'
205
+ generate_model_data(name)
206
+ else
207
+ generate_scaffolding(name, comp, load_config_path(comp), uses:)
208
+ end
209
+ end
210
+ end
211
+
212
+ def generate_crud(name)
213
+ require_relative '../scaffolding/crud_generator'
214
+ if options[:dry_run]
215
+ crud = CrudGenerator.new(name, Scaffolding, method(:load_config_path))
216
+ DryRunPresenter.preview(crud.planned_files)
217
+ return
218
+ end
219
+ crud = CrudGenerator.new(name, Scaffolding, method(:load_config_path))
220
+ generated = crud.generate
221
+ say "Generated CRUD scaffold for: #{generated.join(', ')}"
222
+ end
223
+
224
+ def generate_spec_from_page(name, source_file, ai: false) # rubocop:disable Naming/MethodParameterName
225
+ Scaffolding.new([name]).generate_spec_from_page(source_file, ai:)
226
+ end
227
+
228
+ def generate_model_data(name)
229
+ normalized = NameNormalizer.normalize(name)
230
+ path = "models/data/#{normalized}.yml"
231
+ return if File.exist?(path)
232
+
233
+ FileUtils.mkdir_p(File.dirname(path))
234
+ File.write(path, model_template(normalized))
235
+ end
236
+
237
+ def model_template(name)
238
+ <<~YAML
239
+ # Data model for #{name}
240
+ default:
241
+ name: 'Test #{name.capitalize}'
242
+ email: 'test@example.com'
243
+
244
+ valid:
245
+ name: 'Valid #{name.capitalize}'
246
+ email: 'valid@example.com'
247
+
248
+ invalid:
249
+ name: ''
250
+ email: 'invalid'
251
+ YAML
252
+ end
253
+
254
+ def destroy_scaffold(name, components = nil)
255
+ types = components ? components.map { |c| c.downcase.strip } : detect_scaffold_types
256
+ types.each { |type| delete_scaffolding(name, type) }
257
+ end
258
+
259
+ def detect_scaffold_types
260
+ if Pathname.new('spec').exist? && !Pathname.new('features').exist?
261
+ %w[page spec]
262
+ else
263
+ %w[page feature steps]
264
+ end
265
+ end
266
+
267
+ def interactive_scaffold
268
+ require_relative '../scaffolding/scaffold_menu'
269
+ result = ScaffoldMenu.new.run
270
+ return unless result
271
+
272
+ result[:names].each do |name|
273
+ result[:components].each do |comp|
274
+ if comp == :model
275
+ generate_model_data(name)
276
+ else
277
+ generate_scaffolding(name, comp.to_s, load_config_path(comp.to_s), uses: result[:uses])
278
+ end
279
+ end
280
+ end
98
281
  end
99
282
  end
100
283
  end
@@ -3,8 +3,6 @@
3
3
  require 'thor'
4
4
  require_relative '../utilities/utilities'
5
5
 
6
- # :reek:FeatureEnvy { enabled: false }
7
- # :reek:UtilityFunction { enabled: false }
8
6
  class UtilityCommands < Thor
9
7
  desc 'path [PATH]', 'Sets the default path for scaffolding'
10
8
  option :feature,
@@ -67,14 +65,96 @@ class UtilityCommands < Thor
67
65
  option :delete,
68
66
  type: :boolean, required: false, desc: 'This will delete the selected config file', aliases: '-d'
69
67
 
68
+ desc 'timeout [SECONDS]', 'Sets the default test timeout in seconds'
69
+
70
+ def timeout(seconds)
71
+ Utilities.timeout = seconds
72
+ end
73
+
74
+ desc 'viewport [DIMENSIONS]', 'Sets the default viewport size (e.g. 1920x1080, 375x812)'
75
+
76
+ def viewport(dimensions)
77
+ Utilities.viewport = dimensions
78
+ end
79
+
70
80
  desc 'platform [PLATFORM]', 'Sets the default platform for a cross-platform project'
71
81
 
72
82
  def platform(platform)
73
83
  Utilities.platform = platform
74
84
  end
75
85
 
86
+ desc 'debug [on/off]', 'Toggles debug mode for failure diagnostics and logging'
87
+
88
+ def debug(toggle)
89
+ enabled = %w[on true 1 yes].include?(toggle.downcase)
90
+ Utilities.debug = enabled
91
+ state = enabled ? 'enabled' : 'disabled'
92
+ say "Debug mode #{state}", :green
93
+ end
94
+
76
95
  desc 'start_appium', 'It starts the appium server'
77
96
  def start_appium
78
97
  system 'appium --base-path /wd/hub'
79
98
  end
99
+
100
+ desc 'desktop', 'Downloads the Raider Desktop GUI application'
101
+ option :path, type: :string, required: false,
102
+ desc: 'Directory to save the download', aliases: '-p'
103
+
104
+ def desktop
105
+ require_relative '../utilities/desktop_downloader'
106
+ version = DesktopDownloader.latest_version
107
+ unless version
108
+ say 'Could not reach GitHub releases. Check your internet connection.', :red
109
+ return
110
+ end
111
+
112
+ say "Raider Desktop v#{version} available for #{DesktopDownloader.platform_display_name}"
113
+ DesktopDownloader.download(options[:path])
114
+ say 'Raider Desktop downloaded successfully!', :green
115
+ end
116
+
117
+ desc 'llm [PROVIDER]', 'Configures the LLM provider (openai, anthropic, ollama)'
118
+ option :key, type: :string, required: false, desc: 'API key for the provider', aliases: '-k'
119
+ option :model, type: :string, required: false, desc: 'Model name to use', aliases: '-m'
120
+ option :url, type: :string, required: false, desc: 'API URL (for ollama)', aliases: '-u'
121
+ option :status, type: :boolean, required: false, desc: 'Show current LLM configuration', aliases: '-s'
122
+
123
+ def llm(provider = nil)
124
+ if options[:status] || provider.nil?
125
+ show_llm_status
126
+ return
127
+ end
128
+ configure_llm(provider)
129
+ end
130
+
131
+ no_commands do
132
+ def configure_llm(provider)
133
+ unless %w[openai anthropic ollama].include?(provider)
134
+ say "Unknown provider '#{provider}'. Choose: openai, anthropic, ollama", :red
135
+ return
136
+ end
137
+
138
+ Utilities.llm_provider = provider
139
+ Utilities.llm_api_key = options[:key] if options[:key]
140
+ Utilities.llm_model = options[:model] if options[:model]
141
+ Utilities.llm_url = options[:url] if options[:url]
142
+ say "LLM configured: #{provider}", :green
143
+ end
144
+
145
+ def show_llm_status
146
+ require_relative '../llm/client'
147
+ status = Llm::Client.status
148
+ if status[:configured]
149
+ say "Provider: #{status[:provider]}"
150
+ say "Model: #{status[:model] || 'default'}"
151
+ say "Available: #{status[:available] ? 'yes' : 'no'}"
152
+ else
153
+ say 'No LLM configured. Use: raider u llm <provider>', :yellow
154
+ say ' Providers: openai, anthropic, ollama'
155
+ say ' Example: raider u llm ollama'
156
+ say ' Example: raider u llm openai -k sk-...'
157
+ end
158
+ end
159
+ end
80
160
  end
@@ -9,7 +9,6 @@ class AutomationGenerator < Generator
9
9
  generate_home_page
10
10
  generate_pdp_page
11
11
  else
12
- generate_visual_options
13
12
  generate_components
14
13
  generate_model_files
15
14
  generate_pages
@@ -47,12 +46,6 @@ class AutomationGenerator < Generator
47
46
  template('appium_caps.tt', "#{name}/config/capabilities.yml")
48
47
  end
49
48
 
50
- def generate_visual_options
51
- return unless visual?
52
-
53
- template('visual_options.tt', "#{name}/config/options.yml")
54
- end
55
-
56
49
  def generate_login_page
57
50
  template('login.tt', "#{name}/page_objects/pages/login.rb")
58
51
  end
@@ -1,5 +1,9 @@
1
- <%- if selenium_based? -%>
2
- <%=- ERB.new(File.read(File.expand_path('./partials/selenium_account.tt', __dir__)), trim_mode: '-').result(binding) -%>
3
- <%- elsif watir? -%>
4
- <%=- ERB.new(File.read(File.expand_path('./partials/watir_account.tt', __dir__)), trim_mode: '-').result(binding) -%>
5
- <%- end -%>
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../abstract/page'
4
+
5
+ class Account < Page
6
+ def url(_page)
7
+ 'index.php?rt=account/account'
8
+ end
9
+ end