ruby_raider 1.1.4 → 2.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/integration.yml +4 -6
  3. data/.github/workflows/reek.yml +6 -5
  4. data/.github/workflows/release.yml +175 -0
  5. data/.github/workflows/rubocop.yml +7 -6
  6. data/.github/workflows/system_tests.yml +83 -0
  7. data/.gitignore +1 -1
  8. data/.rubocop.yml +24 -0
  9. data/README.md +3 -1
  10. data/RELEASE.md +412 -0
  11. data/RELEASE_QUICK_GUIDE.md +77 -0
  12. data/bin/release +186 -0
  13. data/lib/adopter/adopt_menu.rb +150 -0
  14. data/lib/adopter/converters/base_converter.rb +85 -0
  15. data/lib/adopter/converters/identity_converter.rb +56 -0
  16. data/lib/adopter/migration_plan.rb +75 -0
  17. data/lib/adopter/migrator.rb +96 -0
  18. data/lib/adopter/plan_builder.rb +278 -0
  19. data/lib/adopter/project_analyzer.rb +256 -0
  20. data/lib/adopter/project_detector.rb +159 -0
  21. data/lib/commands/adopt_commands.rb +43 -0
  22. data/lib/generators/automation/templates/account.tt +9 -5
  23. data/lib/generators/automation/templates/appium_caps.tt +60 -6
  24. data/lib/generators/automation/templates/home.tt +4 -4
  25. data/lib/generators/automation/templates/login.tt +61 -4
  26. data/lib/generators/automation/templates/page.tt +13 -7
  27. data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -4
  28. data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -1
  29. data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -4
  30. data/lib/generators/automation/templates/partials/visit_method.tt +11 -1
  31. data/lib/generators/automation/templates/pdp.tt +1 -1
  32. data/lib/generators/cucumber/templates/env.tt +6 -4
  33. data/lib/generators/cucumber/templates/partials/capybara_env.tt +20 -0
  34. data/lib/generators/cucumber/templates/partials/capybara_world.tt +6 -0
  35. data/lib/generators/cucumber/templates/partials/mobile_steps.tt +2 -2
  36. data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -3
  37. data/lib/generators/cucumber/templates/steps.tt +2 -2
  38. data/lib/generators/cucumber/templates/world.tt +5 -3
  39. data/lib/generators/generator.rb +14 -2
  40. data/lib/generators/helper_generator.rb +16 -3
  41. data/lib/generators/infrastructure/github_generator.rb +6 -0
  42. data/lib/generators/infrastructure/templates/github.tt +11 -7
  43. data/lib/generators/infrastructure/templates/github_appium.tt +108 -0
  44. data/lib/generators/infrastructure/templates/gitlab.tt +5 -2
  45. data/lib/generators/invoke_generators.rb +1 -0
  46. data/lib/generators/menu_generator.rb +2 -0
  47. data/lib/generators/minitest/minitest_generator.rb +23 -0
  48. data/lib/generators/minitest/templates/test.tt +93 -0
  49. data/lib/generators/rspec/templates/spec.tt +12 -10
  50. data/lib/generators/template_renderer/partial_cache.rb +116 -0
  51. data/lib/generators/template_renderer/partial_resolver.rb +103 -0
  52. data/lib/generators/template_renderer/template_error.rb +50 -0
  53. data/lib/generators/template_renderer.rb +90 -0
  54. data/lib/generators/templates/common/config.tt +2 -2
  55. data/lib/generators/templates/common/gemfile.tt +15 -3
  56. data/lib/generators/templates/common/partials/web_config.tt +1 -1
  57. data/lib/generators/templates/common/read_me.tt +3 -1
  58. data/lib/generators/templates/helpers/allure_helper.tt +2 -2
  59. data/lib/generators/templates/helpers/browser_helper.tt +1 -0
  60. data/lib/generators/templates/helpers/capybara_helper.tt +28 -0
  61. data/lib/generators/templates/helpers/driver_helper.tt +1 -1
  62. data/lib/generators/templates/helpers/partials/allure_imports.tt +3 -1
  63. data/lib/generators/templates/helpers/partials/allure_requirements.tt +3 -1
  64. data/lib/generators/templates/helpers/partials/appium_driver.tt +46 -0
  65. data/lib/generators/templates/helpers/partials/axe_driver.tt +10 -0
  66. data/lib/generators/templates/helpers/partials/browserstack_config.tt +13 -0
  67. data/lib/generators/templates/helpers/partials/driver_and_options.tt +6 -114
  68. data/lib/generators/templates/helpers/partials/quit_driver.tt +3 -1
  69. data/lib/generators/templates/helpers/partials/screenshot.tt +3 -1
  70. data/lib/generators/templates/helpers/partials/selenium_driver.tt +25 -0
  71. data/lib/generators/templates/helpers/spec_helper.tt +17 -4
  72. data/lib/generators/templates/helpers/test_helper.tt +26 -0
  73. data/lib/generators/templates/helpers/visual_spec_helper.tt +1 -1
  74. data/lib/ruby_raider.rb +5 -0
  75. data/lib/version +1 -1
  76. data/spec/adopter/adopt_menu_spec.rb +176 -0
  77. data/spec/adopter/converters/identity_converter_spec.rb +145 -0
  78. data/spec/adopter/migration_plan_spec.rb +113 -0
  79. data/spec/adopter/migrator_spec.rb +277 -0
  80. data/spec/adopter/plan_builder_spec.rb +298 -0
  81. data/spec/adopter/project_analyzer_spec.rb +337 -0
  82. data/spec/adopter/project_detector_spec.rb +295 -0
  83. data/spec/generators/fixtures/templates/test.tt +1 -0
  84. data/spec/generators/fixtures/templates/test_partial.tt +1 -0
  85. data/spec/generators/template_renderer_spec.rb +298 -0
  86. data/spec/integration/commands/scaffolding_commands_spec.rb +2 -2
  87. data/spec/integration/commands/utility_commands_spec.rb +2 -2
  88. data/spec/integration/end_to_end_spec.rb +325 -0
  89. data/spec/integration/generators/automation_generator_spec.rb +11 -11
  90. data/spec/integration/generators/common_generator_spec.rb +40 -40
  91. data/spec/integration/generators/cucumber_generator_spec.rb +7 -7
  92. data/spec/integration/generators/github_generator_spec.rb +8 -8
  93. data/spec/integration/generators/gitlab_generator_spec.rb +8 -8
  94. data/spec/integration/generators/helpers_generator_spec.rb +73 -35
  95. data/spec/integration/generators/minitest_generator_spec.rb +70 -0
  96. data/spec/integration/generators/rspec_generator_spec.rb +7 -7
  97. data/spec/integration/settings_helper.rb +1 -1
  98. data/spec/integration/spec_helper.rb +20 -2
  99. data/spec/system/capybara_spec.rb +42 -0
  100. data/spec/system/selenium_spec.rb +19 -17
  101. data/spec/system/support/system_test_helper.rb +35 -0
  102. data/spec/system/watir_spec.rb +19 -17
  103. metadata +46 -16
  104. data/.github/workflows/push_gem.yml +0 -37
  105. data/.github/workflows/selenium.yml +0 -22
  106. data/.github/workflows/watir.yml +0 -22
  107. data/lib/generators/automation/templates/partials/android_caps.tt +0 -17
  108. data/lib/generators/automation/templates/partials/cross_platform_caps.tt +0 -25
  109. data/lib/generators/automation/templates/partials/ios_caps.tt +0 -18
  110. data/lib/generators/automation/templates/partials/selenium_account.tt +0 -9
  111. data/lib/generators/automation/templates/partials/selenium_login.tt +0 -34
  112. data/lib/generators/automation/templates/partials/watir_account.tt +0 -7
  113. data/lib/generators/automation/templates/partials/watir_login.tt +0 -32
@@ -0,0 +1,176 @@
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
8
+ let(:source_dir) { 'tmp_adopt_menu_source' }
9
+ let(:output_dir) { 'tmp_adopt_menu_output' }
10
+
11
+ before do
12
+ FileUtils.mkdir_p(source_dir)
13
+ File.write("#{source_dir}/Gemfile", "gem 'rspec'\ngem 'selenium-webdriver'\ngem 'faker'\n")
14
+ FileUtils.mkdir_p("#{source_dir}/pages")
15
+ FileUtils.mkdir_p("#{source_dir}/spec")
16
+
17
+ File.write("#{source_dir}/pages/login_page.rb", <<~RUBY)
18
+ class LoginPage < BasePage
19
+ def login(user, pass)
20
+ driver.find_element(id: 'user').send_keys user
21
+ end
22
+ end
23
+ RUBY
24
+
25
+ File.write("#{source_dir}/spec/login_spec.rb", <<~RUBY)
26
+ describe 'Login' do
27
+ it 'can log in' do
28
+ expect(result).to eq 'ok'
29
+ end
30
+ end
31
+ RUBY
32
+ end
33
+
34
+ after do
35
+ FileUtils.rm_rf(source_dir)
36
+ FileUtils.rm_rf(output_dir)
37
+ end
38
+
39
+ describe '.adopt' do
40
+ let(:params) do
41
+ {
42
+ source_path: source_dir,
43
+ output_path: output_dir,
44
+ target_automation: 'selenium',
45
+ target_framework: 'rspec'
46
+ }
47
+ end
48
+
49
+ it 'runs the full adoption pipeline' do
50
+ result = described_class.adopt(params)
51
+
52
+ expect(result[:plan]).to be_a(Adopter::MigrationPlan)
53
+ expect(result[:results][:pages]).to eq(1)
54
+ expect(result[:results][:tests]).to eq(1)
55
+ expect(File).to exist("#{output_dir}/Gemfile")
56
+ expect(File).to exist("#{output_dir}/page_objects/pages/login.rb")
57
+ end
58
+
59
+ it 'merges custom gems into the generated Gemfile' do
60
+ described_class.adopt(params)
61
+
62
+ gemfile = File.read("#{output_dir}/Gemfile")
63
+ expect(gemfile).to include("gem 'faker'")
64
+ end
65
+
66
+ it 'converts page content with raider conventions' do
67
+ described_class.adopt(params)
68
+
69
+ page = File.read("#{output_dir}/page_objects/pages/login.rb")
70
+ expect(page).to include('frozen_string_literal: true')
71
+ expect(page).to include('class LoginPage < Page')
72
+ end
73
+
74
+ context 'with capybara target' do
75
+ let(:params) do
76
+ {
77
+ source_path: source_dir,
78
+ output_path: output_dir,
79
+ target_automation: 'capybara',
80
+ target_framework: 'rspec'
81
+ }
82
+ end
83
+
84
+ it 'removes driver arguments from page instantiation' do
85
+ File.write("#{source_dir}/spec/login_spec.rb", <<~RUBY)
86
+ describe 'Login' do
87
+ it 'can log in' do
88
+ @page = LoginPage.new(driver)
89
+ end
90
+ end
91
+ RUBY
92
+
93
+ result = described_class.adopt(params)
94
+ test_content = result[:plan].converted_tests.first.content
95
+ expect(test_content).to include('LoginPage.new')
96
+ expect(test_content).not_to include('LoginPage.new(driver)')
97
+ end
98
+ end
99
+
100
+ context 'with ci_platform' do
101
+ let(:params) do
102
+ {
103
+ source_path: source_dir,
104
+ output_path: output_dir,
105
+ target_automation: 'selenium',
106
+ target_framework: 'rspec',
107
+ ci_platform: 'github'
108
+ }
109
+ end
110
+
111
+ it 'generates CI configuration' do
112
+ described_class.adopt(params)
113
+
114
+ expect(File).to exist("#{output_dir}/.github")
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '.validate_params!' do
120
+ it 'raises on missing source_path' do
121
+ expect { described_class.validate_params!({}) }.to raise_error(ArgumentError, /source_path/)
122
+ end
123
+
124
+ it 'raises on missing output_path' do
125
+ expect { described_class.validate_params!(source_path: 'x') }.to raise_error(ArgumentError, /output_path/)
126
+ end
127
+
128
+ it 'raises on missing target_automation' do
129
+ expect {
130
+ described_class.validate_params!(source_path: 'x', output_path: 'y')
131
+ }.to raise_error(ArgumentError, /target_automation/)
132
+ end
133
+
134
+ it 'raises on missing target_framework' do
135
+ expect {
136
+ described_class.validate_params!(source_path: 'x', output_path: 'y', target_automation: 'selenium')
137
+ }.to raise_error(ArgumentError, /target_framework/)
138
+ end
139
+
140
+ it 'raises on invalid target_automation' do
141
+ expect {
142
+ described_class.validate_params!(
143
+ source_path: 'x', output_path: 'y',
144
+ target_automation: 'appium', target_framework: 'rspec'
145
+ )
146
+ }.to raise_error(ArgumentError, /target_automation must be/)
147
+ end
148
+
149
+ it 'raises on invalid target_framework' do
150
+ expect {
151
+ described_class.validate_params!(
152
+ source_path: 'x', output_path: 'y',
153
+ target_automation: 'selenium', target_framework: 'junit'
154
+ )
155
+ }.to raise_error(ArgumentError, /target_framework must be/)
156
+ end
157
+
158
+ it 'accepts valid params without raising' do
159
+ expect {
160
+ described_class.validate_params!(
161
+ source_path: 'x', output_path: 'y',
162
+ target_automation: 'selenium', target_framework: 'rspec'
163
+ )
164
+ }.not_to raise_error
165
+ end
166
+
167
+ it 'accepts capybara as valid automation' do
168
+ expect {
169
+ described_class.validate_params!(
170
+ source_path: 'x', output_path: 'y',
171
+ target_automation: 'capybara', target_framework: 'minitest'
172
+ )
173
+ }.not_to raise_error
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec'
4
+ require_relative '../../../lib/adopter/converters/identity_converter'
5
+
6
+ RSpec.describe Adopter::Converters::IdentityConverter do
7
+ describe '#convert_page' do
8
+ context 'targeting selenium' do
9
+ let(:converter) { described_class.new('selenium') }
10
+
11
+ it 'adds frozen_string_literal if missing' do
12
+ input = "class LoginPage < BasePage\nend\n"
13
+ result = converter.convert_page(input, {})
14
+ expect(result).to start_with('# frozen_string_literal: true')
15
+ end
16
+
17
+ it 'does not duplicate frozen_string_literal' do
18
+ input = "# frozen_string_literal: true\n\nclass LoginPage < Page\nend\n"
19
+ result = converter.convert_page(input, {})
20
+ expect(result.scan('frozen_string_literal').length).to eq(1)
21
+ end
22
+
23
+ it 'updates base class to Page' do
24
+ input = "class LoginPage < BasePage\nend\n"
25
+ result = converter.convert_page(input, {})
26
+ expect(result).to include('class LoginPage < Page')
27
+ end
28
+
29
+ it 'preserves non-page base classes' do
30
+ input = "class UserFactory < ModelFactory\nend\n"
31
+ result = converter.convert_page(input, {})
32
+ expect(result).to include('class UserFactory < ModelFactory')
33
+ end
34
+
35
+ it 'adds require_relative for abstract page' do
36
+ input = "class LoginPage < BasePage\nend\n"
37
+ result = converter.convert_page(input, {})
38
+ expect(result).to include("require_relative '../abstract/page'")
39
+ end
40
+
41
+ it 'does not duplicate page require' do
42
+ input = "require_relative '../abstract/page'\n\nclass LoginPage < Page\nend\n"
43
+ result = converter.convert_page(input, {})
44
+ expect(result.scan("require_relative '../abstract/page'").length).to eq(1)
45
+ end
46
+
47
+ it 'swaps browser to driver in page instantiation' do
48
+ input = "class Test\n @page = LoginPage.new(browser)\nend\n"
49
+ result = converter.convert_page(input, {})
50
+ expect(result).to include('LoginPage.new(driver)')
51
+ end
52
+ end
53
+
54
+ context 'targeting capybara' do
55
+ let(:converter) { described_class.new('capybara') }
56
+
57
+ it 'removes driver argument from page instantiation' do
58
+ input = "class Test\n @page = LoginPage.new(driver)\nend\n"
59
+ result = converter.convert_page(input, {})
60
+ expect(result).to include('LoginPage.new')
61
+ expect(result).not_to include('LoginPage.new(driver)')
62
+ end
63
+
64
+ it 'removes browser argument from page instantiation' do
65
+ input = " @page = LoginPage.new(browser)\n"
66
+ result = converter.convert_page(input, {})
67
+ expect(result).to include('LoginPage.new')
68
+ expect(result).not_to include('LoginPage.new(browser)')
69
+ end
70
+ end
71
+
72
+ context 'targeting watir' do
73
+ let(:converter) { described_class.new('watir') }
74
+
75
+ it 'swaps driver to browser in page instantiation' do
76
+ input = "class Test\n @page = LoginPage.new(driver)\nend\n"
77
+ result = converter.convert_page(input, {})
78
+ expect(result).to include('LoginPage.new(browser)')
79
+ end
80
+ end
81
+ end
82
+
83
+ describe '#convert_test' do
84
+ let(:converter) { described_class.new('selenium') }
85
+
86
+ it 'adds frozen_string_literal if missing' do
87
+ input = "describe 'Login' do\nend\n"
88
+ result = converter.convert_test(input, {})
89
+ expect(result).to start_with('# frozen_string_literal: true')
90
+ end
91
+
92
+ it 'updates helper require paths for selenium' do
93
+ input = "require_relative '../support/browser_setup'\n\ndescribe 'x' do; end\n"
94
+ result = converter.convert_test(input, {})
95
+ expect(result).to include("require_relative '../helpers/driver_helper'")
96
+ end
97
+
98
+ context 'targeting capybara' do
99
+ let(:converter) { described_class.new('capybara') }
100
+
101
+ it 'updates helper require paths for capybara' do
102
+ input = "require_relative '../support/driver_helper'\n\ndescribe 'x' do; end\n"
103
+ result = converter.convert_test(input, {})
104
+ expect(result).to include("require_relative '../helpers/capybara_helper'")
105
+ end
106
+ end
107
+
108
+ context 'targeting watir' do
109
+ let(:converter) { described_class.new('watir') }
110
+
111
+ it 'updates helper require paths for watir' do
112
+ input = "require_relative '../support/driver_setup'\n\ndescribe 'x' do; end\n"
113
+ result = converter.convert_test(input, {})
114
+ expect(result).to include("require_relative '../helpers/browser_helper'")
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#convert_step' do
120
+ context 'targeting capybara' do
121
+ let(:converter) { described_class.new('capybara') }
122
+
123
+ it 'removes driver argument from page instantiation in steps' do
124
+ input = <<~RUBY
125
+ Given('I am on the login page') do
126
+ @page = LoginPage.new(driver)
127
+ end
128
+ RUBY
129
+ result = converter.convert_step(input)
130
+ expect(result).to include('LoginPage.new')
131
+ expect(result).not_to include('LoginPage.new(driver)')
132
+ end
133
+ end
134
+
135
+ context 'targeting selenium' do
136
+ let(:converter) { described_class.new('selenium') }
137
+
138
+ it 'swaps browser to driver in step page instantiation' do
139
+ input = " @page = LoginPage.new(browser)\n"
140
+ result = converter.convert_step(input)
141
+ expect(result).to include('LoginPage.new(driver)')
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'rspec'
5
+ require_relative '../../lib/adopter/migration_plan'
6
+
7
+ RSpec.describe Adopter::MigrationPlan do
8
+ let(:converted_page) do
9
+ Adopter::ConvertedFile.new(
10
+ output_path: 'my_project/page_objects/pages/login.rb',
11
+ content: 'class Login < Page; end',
12
+ source_file: 'pages/login_page.rb',
13
+ conversion_notes: 'Converted from capybara to selenium'
14
+ )
15
+ end
16
+
17
+ let(:plan) do
18
+ described_class.new(
19
+ source_path: '/source',
20
+ output_path: '/output',
21
+ target_automation: 'selenium',
22
+ target_framework: 'rspec',
23
+ ci_platform: 'github',
24
+ skeleton_structure: { automation: 'selenium', framework: 'rspec', name: '/output' },
25
+ converted_pages: [converted_page],
26
+ converted_tests: [],
27
+ gemfile_additions: %w[faker httparty],
28
+ config_overrides: { browser: 'chrome', url: 'https://example.com' },
29
+ warnings: ['Complex matcher needs review'],
30
+ manual_actions: []
31
+ )
32
+ end
33
+
34
+ describe '#to_h' do
35
+ it 'serializes all fields' do
36
+ hash = plan.to_h
37
+ expect(hash[:source_path]).to eq('/source')
38
+ expect(hash[:output_path]).to eq('/output')
39
+ expect(hash[:target_automation]).to eq('selenium')
40
+ expect(hash[:target_framework]).to eq('rspec')
41
+ expect(hash[:converted_pages].length).to eq(1)
42
+ expect(hash[:gemfile_additions]).to eq(%w[faker httparty])
43
+ expect(hash[:config_overrides]).to eq(browser: 'chrome', url: 'https://example.com')
44
+ end
45
+
46
+ it 'serializes converted files as hashes' do
47
+ page_hash = plan.to_h[:converted_pages].first
48
+ expect(page_hash[:output_path]).to eq('my_project/page_objects/pages/login.rb')
49
+ expect(page_hash[:source_file]).to eq('pages/login_page.rb')
50
+ end
51
+ end
52
+
53
+ describe '#to_json' do
54
+ it 'produces valid JSON' do
55
+ parsed = JSON.parse(plan.to_json)
56
+ expect(parsed['source_path']).to eq('/source')
57
+ expect(parsed['converted_pages'].length).to eq(1)
58
+ end
59
+ end
60
+
61
+ describe '#summary' do
62
+ it 'returns counts' do
63
+ expect(plan.summary).to eq(
64
+ pages: 1,
65
+ tests: 0,
66
+ features: 0,
67
+ steps: 0,
68
+ custom_gems: 2,
69
+ warnings: 1,
70
+ manual_actions: 0
71
+ )
72
+ end
73
+ end
74
+
75
+ describe 'defaults' do
76
+ it 'initializes with empty arrays and hashes' do
77
+ empty_plan = described_class.new
78
+ expect(empty_plan.converted_pages).to eq([])
79
+ expect(empty_plan.converted_tests).to eq([])
80
+ expect(empty_plan.gemfile_additions).to eq([])
81
+ expect(empty_plan.config_overrides).to eq({})
82
+ expect(empty_plan.warnings).to eq([])
83
+ end
84
+ end
85
+ end
86
+
87
+ RSpec.describe Adopter::ConvertedFile do
88
+ it 'stores file conversion data' do
89
+ file = described_class.new(
90
+ output_path: 'project/spec/login_spec.rb',
91
+ content: 'describe "Login" do; end',
92
+ source_file: 'spec/login_spec.rb',
93
+ conversion_notes: 'Restructured'
94
+ )
95
+ expect(file.output_path).to eq('project/spec/login_spec.rb')
96
+ expect(file.content).to eq('describe "Login" do; end')
97
+ end
98
+
99
+ it 'serializes to hash' do
100
+ file = described_class.new(
101
+ output_path: 'out.rb',
102
+ content: 'code',
103
+ source_file: 'in.rb',
104
+ conversion_notes: 'notes'
105
+ )
106
+ expect(file.to_h).to eq(
107
+ output_path: 'out.rb',
108
+ content: 'code',
109
+ source_file: 'in.rb',
110
+ conversion_notes: 'notes'
111
+ )
112
+ end
113
+ end