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
@@ -4,7 +4,6 @@ require_relative 'migration_plan'
4
4
  require_relative 'converters/identity_converter'
5
5
 
6
6
  module Adopter
7
- # :reek:TooManyMethods { enabled: false }
8
7
  class PlanBuilder
9
8
  def initialize(analysis, params)
10
9
  @analysis = analysis
@@ -63,7 +62,7 @@ module Adopter
63
62
  tests = @analysis[:tests] || []
64
63
  return [] if target_cucumber?
65
64
 
66
- tests.select { |t| t[:type] != :cucumber }.map do |test|
65
+ tests.reject { |t| t[:type] == :cucumber }.map do |test|
67
66
  source_file = File.join(@params[:source_path], test[:path])
68
67
  content = File.read(source_file)
69
68
  converted = convert_test(content, test)
@@ -87,7 +86,7 @@ module Adopter
87
86
 
88
87
  ConvertedFile.new(
89
88
  output_path: raider_feature_path(feature[:path]),
90
- content: content,
89
+ content:,
91
90
  source_file: feature[:path],
92
91
  conversion_notes: 'Feature file copied as-is'
93
92
  )
@@ -125,7 +124,6 @@ module Adopter
125
124
  end
126
125
  end
127
126
 
128
- # :reek:ControlParameter { enabled: false }
129
127
  def convert_page_dsl(content, page, source_dsl, target)
130
128
  converter = find_page_converter(source_dsl, target)
131
129
  if converter
@@ -162,7 +160,6 @@ module Adopter
162
160
  end
163
161
  end
164
162
 
165
- # :reek:ControlParameter { enabled: false }
166
163
  def convert_test_framework(content, test, source_framework, target)
167
164
  converter = find_test_converter(source_framework, target)
168
165
  if converter
@@ -5,7 +5,6 @@ require_relative 'project_detector'
5
5
  module Adopter
6
6
  class MobileProjectError < StandardError; end
7
7
 
8
- # :reek:TooManyMethods { enabled: false }
9
8
  class ProjectAnalyzer
10
9
  KNOWN_FRAMEWORK_GEMS = %w[
11
10
  activesupport allure-cucumber allure-rspec allure-minitest allure-ruby-commons
@@ -79,7 +78,6 @@ module Adopter
79
78
  'Mobile (Appium) projects cannot be adopted. Only web-based projects are supported.'
80
79
  end
81
80
 
82
- # :reek:FeatureEnvy { enabled: false }
83
81
  def discover_pages
84
82
  page_path = detect_page_dir
85
83
  return [] unless page_path
@@ -115,7 +113,6 @@ module Adopter
115
113
  results
116
114
  end
117
115
 
118
- # :reek:FeatureEnvy { enabled: false }
119
116
  def discover_helpers
120
117
  helper_files = Dir.glob(File.join(@source_path, '**', '*helper*.rb')) +
121
118
  Dir.glob(File.join(@source_path, '**', 'support', '*.rb')) +
@@ -129,7 +126,7 @@ module Adopter
129
126
 
130
127
  {
131
128
  path: relative_path(file),
132
- role: role,
129
+ role:,
133
130
  modules_defined: extract_modules(content)
134
131
  }
135
132
  end
@@ -153,7 +150,6 @@ module Adopter
153
150
  end
154
151
  end
155
152
 
156
- # :reek:NestedIterators { enabled: false }
157
153
  def discover_custom_gems
158
154
  gemfile = File.join(@source_path, 'Gemfile')
159
155
  return [] unless File.exist?(gemfile)
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Adopter
4
- # :reek:TooManyMethods { enabled: false }
5
4
  module ProjectDetector
6
5
  GEM_AUTOMATION_MAP = {
7
6
  'site_prism' => 'capybara',
@@ -9,9 +8,9 @@ module Adopter
9
8
  'selenium-webdriver' => 'selenium',
10
9
  'watir' => 'watir',
11
10
  'appium_lib' => 'appium',
12
- 'eyes_selenium' => 'applitools',
13
- 'axe-core-selenium' => 'axe',
14
- 'axe-core-rspec' => 'axe'
11
+ 'eyes_selenium' => 'selenium',
12
+ 'axe-core-selenium' => 'selenium',
13
+ 'axe-core-rspec' => 'selenium'
15
14
  }.freeze
16
15
 
17
16
  GEM_FRAMEWORK_MAP = {
@@ -78,7 +77,6 @@ module Adopter
78
77
  find_existing_dir(path, candidates)
79
78
  end
80
79
 
81
- # :reek:NestedIterators { enabled: false }
82
80
  def detect_browser(path)
83
81
  config_files = helper_and_config_files(path)
84
82
  config_files.each do |file|
@@ -3,7 +3,6 @@
3
3
  require 'thor'
4
4
  require_relative '../adopter/adopt_menu'
5
5
 
6
- # :reek:FeatureEnvy { enabled: false }
7
6
  class AdoptCommands < Thor
8
7
  desc 'project [SOURCE_PATH]', 'Adopts an existing test project into Ruby Raider conventions'
9
8
  option :parameters,
@@ -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,4 +1,4 @@
1
- <% if %w[selenium axe applitools appium_ios appium_android].include?(automation) -%>
1
+ <% if %w[selenium appium_ios appium_android].include?(automation) -%>
2
2
  driver.find_element
3
3
  <% else -%>
4
4
  browser.element
@@ -6,13 +6,6 @@
6
6
  def initialize(browser)
7
7
  @browser = browser
8
8
  end
9
- <% elsif axe? %>
10
- attr_reader :driver
11
- alias page driver
12
-
13
- def initialize(driver)
14
- @driver = driver
15
- end
16
9
  <% else %>
17
10
  attr_reader :driver
18
11
 
@@ -3,7 +3,6 @@
3
3
  "#{base_url}#{url(*page)}"
4
4
  end
5
5
 
6
- # :reek:UtilityFunction
7
6
  def base_url
8
7
  YAML.load_file('config/config.yml')['url']
9
8
  end
@@ -17,6 +17,10 @@ class CommonGenerator < Generator
17
17
  template('common/rakefile.tt', "#{name}/Rakefile")
18
18
  end
19
19
 
20
+ def generate_ruby_version_file
21
+ template('common/ruby_version.tt', "#{name}/.ruby-version")
22
+ end
23
+
20
24
  def generate_gemfile
21
25
  template('common/gemfile.tt', "#{name}/Gemfile")
22
26
  end
@@ -33,7 +37,15 @@ class CommonGenerator < Generator
33
37
  template('common/git_ignore.tt', "#{name}/.gitignore")
34
38
  end
35
39
 
40
+ def generate_rspec_file
41
+ return unless rspec?
42
+
43
+ template('common/rspec.tt', "#{name}/.rspec")
44
+ end
45
+
36
46
  def create_allure_folder
47
+ return unless allure_reporter?
48
+
37
49
  empty_directory "#{name}/allure-results"
38
50
  end
39
51
  end
@@ -27,6 +27,42 @@ class CucumberGenerator < Generator
27
27
  template('cucumber.tt', "#{name}/cucumber.yml")
28
28
  end
29
29
 
30
+ def generate_visual_feature
31
+ return unless visual_addon? && web?
32
+
33
+ template('visual_feature.tt', "#{name}/features/visual.feature")
34
+ end
35
+
36
+ def generate_visual_steps
37
+ return unless visual_addon? && web?
38
+
39
+ template('visual_steps.tt', "#{name}/features/step_definitions/visual_steps.rb")
40
+ end
41
+
42
+ def generate_accessibility_feature
43
+ return unless axe_addon? && web?
44
+
45
+ template('accessibility_feature.tt', "#{name}/features/accessibility.feature")
46
+ end
47
+
48
+ def generate_accessibility_steps
49
+ return unless axe_addon? && web?
50
+
51
+ template('accessibility_steps.tt', "#{name}/features/step_definitions/accessibility_steps.rb")
52
+ end
53
+
54
+ def generate_performance_feature
55
+ return unless lighthouse_addon? && web?
56
+
57
+ template('performance_feature.tt', "#{name}/features/performance.feature")
58
+ end
59
+
60
+ def generate_performance_steps
61
+ return unless lighthouse_addon? && web?
62
+
63
+ template('performance_steps.tt', "#{name}/features/step_definitions/performance_steps.rb")
64
+ end
65
+
30
66
  def template_name
31
67
  @template_name ||= (@_initializer.first & %w[android ios cross_platform]).empty? ? 'login' : 'home'
32
68
  end
@@ -0,0 +1,5 @@
1
+ Feature: Accessibility
2
+
3
+ Scenario: Page has no accessibility violations
4
+ Given I am on the home page
5
+ Then the page should be accessible