ruby_raider 3.0.0 → 3.1.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 (193) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/e2e_tests.yml +0 -17
  3. data/.github/workflows/system_tests.yml +0 -22
  4. data/README.md +8 -62
  5. data/lib/commands/scaffolding_commands.rb +22 -188
  6. data/lib/commands/utility_commands.rb +10 -44
  7. data/lib/generators/automation/templates/login.tt +1 -9
  8. data/lib/generators/automation/templates/page.tt +1 -7
  9. data/lib/generators/automation/templates/partials/initialize_selector.tt +1 -3
  10. data/lib/generators/automation/templates/partials/visit_method.tt +2 -6
  11. data/lib/generators/common_generator.rb +0 -6
  12. data/lib/generators/cucumber/cucumber_generator.rb +0 -24
  13. data/lib/generators/cucumber/templates/accessibility_steps.tt +2 -6
  14. data/lib/generators/cucumber/templates/cucumber.tt +0 -7
  15. data/lib/generators/cucumber/templates/env.tt +1 -3
  16. data/lib/generators/cucumber/templates/partials/appium_env.tt +1 -6
  17. data/lib/generators/cucumber/templates/partials/selenium_env.tt +0 -5
  18. data/lib/generators/cucumber/templates/partials/watir_env.tt +0 -5
  19. data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -4
  20. data/lib/generators/cucumber/templates/world.tt +1 -3
  21. data/lib/generators/generator.rb +2 -46
  22. data/lib/generators/helper_generator.rb +4 -42
  23. data/lib/generators/infrastructure/templates/github.tt +2 -2
  24. data/lib/generators/infrastructure/templates/github_appium.tt +2 -6
  25. data/lib/generators/invoke_generators.rb +4 -25
  26. data/lib/generators/menu_generator.rb +10 -100
  27. data/lib/generators/rspec/rspec_generator.rb +0 -11
  28. data/lib/generators/rspec/templates/accessibility_spec.tt +2 -6
  29. data/lib/generators/rspec/templates/spec.tt +6 -8
  30. data/lib/generators/templates/common/gemfile.tt +0 -26
  31. data/lib/generators/templates/common/git_ignore.tt +0 -2
  32. data/lib/generators/templates/common/partials/mobile_config.tt +0 -4
  33. data/lib/generators/templates/common/partials/web_config.tt +0 -4
  34. data/lib/generators/templates/common/rakefile.tt +0 -9
  35. data/lib/generators/templates/common/read_me.tt +4 -10
  36. data/lib/generators/templates/helpers/allure_helper.tt +0 -10
  37. data/lib/generators/templates/helpers/browser_helper.tt +1 -1
  38. data/lib/generators/templates/helpers/partials/debug_diagnostics.tt +1 -3
  39. data/lib/generators/templates/helpers/partials/debug_start.tt +1 -3
  40. data/lib/generators/templates/helpers/partials/quit_driver.tt +1 -3
  41. data/lib/generators/templates/helpers/partials/screenshot.tt +1 -3
  42. data/lib/generators/templates/helpers/partials/selenium_driver.tt +1 -1
  43. data/lib/generators/templates/helpers/spec_helper.tt +2 -39
  44. data/lib/ruby_raider.rb +2 -50
  45. data/lib/scaffolding/project_detector.rb +30 -13
  46. data/lib/scaffolding/scaffolding.rb +0 -109
  47. data/lib/scaffolding/templates/component.tt +1 -4
  48. data/lib/scaffolding/templates/page_object.tt +0 -10
  49. data/lib/scaffolding/templates/spec.tt +2 -6
  50. data/lib/scaffolding/templates/steps.tt +0 -2
  51. data/lib/utilities/utilities.rb +26 -24
  52. data/lib/version +1 -1
  53. data/sig/commands/scaffolding_commands.rbs +0 -12
  54. data/sig/commands/utility_commands.rbs +0 -6
  55. data/sig/generators/cucumber/cucumber_generator.rbs +0 -4
  56. data/sig/generators/generator.rbs +0 -11
  57. data/sig/generators/helper_generator.rbs +1 -5
  58. data/sig/generators/invoke_generators.rbs +1 -2
  59. data/sig/generators/menu_generator.rbs +2 -6
  60. data/sig/generators/rspec/rspec_generator.rbs +0 -2
  61. data/sig/ruby_raider.rbs +1 -3
  62. data/sig/scaffolding/project_detector.rbs +0 -1
  63. data/sig/scaffolding/scaffolding.rbs +0 -23
  64. data/sig/utilities/utilities.rbs +0 -4
  65. data/spec/commands/raider_commands_spec.rb +0 -61
  66. data/spec/commands/scaffolding_commands_spec.rb +22 -0
  67. data/spec/integration/commands/browser_update_after_creation_spec.rb +123 -0
  68. data/spec/integration/commands/scaffolding_commands_spec.rb +0 -20
  69. data/spec/integration/commands/utility_commands_spec.rb +9 -9
  70. data/spec/integration/content/ci_content_spec.rb +6 -61
  71. data/spec/integration/content/common_content_spec.rb +0 -64
  72. data/spec/integration/content/config_content_spec.rb +1 -51
  73. data/spec/integration/content/content_helper.rb +2 -2
  74. data/spec/integration/content/gemfile_content_spec.rb +0 -71
  75. data/spec/integration/content/helper_content_spec.rb +4 -240
  76. data/spec/integration/content/page_content_spec.rb +0 -89
  77. data/spec/integration/content/syntax_validation_spec.rb +2 -2
  78. data/spec/integration/content/test_content_spec.rb +0 -119
  79. data/spec/integration/end_to_end_features_spec.rb +13 -411
  80. data/spec/integration/end_to_end_spec.rb +0 -96
  81. data/spec/integration/generators/axe_addon_spec.rb +2 -52
  82. data/spec/integration/generators/common_generator_spec.rb +1 -13
  83. data/spec/integration/generators/config_features_spec.rb +3 -81
  84. data/spec/integration/generators/debug_helper_spec.rb +0 -20
  85. data/spec/integration/generators/github_generator_spec.rb +5 -15
  86. data/spec/integration/generators/helpers_generator_spec.rb +0 -37
  87. data/spec/integration/scaffolding_e2e_spec.rb +540 -0
  88. data/spec/integration/settings_helper.rb +1 -3
  89. data/spec/integration/spec_helper.rb +6 -11
  90. data/spec/menus/menu_generator_spec.rb +4 -107
  91. data/spec/scaffolding/scaffold_project_detector_spec.rb +42 -26
  92. data/spec/scaffolding/scaffolding_features_spec.rb +0 -183
  93. data/spec/system/selenium_spec.rb +1 -4
  94. data/spec/system/support/system_test_helper.rb +0 -1
  95. data/spec/system/watir_spec.rb +1 -4
  96. data/spec/utilities/headless_config_spec.rb +82 -0
  97. data/spec/utilities/utilities_spec.rb +105 -0
  98. metadata +7 -97
  99. data/lib/adopter/adopt_menu.rb +0 -146
  100. data/lib/adopter/converters/base_converter.rb +0 -84
  101. data/lib/adopter/converters/identity_converter.rb +0 -53
  102. data/lib/adopter/migration_plan.rb +0 -74
  103. data/lib/adopter/migrator.rb +0 -96
  104. data/lib/adopter/plan_builder.rb +0 -275
  105. data/lib/adopter/project_analyzer.rb +0 -252
  106. data/lib/adopter/project_detector.rb +0 -157
  107. data/lib/generators/cucumber/templates/partials/capybara_env.tt +0 -38
  108. data/lib/generators/cucumber/templates/partials/capybara_world.tt +0 -6
  109. data/lib/generators/cucumber/templates/performance_feature.tt +0 -5
  110. data/lib/generators/cucumber/templates/performance_steps.tt +0 -17
  111. data/lib/generators/cucumber/templates/visual_feature.tt +0 -5
  112. data/lib/generators/cucumber/templates/visual_steps.tt +0 -19
  113. data/lib/generators/infrastructure/gitlab_generator.rb +0 -11
  114. data/lib/generators/infrastructure/templates/gitlab.tt +0 -46
  115. data/lib/generators/minitest/minitest_generator.rb +0 -35
  116. data/lib/generators/minitest/templates/accessibility_test.tt +0 -26
  117. data/lib/generators/minitest/templates/performance_test.tt +0 -18
  118. data/lib/generators/minitest/templates/test.tt +0 -64
  119. data/lib/generators/minitest/templates/visual_test.tt +0 -23
  120. data/lib/generators/rspec/templates/performance_spec.tt +0 -18
  121. data/lib/generators/rspec/templates/visual_spec.tt +0 -20
  122. data/lib/generators/templates/helpers/capybara_helper.tt +0 -32
  123. data/lib/generators/templates/helpers/performance_helper.tt +0 -57
  124. data/lib/generators/templates/helpers/visual_helper.tt +0 -58
  125. data/lib/llm/client.rb +0 -79
  126. data/lib/llm/config.rb +0 -57
  127. data/lib/llm/prompts.rb +0 -84
  128. data/lib/llm/provider.rb +0 -27
  129. data/lib/llm/providers/anthropic_provider.rb +0 -43
  130. data/lib/llm/providers/ollama_provider.rb +0 -56
  131. data/lib/llm/providers/openai_provider.rb +0 -42
  132. data/lib/llm/response_parser.rb +0 -67
  133. data/lib/plugin/plugin.rb +0 -111
  134. data/lib/plugin/plugin_exposer.rb +0 -55
  135. data/lib/scaffolding/crud_generator.rb +0 -94
  136. data/lib/scaffolding/dry_run_presenter.rb +0 -16
  137. data/lib/scaffolding/page_introspector.rb +0 -45
  138. data/lib/scaffolding/scaffold_menu.rb +0 -103
  139. data/lib/scaffolding/templates/page_from_url.tt +0 -75
  140. data/lib/scaffolding/templates/spec_from_page.tt +0 -31
  141. data/lib/scaffolding/templates/spec_from_url.tt +0 -46
  142. data/lib/scaffolding/url_analyzer.rb +0 -179
  143. data/sig/adopter/adopt_menu.rbs +0 -25
  144. data/sig/adopter/converters/base_converter.rbs +0 -23
  145. data/sig/adopter/converters/identity_converter.rbs +0 -16
  146. data/sig/adopter/migration_plan.rbs +0 -34
  147. data/sig/adopter/migrator.rbs +0 -21
  148. data/sig/adopter/plan_builder.rbs +0 -38
  149. data/sig/adopter/project_analyzer.rbs +0 -39
  150. data/sig/adopter/project_detector.rbs +0 -26
  151. data/sig/generators/infrastructure/gitlab_generator.rbs +0 -4
  152. data/sig/generators/minitest/minitest_generator.rbs +0 -8
  153. data/sig/llm/client.rbs +0 -15
  154. data/sig/llm/config.rbs +0 -20
  155. data/sig/llm/prompts.rbs +0 -8
  156. data/sig/llm/provider.rbs +0 -12
  157. data/sig/llm/providers/anthropic_provider.rbs +0 -16
  158. data/sig/llm/providers/ollama_provider.rbs +0 -18
  159. data/sig/llm/providers/openai_provider.rbs +0 -16
  160. data/sig/llm/response_parser.rbs +0 -13
  161. data/sig/plugin/plugin.rbs +0 -24
  162. data/sig/plugin/plugin_exposer.rbs +0 -20
  163. data/sig/scaffolding/crud_generator.rbs +0 -16
  164. data/sig/scaffolding/dry_run_presenter.rbs +0 -4
  165. data/sig/scaffolding/page_introspector.rbs +0 -14
  166. data/sig/scaffolding/scaffold_menu.rbs +0 -18
  167. data/sig/scaffolding/url_analyzer.rbs +0 -28
  168. data/spec/adopter/adopt_menu_spec.rb +0 -176
  169. data/spec/adopter/converters/identity_converter_spec.rb +0 -145
  170. data/spec/adopter/migration_plan_spec.rb +0 -113
  171. data/spec/adopter/migrator_spec.rb +0 -277
  172. data/spec/adopter/plan_builder_spec.rb +0 -298
  173. data/spec/adopter/project_analyzer_spec.rb +0 -337
  174. data/spec/adopter/project_detector_spec.rb +0 -295
  175. data/spec/generators/generator_spec.rb +0 -23
  176. data/spec/integration/content/reporter_content_spec.rb +0 -236
  177. data/spec/integration/content/skip_flags_content_spec.rb +0 -206
  178. data/spec/integration/generators/gitlab_generator_spec.rb +0 -38
  179. data/spec/integration/generators/lighthouse_addon_spec.rb +0 -132
  180. data/spec/integration/generators/minitest_generator_spec.rb +0 -64
  181. data/spec/integration/generators/reporter_spec.rb +0 -159
  182. data/spec/integration/generators/skip_flags_spec.rb +0 -134
  183. data/spec/integration/generators/visual_addon_spec.rb +0 -148
  184. data/spec/llm/client_spec.rb +0 -79
  185. data/spec/llm/config_spec.rb +0 -92
  186. data/spec/llm/prompts_spec.rb +0 -49
  187. data/spec/llm/response_parser_spec.rb +0 -92
  188. data/spec/menus/adopter_adopt_menu_spec.rb +0 -97
  189. data/spec/scaffolding/page_introspector_spec.rb +0 -82
  190. data/spec/scaffolding/url_analyzer_spec.rb +0 -110
  191. data/spec/system/adopt_matrix_spec.rb +0 -537
  192. data/spec/system/adopt_spec.rb +0 -225
  193. data/spec/system/capybara_spec.rb +0 -42
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../lib/llm/response_parser'
4
-
5
- RSpec.describe Llm::ResponseParser do
6
- describe '.parse_json' do
7
- it 'parses raw JSON' do
8
- result = described_class.parse_json('{"key": "value"}')
9
- expect(result).to eq(key: 'value')
10
- end
11
-
12
- it 'parses JSON from markdown code block' do
13
- response = "```json\n{\"key\": \"value\"}\n```"
14
- result = described_class.parse_json(response)
15
- expect(result).to eq(key: 'value')
16
- end
17
-
18
- it 'extracts JSON from surrounding text' do
19
- response = "Here is the result:\n{\"key\": \"value\"}\nDone."
20
- result = described_class.parse_json(response)
21
- expect(result).to eq(key: 'value')
22
- end
23
-
24
- it 'returns nil for nil input' do
25
- expect(described_class.parse_json(nil)).to be_nil
26
- end
27
-
28
- it 'returns nil for empty input' do
29
- expect(described_class.parse_json('')).to be_nil
30
- end
31
-
32
- it 'returns nil for invalid JSON' do
33
- expect(described_class.parse_json('not json at all')).to be_nil
34
- end
35
-
36
- it 'returns nil for JSON arrays (not hashes)' do
37
- expect(described_class.parse_json('[1, 2, 3]')).to be_nil
38
- end
39
- end
40
-
41
- describe '.extract_elements' do
42
- let(:valid_response) do
43
- {
44
- elements: [
45
- { name: 'email_field', type: 'input', locator: { type: 'id', value: 'email' }, purpose: 'Email input' },
46
- { name: 'submit_btn', type: 'button', locator: { type: 'id', value: 'submit' }, text: 'Submit' }
47
- ]
48
- }.to_json
49
- end
50
-
51
- it 'extracts and normalizes elements' do
52
- elements = described_class.extract_elements(valid_response)
53
- expect(elements.length).to eq(2)
54
- expect(elements.first[:name]).to eq('email_field')
55
- expect(elements.first[:locator][:type]).to eq(:id)
56
- end
57
-
58
- it 'skips elements without required fields' do
59
- response = { elements: [{ name: 'incomplete' }] }.to_json
60
- elements = described_class.extract_elements(response)
61
- expect(elements).to be_empty
62
- end
63
-
64
- it 'returns nil for non-element response' do
65
- expect(described_class.extract_elements('{"other": "data"}')).to be_nil
66
- end
67
-
68
- it 'returns nil for nil response' do
69
- expect(described_class.extract_elements(nil)).to be_nil
70
- end
71
- end
72
-
73
- describe '.extract_scenarios' do
74
- let(:valid_response) do
75
- {
76
- scenarios: [
77
- { method: 'login', description: 'logs in with valid credentials', assertion_hint: 'expect redirect' }
78
- ]
79
- }.to_json
80
- end
81
-
82
- it 'extracts scenarios' do
83
- scenarios = described_class.extract_scenarios(valid_response)
84
- expect(scenarios.length).to eq(1)
85
- expect(scenarios.first[:method]).to eq('login')
86
- end
87
-
88
- it 'returns nil for non-scenario response' do
89
- expect(described_class.extract_scenarios('{"other": "data"}')).to be_nil
90
- end
91
- end
92
- end
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'fileutils'
4
- require 'rspec'
5
- require_relative '../../lib/adopter/adopt_menu'
6
-
7
- RSpec.describe Adopter::AdoptMenu do # rubocop:disable RSpec/FilePath
8
- let(:prompt) { instance_double(TTY::Prompt) }
9
- let(:source_dir) { 'tmp_adopt_interactive_source' }
10
- let(:output_dir) { 'tmp_adopt_interactive_output' }
11
-
12
- before do
13
- allow(TTY::Prompt).to receive(:new).and_return(prompt)
14
- allow(prompt).to receive(:say)
15
- allow(prompt).to receive(:warn)
16
- allow(prompt).to receive(:error)
17
-
18
- FileUtils.mkdir_p(source_dir)
19
- File.write("#{source_dir}/Gemfile", "gem 'rspec'\ngem 'selenium-webdriver'\n")
20
- FileUtils.mkdir_p("#{source_dir}/spec")
21
- File.write("#{source_dir}/spec/login_spec.rb", "describe 'Login' do\n it('works') { expect(true).to be true }\nend\n")
22
- end
23
-
24
- after do
25
- FileUtils.rm_rf(source_dir)
26
- FileUtils.rm_rf(output_dir)
27
- end
28
-
29
- describe '#run' do
30
- before do
31
- allow(prompt).to receive(:ask).and_return(source_dir, output_dir)
32
- allow(prompt).to receive(:select)
33
- .with('Select target automation framework:', anything, anything).and_return('Selenium')
34
- allow(prompt).to receive(:select)
35
- .with('Select target test framework:', anything, anything).and_return('Rspec')
36
- allow(prompt).to receive(:select)
37
- .with('Configure CI/CD?', anything, anything).and_return(nil)
38
- end
39
-
40
- it 'shows detection preview' do
41
- described_class.new.run
42
-
43
- expect(prompt).to have_received(:say).with(match(/Detected project settings/))
44
- expect(prompt).to have_received(:say).with(match(/Automation: selenium/))
45
- expect(prompt).to have_received(:say).with(match(/Framework:.*rspec/))
46
- end
47
-
48
- it 'executes adoption and shows results' do
49
- described_class.new.run
50
-
51
- expect(prompt).to have_received(:say).with(match(/Adoption complete/))
52
- expect(prompt).to have_received(:say).with(match(/Pages converted/))
53
- expect(prompt).to have_received(:say).with(match(/Tests converted/))
54
- end
55
-
56
- it 'generates the output project' do
57
- described_class.new.run
58
-
59
- expect(File).to exist("#{output_dir}/Gemfile")
60
- end
61
-
62
- it 'shows output path and bundle install instruction' do
63
- described_class.new.run
64
-
65
- expect(prompt).to have_received(:say).with(match(/Output: #{output_dir}/))
66
- expect(prompt).to have_received(:say).with(match(/bundle install/))
67
- end
68
- end
69
-
70
- describe '#run with mobile source project' do
71
- before do
72
- File.write("#{source_dir}/Gemfile", "gem 'rspec'\ngem 'appium_lib'\n")
73
- allow(prompt).to receive(:ask).and_return(source_dir, output_dir)
74
- allow(prompt).to receive(:select).and_return('Selenium', 'Rspec', nil)
75
- end
76
-
77
- it 'shows MobileProjectError message' do
78
- described_class.new.run
79
-
80
- expect(prompt).to have_received(:error).with(match(/Mobile.*Appium.*cannot be adopted/))
81
- end
82
- end
83
-
84
- describe 'validation constants' do
85
- it 'defines web automations' do
86
- expect(described_class::WEB_AUTOMATIONS).to contain_exactly('selenium', 'capybara', 'watir')
87
- end
88
-
89
- it 'defines test frameworks' do
90
- expect(described_class::TEST_FRAMEWORKS).to contain_exactly('rspec', 'cucumber', 'minitest')
91
- end
92
-
93
- it 'defines CI platforms' do
94
- expect(described_class::CI_PLATFORMS).to contain_exactly('github', 'gitlab')
95
- end
96
- end
97
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'fileutils'
4
- require_relative '../../lib/scaffolding/page_introspector'
5
-
6
- RSpec.describe PageIntrospector do
7
- subject(:introspector) { described_class.new(page_file) }
8
-
9
- let(:tmp_dir) { 'tmp_introspector_test' }
10
- let(:page_file) { "#{tmp_dir}/login_page.rb" }
11
-
12
- before do
13
- FileUtils.mkdir_p(tmp_dir)
14
- File.write(page_file, <<~RUBY)
15
- class LoginPage < Page
16
- def url(_page)
17
- '/login'
18
- end
19
-
20
- def login(username, password)
21
- username_field.send_keys username
22
- password_field.send_keys password
23
- login_button.click
24
- end
25
-
26
- def welcome_message
27
- driver.find_element(css: '.welcome').text
28
- end
29
-
30
- private
31
-
32
- def username_field
33
- driver.find_element(id: 'username')
34
- end
35
-
36
- def password_field
37
- driver.find_element(id: 'password')
38
- end
39
-
40
- def login_button
41
- driver.find_element(id: 'login-btn')
42
- end
43
- end
44
- RUBY
45
- end
46
-
47
- after do
48
- FileUtils.rm_rf(tmp_dir)
49
- end
50
-
51
- it 'extracts class name' do
52
- expect(introspector.class_name).to eq('LoginPage')
53
- end
54
-
55
- it 'extracts public methods' do
56
- method_names = introspector.methods.map { |m| m[:name] }
57
- expect(method_names).to include('login')
58
- expect(method_names).to include('welcome_message')
59
- end
60
-
61
- it 'skips url method' do
62
- method_names = introspector.methods.map { |m| m[:name] }
63
- expect(method_names).not_to include('url')
64
- end
65
-
66
- it 'skips private methods' do
67
- method_names = introspector.methods.map { |m| m[:name] }
68
- expect(method_names).not_to include('username_field')
69
- expect(method_names).not_to include('password_field')
70
- expect(method_names).not_to include('login_button')
71
- end
72
-
73
- it 'extracts method parameters' do
74
- login_method = introspector.methods.find { |m| m[:name] == 'login' }
75
- expect(login_method[:params]).to eq(%w[username password])
76
- end
77
-
78
- it 'handles methods with no parameters' do
79
- welcome = introspector.methods.find { |m| m[:name] == 'welcome_message' }
80
- expect(welcome[:params]).to eq([])
81
- end
82
- end
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../lib/scaffolding/url_analyzer'
4
-
5
- RSpec.describe UrlAnalyzer do
6
- subject(:analyzer) { described_class.new('https://example.com/login') }
7
-
8
- let(:sample_html) do
9
- <<~HTML
10
- <html>
11
- <body>
12
- <form>
13
- <input type="text" id="username" name="username" />
14
- <input type="password" id="password" name="password" />
15
- <input type="hidden" name="csrf" value="token123" />
16
- <select id="role" name="role"><option>Admin</option></select>
17
- <textarea id="bio" name="bio"></textarea>
18
- <button id="login-btn">Log In</button>
19
- <input type="submit" id="submit-btn" value="Submit" />
20
- <a href="/forgot">Forgot Password</a>
21
- </form>
22
- </body>
23
- </html>
24
- HTML
25
- end
26
-
27
- before do
28
- stub_request = instance_double(Net::HTTPSuccess, body: sample_html)
29
- allow(Net::HTTP).to receive(:get_response).and_return(stub_request)
30
- end
31
-
32
- describe '#analyze' do
33
- before { analyzer.analyze }
34
-
35
- it 'parses input elements (excluding hidden)' do
36
- inputs = analyzer.elements.select { |e| e[:type] == :input }
37
- expect(inputs.length).to eq(2)
38
- names = inputs.map { |e| e[:name] }
39
- expect(names).to include('username', 'password')
40
- end
41
-
42
- it 'parses select elements' do
43
- selects = analyzer.elements.select { |e| e[:type] == :select }
44
- expect(selects.length).to eq(1)
45
- expect(selects.first[:name]).to eq('role')
46
- end
47
-
48
- it 'parses textarea elements' do
49
- textareas = analyzer.elements.select { |e| e[:type] == :textarea }
50
- expect(textareas.length).to eq(1)
51
- end
52
-
53
- it 'parses button elements' do
54
- buttons = analyzer.elements.select { |e| e[:type] == :button }
55
- expect(buttons.length).to eq(1)
56
- expect(buttons.first[:text]).to eq('Log In')
57
- end
58
-
59
- it 'parses submit inputs' do
60
- submits = analyzer.elements.select { |e| e[:type] == :submit }
61
- expect(submits.length).to eq(1)
62
- end
63
-
64
- it 'parses link elements' do
65
- links = analyzer.elements.select { |e| e[:type] == :link }
66
- expect(links.length).to eq(1)
67
- expect(links.first[:name]).to eq('forgot_password')
68
- end
69
-
70
- it 'prefers id locators' do
71
- username = analyzer.elements.find { |e| e[:name] == 'username' }
72
- expect(username[:locator]).to eq(type: :id, value: 'username')
73
- end
74
- end
75
-
76
- describe '#page_name' do
77
- it 'derives name from URL path' do
78
- expect(analyzer.page_name).to eq('login')
79
- end
80
-
81
- it 'uses name override when provided' do
82
- custom = described_class.new('https://example.com/login', name_override: 'auth')
83
- expect(custom.page_name).to eq('auth')
84
- end
85
-
86
- it 'defaults to home for root URL' do
87
- root = described_class.new('https://example.com/')
88
- expect(root.page_name).to eq('home')
89
- end
90
- end
91
-
92
- describe '#to_h' do
93
- it 'returns structured hash' do
94
- analyzer.analyze
95
- result = analyzer.to_h
96
- expect(result).to include(:page_name, :url, :url_path, :elements)
97
- expect(result[:url_path]).to eq('/login')
98
- end
99
- end
100
-
101
- describe 'AI mode' do
102
- let(:ai_analyzer) { described_class.new('https://example.com/login', ai: true) }
103
-
104
- it 'falls back to regex parsing when LLM is unavailable' do
105
- allow_any_instance_of(described_class).to receive(:analyze_with_llm).and_return(nil)
106
- ai_analyzer.analyze
107
- expect(ai_analyzer.elements).not_to be_empty
108
- end
109
- end
110
- end