ruby_raider 3.13.0 → 4.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 (225) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/runtime.yml +38 -0
  3. data/.github/workflows/system_tests.yml +1 -23
  4. data/.reek.yml +1 -0
  5. data/.rspec +1 -0
  6. data/Gemfile +2 -0
  7. data/README.md +16 -49
  8. data/RELEASE.md +22 -0
  9. data/bin/raider +5 -0
  10. data/lib/commands/scaffolding_commands.rb +2 -25
  11. data/lib/commands/utility_commands.rb +23 -3
  12. data/lib/generators/automation/automation_generator.rb +0 -5
  13. data/lib/generators/automation/templates/component.tt +1 -0
  14. data/lib/generators/automation/templates/home.tt +2 -2
  15. data/lib/generators/automation/templates/login.tt +6 -26
  16. data/lib/generators/automation/templates/page.tt +23 -18
  17. data/lib/generators/automation/templates/partials/home_page_selector.tt +1 -2
  18. data/lib/generators/automation/templates/partials/initialize_selector.tt +0 -8
  19. data/lib/generators/automation/templates/partials/pdp_page_selector.tt +1 -2
  20. data/lib/generators/automation/templates/partials/url_methods.tt +8 -2
  21. data/lib/generators/automation/templates/partials/visit_method.tt +0 -5
  22. data/lib/generators/automation/templates/pdp.tt +1 -9
  23. data/lib/generators/common_generator.rb +0 -2
  24. data/lib/generators/generator.rb +12 -19
  25. data/lib/generators/helper_generator.rb +21 -19
  26. data/lib/generators/infrastructure/templates/azure_pipelines.tt +1 -1
  27. data/lib/generators/infrastructure/templates/github.tt +2 -3
  28. data/lib/generators/infrastructure/templates/github_appium.tt +0 -8
  29. data/lib/generators/invoke_generators.rb +4 -23
  30. data/lib/generators/menu_generator.rb +1 -9
  31. data/lib/generators/rspec/templates/accessibility_spec.tt +1 -9
  32. data/lib/generators/rspec/templates/spec.tt +8 -24
  33. data/lib/generators/template_renderer/partial_cache.rb +3 -1
  34. data/lib/generators/template_renderer.rb +1 -1
  35. data/lib/generators/templates/common/gemfile.tt +3 -10
  36. data/lib/generators/templates/common/git_ignore.tt +0 -5
  37. data/lib/generators/templates/common/partials/mobile_config.tt +3 -1
  38. data/lib/generators/templates/common/partials/web_config.tt +10 -1
  39. data/lib/generators/templates/common/rakefile.tt +0 -15
  40. data/lib/generators/templates/common/read_me.tt +357 -250
  41. data/lib/generators/templates/common/rubocop.tt +0 -2
  42. data/lib/generators/templates/helpers/driver_helper.tt +8 -0
  43. data/lib/generators/templates/helpers/gesture_helper.tt +78 -0
  44. data/lib/generators/templates/helpers/network_helper.tt +521 -0
  45. data/lib/generators/templates/helpers/partials/quit_driver.tt +0 -2
  46. data/lib/generators/templates/helpers/partials/screenshot.tt +0 -2
  47. data/lib/generators/templates/helpers/partials/selenium_driver.tt +5 -2
  48. data/lib/generators/templates/helpers/screenshot_helper.tt +490 -0
  49. data/lib/generators/templates/helpers/spec_helper.tt +39 -29
  50. data/lib/generators/templates/helpers/trace_helper.tt +240 -95
  51. data/lib/generators/templates/helpers/video_helper.tt +3 -8
  52. data/lib/ruby_raider/runtime/actionability.rb +129 -0
  53. data/lib/ruby_raider/runtime/actionable.rb +49 -0
  54. data/lib/ruby_raider/runtime/by_role.rb +55 -0
  55. data/lib/ruby_raider/runtime/config.rb +79 -0
  56. data/lib/ruby_raider/runtime/context.rb +38 -0
  57. data/lib/ruby_raider/runtime/errors.rb +69 -0
  58. data/lib/ruby_raider/runtime/finders.rb +73 -0
  59. data/lib/ruby_raider/runtime/js/actionability.js +134 -0
  60. data/lib/ruby_raider/runtime/js/role_engine.js +315 -0
  61. data/lib/ruby_raider/runtime/locator_converter.rb +72 -0
  62. data/lib/ruby_raider/runtime/matchers/base.rb +222 -0
  63. data/lib/ruby_raider/runtime/matchers/element.rb +183 -0
  64. data/lib/ruby_raider/runtime/matchers/page.rb +71 -0
  65. data/lib/ruby_raider/runtime/matchers/presence.rb +41 -0
  66. data/lib/ruby_raider/runtime/matchers/retrying_block.rb +104 -0
  67. data/lib/ruby_raider/runtime/observer.rb +170 -0
  68. data/lib/ruby_raider/runtime/role_finder.rb +83 -0
  69. data/lib/ruby_raider/runtime/rspec.rb +78 -0
  70. data/lib/ruby_raider/runtime/selectors.rb +60 -0
  71. data/lib/ruby_raider/runtime/strict.rb +142 -0
  72. data/lib/ruby_raider/runtime/wait.rb +84 -0
  73. data/lib/ruby_raider/runtime.rb +20 -0
  74. data/lib/scaffolding/name_normalizer.rb +1 -1
  75. data/lib/scaffolding/project_detector.rb +2 -28
  76. data/lib/scaffolding/scaffolding.rb +0 -20
  77. data/lib/scaffolding/templates/component.tt +0 -14
  78. data/lib/scaffolding/templates/page_object.tt +2 -14
  79. data/lib/scaffolding/templates/spec.tt +1 -9
  80. data/lib/utilities/utilities.rb +10 -8
  81. data/lib/version +1 -1
  82. data/ruby_raider.gemspec +2 -1
  83. data/sig/commands/scaffolding_commands.rbs +0 -2
  84. data/sig/commands/utility_commands.rbs +6 -0
  85. data/sig/generators/generator.rbs +0 -4
  86. data/sig/generators/helper_generator.rbs +0 -1
  87. data/sig/generators/menu_generator.rbs +0 -1
  88. data/sig/ruby_raider/runtime/actionability.rbs +25 -0
  89. data/sig/ruby_raider/runtime/actionable.rbs +13 -0
  90. data/sig/ruby_raider/runtime/by_role.rbs +15 -0
  91. data/sig/ruby_raider/runtime/config.rbs +26 -0
  92. data/sig/ruby_raider/runtime/context.rbs +12 -0
  93. data/sig/ruby_raider/runtime/errors.rbs +36 -0
  94. data/sig/ruby_raider/runtime/finders.rbs +26 -0
  95. data/sig/ruby_raider/runtime/locator_converter.rbs +21 -0
  96. data/sig/ruby_raider/runtime/matchers/base.rbs +64 -0
  97. data/sig/ruby_raider/runtime/matchers/element.rbs +78 -0
  98. data/sig/ruby_raider/runtime/matchers/page.rbs +38 -0
  99. data/sig/ruby_raider/runtime/matchers/presence.rbs +21 -0
  100. data/sig/ruby_raider/runtime/matchers/retrying_block.rbs +27 -0
  101. data/sig/ruby_raider/runtime/observer.rbs +46 -0
  102. data/sig/ruby_raider/runtime/role_finder.rbs +18 -0
  103. data/sig/ruby_raider/runtime/selectors.rbs +21 -0
  104. data/sig/ruby_raider/runtime/strict.rbs +35 -0
  105. data/sig/ruby_raider/runtime/wait.rbs +17 -0
  106. data/sig/ruby_raider/runtime.rbs +2 -0
  107. data/sig/scaffolding/project_detector.rbs +0 -4
  108. data/sig/scaffolding/scaffolding.rbs +0 -5
  109. data/sig/utilities/utilities.rbs +2 -1
  110. data/sig/vendor/selenium_webdriver.rbs +71 -0
  111. data/spec/commands/raider_commands_spec.rb +38 -3
  112. data/spec/commands/scaffolding_commands_spec.rb +27 -0
  113. data/spec/commands/utility_commands_spec.rb +53 -0
  114. data/spec/generators/gesture_helper_spec.rb +123 -0
  115. data/spec/generators/network_helper_spec.rb +634 -0
  116. data/spec/generators/screenshot_helper_spec.rb +776 -0
  117. data/spec/generators/template_renderer_spec.rb +0 -4
  118. data/spec/generators/trace_helper_spec.rb +489 -97
  119. data/spec/generators/video_helper_spec.rb +0 -9
  120. data/spec/integration/commands/browser_update_after_creation_spec.rb +0 -8
  121. data/spec/integration/commands/scaffolding_commands_spec.rb +12 -70
  122. data/spec/integration/commands/utility_commands_spec.rb +19 -41
  123. data/spec/integration/content/azure_content_spec.rb +2 -6
  124. data/spec/integration/content/ci_content_spec.rb +1 -55
  125. data/spec/integration/content/common_content_spec.rb +1 -118
  126. data/spec/integration/content/config_content_spec.rb +34 -31
  127. data/spec/integration/content/gemfile_content_spec.rb +7 -72
  128. data/spec/integration/content/helper_content_spec.rb +105 -99
  129. data/spec/integration/content/page_content_spec.rb +85 -87
  130. data/spec/integration/content/readme_content_spec.rb +134 -75
  131. data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
  132. data/spec/integration/content/rubocop_content_spec.rb +1 -1
  133. data/spec/integration/content/syntax_validation_spec.rb +2 -2
  134. data/spec/integration/content/test_content_spec.rb +23 -147
  135. data/spec/integration/end_to_end_features_spec.rb +1 -43
  136. data/spec/integration/end_to_end_spec.rb +319 -326
  137. data/spec/integration/generators/automation_generator_spec.rb +3 -37
  138. data/spec/integration/generators/axe_addon_spec.rb +2 -35
  139. data/spec/integration/generators/azure_generator_spec.rb +1 -1
  140. data/spec/integration/generators/common_generator_spec.rb +1 -40
  141. data/spec/integration/generators/config_features_spec.rb +1 -24
  142. data/spec/integration/generators/github_generator_spec.rb +0 -12
  143. data/spec/integration/generators/helpers_generator_spec.rb +74 -80
  144. data/spec/integration/generators/invoke_generators_spec.rb +54 -45
  145. data/spec/integration/generators/rspec_generator_spec.rb +0 -6
  146. data/spec/integration/scaffolding_e2e_spec.rb +17 -179
  147. data/spec/integration/spec_helper.rb +4 -6
  148. data/spec/integration/trace_format_checker.rb +11 -2
  149. data/spec/integration/trace_page_server.rb +72 -5
  150. data/spec/menus/menu_generator_spec.rb +14 -39
  151. data/spec/runtime/actionability_spec.rb +207 -0
  152. data/spec/runtime/actionable_spec.rb +125 -0
  153. data/spec/runtime/browser/actionability_script_browser_spec.rb +150 -0
  154. data/spec/runtime/browser/actionable_browser_spec.rb +98 -0
  155. data/spec/runtime/browser/finders_browser_spec.rb +148 -0
  156. data/spec/runtime/browser/matchers_browser_spec.rb +67 -0
  157. data/spec/runtime/browser/retrying_block_browser_spec.rb +37 -0
  158. data/spec/runtime/browser/role_engine_browser_spec.rb +184 -0
  159. data/spec/runtime/browser/strict_browser_spec.rb +38 -0
  160. data/spec/runtime/by_role_spec.rb +52 -0
  161. data/spec/runtime/config_spec.rb +79 -0
  162. data/spec/runtime/context_spec.rb +73 -0
  163. data/spec/runtime/errors_spec.rb +89 -0
  164. data/spec/runtime/finders_spec.rb +165 -0
  165. data/spec/runtime/fixtures/actionability.html +70 -0
  166. data/spec/runtime/fixtures/locators.html +46 -0
  167. data/spec/runtime/fixtures/matchers.html +33 -0
  168. data/spec/runtime/fixtures/roles.html +162 -0
  169. data/spec/runtime/matchers/base_spec.rb +165 -0
  170. data/spec/runtime/matchers/element_spec.rb +277 -0
  171. data/spec/runtime/matchers/page_spec.rb +102 -0
  172. data/spec/runtime/matchers/presence_spec.rb +59 -0
  173. data/spec/runtime/matchers/retrying_block_spec.rb +165 -0
  174. data/spec/runtime/observer_spec.rb +341 -0
  175. data/spec/runtime/role_finder_spec.rb +156 -0
  176. data/spec/runtime/role_query_spec.rb +75 -0
  177. data/spec/runtime/selectors_spec.rb +135 -0
  178. data/spec/runtime/strict_spec.rb +257 -0
  179. data/spec/runtime/support/browser.rb +48 -0
  180. data/spec/runtime/support/fake_bridge.rb +117 -0
  181. data/spec/runtime/wait_spec.rb +71 -0
  182. data/spec/scaffolding/name_normalizer_spec.rb +3 -2
  183. data/spec/scaffolding/scaffold_project_detector_spec.rb +4 -43
  184. data/spec/system/selenium_spec.rb +1 -5
  185. data/spec/system/support/system_test_helper.rb +2 -5
  186. data/spec/utilities/headless_config_spec.rb +0 -7
  187. data/spec/utilities/utilities_spec.rb +24 -0
  188. metadata +113 -48
  189. data/lib/generators/automation/templates/header.tt +0 -9
  190. data/lib/generators/cucumber/cucumber_generator.rb +0 -58
  191. data/lib/generators/cucumber/templates/accessibility_feature.tt +0 -5
  192. data/lib/generators/cucumber/templates/accessibility_steps.tt +0 -17
  193. data/lib/generators/cucumber/templates/cucumber.tt +0 -1
  194. data/lib/generators/cucumber/templates/env.tt +0 -7
  195. data/lib/generators/cucumber/templates/feature.tt +0 -21
  196. data/lib/generators/cucumber/templates/partials/appium_env.tt +0 -26
  197. data/lib/generators/cucumber/templates/partials/driver_world.tt +0 -18
  198. data/lib/generators/cucumber/templates/partials/mobile_steps.tt +0 -20
  199. data/lib/generators/cucumber/templates/partials/selenium_env.tt +0 -120
  200. data/lib/generators/cucumber/templates/partials/watir_env.tt +0 -48
  201. data/lib/generators/cucumber/templates/partials/watir_world.tt +0 -4
  202. data/lib/generators/cucumber/templates/partials/web_steps.tt +0 -28
  203. data/lib/generators/cucumber/templates/steps.tt +0 -5
  204. data/lib/generators/cucumber/templates/user_factory.tt +0 -15
  205. data/lib/generators/cucumber/templates/world.tt +0 -7
  206. data/lib/generators/templates/helpers/browser_helper.tt +0 -31
  207. data/lib/generators/templates/helpers/raider_element.tt +0 -159
  208. data/lib/generators/templates/helpers/wait_helper.tt +0 -611
  209. data/lib/scaffolding/templates/feature.tt +0 -6
  210. data/lib/scaffolding/templates/steps.tt +0 -19
  211. data/sig/generators/cucumber/cucumber_generator.rbs +0 -12
  212. data/spec/fixtures/watir_master/cucumber_watir/Readme.md.snapshot +0 -175
  213. data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +0 -24
  214. data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +0 -37
  215. data/spec/fixtures/watir_master/cucumber_watir/page_objects/pages/login.rb.snapshot +0 -33
  216. data/spec/fixtures/watir_master/rspec_watir/Readme.md.snapshot +0 -174
  217. data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +0 -37
  218. data/spec/fixtures/watir_master/rspec_watir/page_objects/pages/login.rb.snapshot +0 -33
  219. data/spec/fixtures/watir_master/rspec_watir/spec/login_page_spec.rb.snapshot +0 -49
  220. data/spec/integration/content/cucumber_reporter_content_spec.rb +0 -161
  221. data/spec/integration/generators/cucumber_generator_spec.rb +0 -100
  222. data/spec/integration/helpers/wait_helper_mobile_spec.rb +0 -281
  223. data/spec/integration/helpers/wait_helper_spec.rb +0 -679
  224. data/spec/integration/helpers/wait_helper_support.rb +0 -183
  225. data/spec/system/watir_spec.rb +0 -38
@@ -20,13 +20,6 @@ end
20
20
  # End-to-end scaffolding tests that verify the scaffold command creates the right
21
21
  # files with the right content across all automation × framework combinations.
22
22
  describe 'Scaffolding E2E' do
23
- def self.scaffold_style(framework)
24
- case framework
25
- when 'rspec' then :spec
26
- when 'cucumber' then :feature
27
- end
28
- end
29
-
30
23
  # Build a minimal mock project that the scaffolding system can detect.
31
24
  def self.build_mock_project(automation, framework)
32
25
  dir = File.expand_path("tmp_scaffold_e2e_#{framework}_#{automation}")
@@ -38,21 +31,13 @@ describe 'Scaffolding E2E' do
38
31
  FileUtils.mkdir_p("#{dir}/config")
39
32
  FileUtils.mkdir_p("#{dir}/models/data")
40
33
 
41
- case framework
42
- when 'rspec' then FileUtils.mkdir_p("#{dir}/spec")
43
- when 'cucumber' then FileUtils.mkdir_p("#{dir}/features/step_definitions")
44
- FileUtils.mkdir_p("#{dir}/features/support")
45
- end
34
+ FileUtils.mkdir_p("#{dir}/spec")
46
35
 
47
36
  gems = []
48
37
  case automation
49
- when 'selenium' then gems << "gem 'selenium-webdriver'"
50
- when 'watir' then gems << "gem 'watir'"
51
- end
52
- case framework
53
- when 'rspec' then gems << "gem 'rspec'"
54
- when 'cucumber' then gems << "gem 'cucumber'"
38
+ when 'selenium' then gems << "gem 'selenium-webdriver'"
55
39
  end
40
+ gems << "gem 'rspec'"
56
41
  File.write("#{dir}/Gemfile", "#{gems.join("\n")}\n")
57
42
  File.write("#{dir}/config/config.yml", "browser: chrome\nurl: http://localhost:3000\n")
58
43
  File.write("#{dir}/page_objects/abstract/page.rb", "class Page; end\n")
@@ -100,28 +85,6 @@ describe 'Scaffolding E2E' do
100
85
  it 'does not have capybara fill_in' do
101
86
  expect(page_content).not_to include('fill_in')
102
87
  end
103
-
104
- it 'does not have watir browser.text_field' do
105
- expect(page_content).not_to include('browser.text_field')
106
- end
107
- end
108
-
109
- shared_examples 'watir scaffolded page' do
110
- it 'has browser.text_field example' do
111
- expect(page_content).to include('browser.text_field')
112
- end
113
-
114
- it 'has private section' do
115
- expect(page_content).to include('private')
116
- end
117
-
118
- it 'does not have selenium find_element' do
119
- expect(page_content).not_to include('driver.find_element')
120
- end
121
-
122
- it 'does not have capybara fill_in' do
123
- expect(page_content).not_to include('fill_in')
124
- end
125
88
  end
126
89
 
127
90
  # ──────────────────────────────────────────────
@@ -150,7 +113,7 @@ describe 'Scaffolding E2E' do
150
113
  end
151
114
 
152
115
  it 'has before block with navigation' do
153
- expect(spec_content).to include('before do')
116
+ expect(spec_content).to include('before { page.visit }')
154
117
  end
155
118
  end
156
119
 
@@ -159,8 +122,9 @@ describe 'Scaffolding E2E' do
159
122
  expect(spec_content).to include('CheckoutPage.new(driver)')
160
123
  end
161
124
 
162
- it 'navigates with driver' do
163
- expect(spec_content).to include('driver.navigate.to')
125
+ it 'visits through the page object, which joins the config url and the page path' do
126
+ expect(spec_content).to include('before { page.visit }')
127
+ expect(spec_content).not_to include('project_config', 'driver.navigate.to')
164
128
  end
165
129
 
166
130
  it 'does not use capybara visit' do
@@ -168,80 +132,6 @@ describe 'Scaffolding E2E' do
168
132
  end
169
133
  end
170
134
 
171
- shared_examples 'watir scaffolded spec' do
172
- it 'instantiates page with browser' do
173
- expect(spec_content).to include('CheckoutPage.new(browser)')
174
- end
175
-
176
- it 'navigates with browser.goto' do
177
- expect(spec_content).to include('browser.goto')
178
- end
179
- end
180
-
181
- # ──────────────────────────────────────────────
182
- # Shared examples: Feature content (Cucumber)
183
- # ──────────────────────────────────────────────
184
-
185
- shared_examples 'valid scaffolded feature' do
186
- it 'has Feature keyword' do
187
- expect(feature_content).to include('Feature: Checkout')
188
- end
189
-
190
- it 'has Scenario keyword' do
191
- expect(feature_content).to include('Scenario: Scenario name')
192
- end
193
-
194
- it 'has Given/When/Then steps' do
195
- expect(feature_content).to include('Given I am on the checkout page')
196
- expect(feature_content).to include('When I perform an action')
197
- expect(feature_content).to include('Then I see the expected result')
198
- end
199
- end
200
-
201
- # ──────────────────────────────────────────────
202
- # Shared examples: Steps content (Cucumber)
203
- # ──────────────────────────────────────────────
204
-
205
- shared_examples 'valid scaffolded steps' do
206
- it 'has frozen_string_literal' do
207
- expect(steps_content).to have_frozen_string_literal
208
- end
209
-
210
- it 'has valid Ruby syntax' do
211
- expect(steps_content).to have_valid_ruby_syntax
212
- end
213
-
214
- it 'requires the page object' do
215
- expect(steps_content).to include("require_relative '../../page_objects/pages/checkout'")
216
- end
217
-
218
- it 'has Given/When/Then blocks' do
219
- expect(steps_content).to include("Given('I am on the checkout page')")
220
- expect(steps_content).to include("When('I perform an action')")
221
- expect(steps_content).to include("Then('I see the expected result')")
222
- end
223
-
224
- it 'has pending steps' do
225
- expect(steps_content).to include("pending 'implement step'")
226
- end
227
- end
228
-
229
- shared_examples 'selenium scaffolded steps' do
230
- it 'instantiates page with driver' do
231
- expect(steps_content).to include('CheckoutPage.new(driver)')
232
- end
233
-
234
- it 'does not use capybara visit' do
235
- expect(steps_content).not_to match(/^\s+visit '/)
236
- end
237
- end
238
-
239
- shared_examples 'watir scaffolded steps' do
240
- it 'instantiates page with browser' do
241
- expect(steps_content).to include('CheckoutPage.new(browser)')
242
- end
243
- end
244
-
245
135
  # ──────────────────────────────────────────────
246
136
  # Shared examples: Helper content
247
137
  # ──────────────────────────────────────────────
@@ -287,22 +177,8 @@ describe 'Scaffolding E2E' do
287
177
  end
288
178
 
289
179
  shared_examples 'selenium scaffolded component' do
290
- it 'has driver.find_element example' do
291
- expect(component_content).to include('driver.find_element')
292
- end
293
-
294
- it 'has private section' do
295
- expect(component_content).to include('private')
296
- end
297
-
298
- it 'has content method' do
299
- expect(component_content).to include('def content')
300
- end
301
- end
302
-
303
- shared_examples 'watir scaffolded component' do
304
- it 'has browser.element example' do
305
- expect(component_content).to include('browser.element')
180
+ it 'has component.find_element example' do
181
+ expect(component_content).to include('component.find_element')
306
182
  end
307
183
 
308
184
  it 'has private section' do
@@ -318,10 +194,8 @@ describe 'Scaffolding E2E' do
318
194
  # Test matrix: automation × framework
319
195
  # ══════════════════════════════════════════════
320
196
 
321
- %w[selenium watir].each do |automation|
322
- %w[rspec cucumber].each do |framework|
323
- style = scaffold_style(framework)
324
-
197
+ %w[selenium].each do |automation|
198
+ %w[rspec].each do |framework|
325
199
  context "with #{framework} and #{automation}" do
326
200
  let(:scaffold) { ScaffoldingCommands }
327
201
 
@@ -350,56 +224,24 @@ describe 'Scaffolding E2E' do
350
224
  after do
351
225
  FileUtils.rm_f('page_objects/pages/checkout.rb')
352
226
  FileUtils.rm_f('spec/checkout_page_spec.rb')
353
- if framework == 'cucumber'
354
- FileUtils.rm_f('features/checkout.feature')
355
- FileUtils.rm_f('features/step_definitions/checkout_steps.rb')
356
- else
357
- FileUtils.rm_rf('features')
358
- FileUtils.rm_rf('spec') unless framework == 'rspec'
359
- end
360
227
  end
361
228
 
362
- # Page content validation
363
229
  let(:page_content) { File.read('page_objects/pages/checkout.rb') }
230
+ let(:spec_content) { File.read('spec/checkout_page_spec.rb') }
364
231
 
365
232
  it 'creates page object' do
366
233
  expect(Pathname.new('page_objects/pages/checkout.rb')).to be_file
367
234
  end
368
235
 
369
- if style == :spec
370
- it 'creates spec file' do
371
- expect(Pathname.new('spec/checkout_page_spec.rb')).to be_file
372
- end
373
-
374
- it 'does not create feature file' do
375
- expect(Pathname.new('features/checkout.feature')).not_to be_file
376
- end
377
- else # :feature
378
- it 'creates feature file' do
379
- expect(Pathname.new('features/checkout.feature')).to be_file
380
- end
381
-
382
- it 'creates steps file' do
383
- expect(Pathname.new('features/step_definitions/checkout_steps.rb')).to be_file
384
- end
236
+ it 'creates spec file' do
237
+ expect(Pathname.new('spec/checkout_page_spec.rb')).to be_file
385
238
  end
386
239
 
387
240
  include_examples 'valid scaffolded page'
388
241
  include_examples "#{automation} scaffolded page"
389
242
 
390
- # Test content validation (depends on scaffold style)
391
- if style == :spec
392
- let(:spec_content) { File.read('spec/checkout_page_spec.rb') }
393
- include_examples 'valid scaffolded spec'
394
- include_examples "#{automation} scaffolded spec"
395
- else # :feature
396
- let(:feature_content) { File.read('features/checkout.feature') }
397
- include_examples 'valid scaffolded feature'
398
-
399
- let(:steps_content) { File.read('features/step_definitions/checkout_steps.rb') }
400
- include_examples 'valid scaffolded steps'
401
- include_examples "#{automation} scaffolded steps"
402
- end
243
+ include_examples 'valid scaffolded spec'
244
+ include_examples "#{automation} scaffolded spec"
403
245
  end
404
246
 
405
247
  # ── Component scaffold ─────────────────
@@ -442,17 +284,13 @@ describe 'Scaffolding E2E' do
442
284
  %w[search filter].each do |name|
443
285
  FileUtils.rm_f("page_objects/pages/#{name}.rb")
444
286
  FileUtils.rm_f("spec/#{name}_page_spec.rb")
445
- FileUtils.rm_f("features/#{name}.feature")
446
- FileUtils.rm_f("features/step_definitions/#{name}_steps.rb")
447
287
  end
448
- FileUtils.rm_rf('features') unless framework == 'cucumber'
449
- FileUtils.rm_rf('spec') unless framework == 'rspec'
450
288
  end
451
289
 
452
290
  it 'generates multiple scaffolds at once' do
453
291
  scaffold.new.invoke(:scaffold, nil, %w[search filter])
454
292
  files = %w[search filter].flat_map do |name|
455
- ["page_objects/pages/#{name}.rb", style == :spec ? "spec/#{name}_page_spec.rb" : "features/#{name}.feature"]
293
+ ["page_objects/pages/#{name}.rb", "spec/#{name}_page_spec.rb"]
456
294
  end
457
295
  expect(files.reject { |file| File.file?(file) }).to be_empty
458
296
  end
@@ -5,21 +5,19 @@ require 'rspec'
5
5
  require_relative '../../lib/generators/invoke_generators'
6
6
  require_relative 'settings_helper'
7
7
 
8
- AUTOMATION_TYPES = %w[android ios selenium watir cross_platform].freeze
9
- FRAMEWORKS = %w[cucumber rspec].freeze
8
+ AUTOMATION_TYPES = %w[android ios selenium cross_platform].freeze
9
+ FRAMEWORKS = %w[rspec].freeze
10
10
 
11
11
  # Named constants for readable spec references — no more fragile numeric indices
12
12
  module FrameworkIndex
13
- CUCUMBER = FRAMEWORKS[0]
14
- RSPEC = FRAMEWORKS[1]
13
+ RSPEC = FRAMEWORKS[0]
15
14
  end
16
15
 
17
16
  module AutomationIndex
18
17
  ANDROID = AUTOMATION_TYPES[0]
19
18
  IOS = AUTOMATION_TYPES[1]
20
19
  SELENIUM = AUTOMATION_TYPES[2]
21
- WATIR = AUTOMATION_TYPES[3]
22
- CROSS_PLATFORM = AUTOMATION_TYPES[4]
20
+ CROSS_PLATFORM = AUTOMATION_TYPES[3]
23
21
  end
24
22
 
25
23
  # Lazy project generation — only generates a project the first time it's needed.
@@ -10,7 +10,7 @@ module TraceFormatChecker
10
10
  ENTRY_NAME = %r{\Atrace\.json\z|\Asteps/\d{4}\.(jpg|png|html|xml)\z}
11
11
  TIMESTAMP = /\A\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z\z/
12
12
  KINDS = %w[navigate action group failure].freeze
13
- STRATEGIES = %w[id css xpath name link_text partial_link_text class_name tag_name].freeze
13
+ STRATEGIES = %w[id css xpath name link_text partial_link_text class_name tag_name role label placeholder alt title text test_id].freeze
14
14
  # The required keys and the types each may have: the top level, then every step.
15
15
  FIELDS = {
16
16
  top: {
@@ -42,7 +42,7 @@ module TraceFormatChecker
42
42
  problems.concat(error_rules('test', trace['test'])) if trace['test'].is_a?(Hash)
43
43
  return problems unless trace['steps'].is_a?(Array)
44
44
 
45
- problems.concat(steps(trace['steps'], names)).concat(events(trace))
45
+ problems.concat(steps(trace['steps'], names)).concat(events(trace)).concat(mocked_rules(trace['network']))
46
46
  rescue Zip::Error, JSON::ParserError, Errno::ENOENT => e
47
47
  ["not a readable trace.zip: #{e.class}: #{e.message}"]
48
48
  end
@@ -129,6 +129,15 @@ module TraceFormatChecker
129
129
  end
130
130
  end
131
131
 
132
+ # mocked is optional on a network entry: "fulfilled" or "failed".
133
+ def self.mocked_rules(network)
134
+ Array(network).each_with_index.filter_map do |entry, position|
135
+ next unless entry.is_a?(Hash) && entry.key?('mocked') && !%w[fulfilled failed].include?(entry['mocked'])
136
+
137
+ "network[#{position}].mocked #{entry['mocked'].inspect} is not fulfilled or failed"
138
+ end
139
+ end
140
+
132
141
  def self.events(trace)
133
142
  count = [trace['steps'].size, 1].max
134
143
  %w[network console].flat_map do |list|
@@ -3,7 +3,8 @@
3
3
  require 'socket'
4
4
 
5
5
  # Serves the trace e2e page over HTTP on 127.0.0.1 (never file://), so the browser makes
6
- # real requests that WebDriver BiDi reports: the page, its same-origin stylesheet and /api.
6
+ # real requests that WebDriver BiDi reports: the page (behind a redirect at /start), its same-origin
7
+ # stylesheet and /api.
7
8
  class TracePageServer
8
9
  CSS_RULE = '#title { color: rgb(1, 2, 3); }'
9
10
  PAGE = <<~HTML
@@ -17,7 +18,7 @@ class TracePageServer
17
18
  <h1 id="title">Trace</h1>
18
19
  <input type="password" id="pw" placeholder="Password">
19
20
  <input type="text" id="pw2" autocomplete="current-password" placeholder="Confirm password">
20
- <input id="name" placeholder="Name">
21
+ <form id="form" onsubmit="return false"><input id="name" placeholder="Name"></form>
21
22
  <button id="go">Go</button>
22
23
  <p id="status"></p>
23
24
  <script>
@@ -30,14 +31,58 @@ class TracePageServer
30
31
  </body>
31
32
  </html>
32
33
  HTML
34
+ # The network mocking e2e page: each button fetches one URL and writes the outcome into its <p>.
35
+ # /api/cors is cross-origin and unresolvable, and the custom header forces a preflight: it only works mocked.
36
+ MOCK_PAGE = <<~HTML
37
+ <!DOCTYPE html>
38
+ <html>
39
+ <head><title>Mock e2e</title></head>
40
+ <body>
41
+ <button id="users">Users</button><p id="users-out"></p>
42
+ <button id="fail">Fail</button><p id="fail-out"></p>
43
+ <button id="echo">Echo</button><p id="echo-out"></p>
44
+ <button id="cors">Cors</button><p id="cors-out"></p>
45
+ <script>
46
+ function show(id, promise, format, failure) {
47
+ promise.then(function (response) { return response.json(); })
48
+ .then(function (body) { document.getElementById(id + '-out').textContent = format(body); })
49
+ .catch(function () { document.getElementById(id + '-out').textContent = failure; });
50
+ }
51
+ document.getElementById('users').addEventListener('click', function () {
52
+ show('users', fetch('/api/users'), function (body) { return body.users.join(','); }, 'users failed');
53
+ });
54
+ document.getElementById('fail').addEventListener('click', function () {
55
+ show('fail', fetch('/api/fail'), function () { return 'fail reached the server'; }, 'request failed');
56
+ });
57
+ document.getElementById('echo').addEventListener('click', function () {
58
+ show('echo', fetch('/api/echo', { headers: { 'x-raider': 'no' } }),
59
+ function (body) { return 'echo ' + body.raider; }, 'echo failed');
60
+ });
61
+ document.getElementById('cors').addEventListener('click', function () {
62
+ show('cors', fetch('http://mock.raider.test/api/cors', { headers: { 'x-a': '1' } }),
63
+ function (body) { return 'cors ' + body.ok; }, 'cors failed');
64
+ });
65
+ </script>
66
+ </body>
67
+ </html>
68
+ HTML
33
69
  ROUTES = {
70
+ '/mock' => ['200 OK', 'text/html; charset=utf-8', MOCK_PAGE],
71
+ '/api/users' => ['200 OK', 'application/json', '{"users":["real"]}'],
72
+ '/api/fail' => ['200 OK', 'application/json', '{"ok":true}'],
34
73
  '/' => ['200 OK', 'text/html; charset=utf-8', PAGE],
35
74
  '/style.css' => ['200 OK', 'text/css', "#{CSS_RULE}\n"],
36
75
  '/api' => ['200 OK', 'application/json', '{"status":"answered by /api"}']
37
76
  }.freeze
77
+ # A first page that redirects, as a login does; BiDi gives both hops the same request id.
78
+ REDIRECTS = { '/start' => '/' }.freeze
38
79
  NOT_FOUND = ['404 Not Found', 'text/plain', 'not found'].freeze
39
80
  # /api answers this late, so a network entry's duration_ms has something to measure.
40
81
  API_DELAY = 0.3
82
+ # The page answers this late too, so the navigation's duration_ms is measurable.
83
+ PAGE_DELAY = 0.1
84
+
85
+ DELAYS = { '/' => PAGE_DELAY, '/api' => API_DELAY }.freeze
41
86
 
42
87
  attr_reader :url
43
88
 
@@ -64,12 +109,34 @@ class TracePageServer
64
109
  nil
65
110
  end
66
111
 
112
+ def redirect(socket, location)
113
+ socket.write("HTTP/1.1 302 Found\r\nLocation: #{location}\r\nContent-Length: 0\r\n" \
114
+ "Cache-Control: no-store\r\nConnection: close\r\n\r\n")
115
+ end
116
+
117
+ # { lowercase name => value } of the request headers.
118
+ def read_headers(socket)
119
+ headers = {}
120
+ while (line = socket.gets) && line != "\r\n"
121
+ name, value = line.split(':', 2)
122
+ headers[name.downcase] = value.to_s.strip
123
+ end
124
+ headers
125
+ end
126
+
127
+ # Answers with the request's x-raider header.
128
+ def echo(headers)
129
+ ['200 OK', 'application/json', %({"raider":"#{headers.fetch('x-raider', 'none')}"})]
130
+ end
131
+
67
132
  def answer(socket)
68
133
  request_line = socket.gets.to_s
69
- nil while (line = socket.gets) && line != "\r\n"
134
+ headers = read_headers(socket)
70
135
  path = request_line.split[1].to_s.split('?').first
71
- status, type, body = ROUTES.fetch(path, NOT_FOUND)
72
- sleep(API_DELAY) if path == '/api'
136
+ return redirect(socket, REDIRECTS[path]) if REDIRECTS.key?(path)
137
+
138
+ status, type, body = path == '/api/echo' ? echo(headers) : ROUTES.fetch(path, NOT_FOUND)
139
+ sleep(DELAYS.fetch(path, 0))
73
140
  socket.write("HTTP/1.1 #{status}\r\nContent-Type: #{type}\r\nContent-Length: #{body.bytesize}\r\n" \
74
141
  "Cache-Control: no-store\r\nConnection: close\r\n\r\n#{body}")
75
142
  rescue StandardError => e
@@ -28,20 +28,6 @@ RSpec.describe MenuGenerator do
28
28
  end
29
29
 
30
30
  describe '#choose_test_framework' do
31
- context 'with a web automation' do
32
- it 'presents test framework selection' do
33
- menu = double('menu') # rubocop:disable RSpec/VerifiedDoubles
34
- allow(menu).to receive(:choice)
35
- allow(prompt).to receive(:select).with('Please select your test framework').and_yield(menu)
36
-
37
- menu_generator.choose_test_framework('selenium')
38
-
39
- expect(menu).to have_received(:choice).with(:Cucumber, anything)
40
- expect(menu).to have_received(:choice).with(:Rspec, anything)
41
- expect(menu).to have_received(:choice).with(:Quit, anything)
42
- end
43
- end
44
-
45
31
  context 'with appium' do
46
32
  it 'presents mobile platform selection' do
47
33
  menu = double('menu') # rubocop:disable RSpec/VerifiedDoubles
@@ -108,26 +94,6 @@ RSpec.describe MenuGenerator do
108
94
  end
109
95
  end
110
96
 
111
- describe 'full flow: watir + cucumber' do
112
- it 'calls set_up_framework with correct options' do
113
- allow(menu_generator).to receive(:generate_framework)
114
- allow(menu_generator).to receive(:system)
115
- allow(prompt).to receive(:say)
116
- allow(prompt).to receive(:yes?).and_return(false)
117
-
118
- menu_generator.send(:create_framework, 'Cucumber', 'watir')
119
-
120
- expect(menu_generator).to have_received(:generate_framework).with(
121
- hash_including(
122
- automation: 'watir',
123
- framework: 'cucumber',
124
- accessibility: false,
125
- name: 'test_project'
126
- )
127
- )
128
- end
129
- end
130
-
131
97
  describe 'full flow: selenium + rspec + accessibility' do
132
98
  it 'passes accessibility flag through' do
133
99
  allow(menu_generator).to receive(:generate_framework)
@@ -219,14 +185,23 @@ RSpec.describe MenuGenerator do
219
185
  allow(prompt).to receive(:yes?).and_return(false)
220
186
  end
221
187
 
222
- it 'is asked after the test framework question' do
223
- allow(prompt).to receive(:select).with('Please select your test framework').and_yield(menu)
224
- allow(menu).to receive(:choice).with(:Rspec, anything) { |_, action| action.call }
188
+ it 'asks for example tests straight away, with no test framework question' do
189
+ allow(prompt).to receive(:select)
225
190
  allow(menu_generator).to receive(:select_examples)
226
191
 
227
192
  menu_generator.choose_test_framework('selenium')
228
193
 
229
194
  expect(menu_generator).to have_received(:select_examples).with('Rspec', 'selenium')
195
+ expect(prompt).not_to have_received(:select).with('Please select your test framework')
196
+ end
197
+
198
+ it 'prompts for example tests without ever offering Cucumber' do
199
+ allow(prompt).to receive(:select).with('Do you want example tests?').and_yield(menu)
200
+
201
+ menu_generator.choose_test_framework('selenium')
202
+
203
+ expect(menu).not_to have_received(:choice).with(:Cucumber, anything)
204
+ expect(menu).to have_received(:choice).with(:Yes, anything)
230
205
  end
231
206
 
232
207
  it 'offers Yes first, so yes is the default' do
@@ -261,10 +236,10 @@ RSpec.describe MenuGenerator do
261
236
  allow(prompt).to receive(:select).with('Do you want example tests?').and_yield(menu)
262
237
  allow(menu).to receive(:choice).with(:No, anything) { |_, action| action.call }
263
238
 
264
- menu_generator.send(:select_examples, 'Cucumber', 'ios')
239
+ menu_generator.send(:select_examples, 'Rspec', 'ios')
265
240
 
266
241
  expect(menu_generator).to have_received(:generate_framework).with(
267
- hash_including(automation: 'ios', framework: 'cucumber', examples: false)
242
+ hash_including(automation: 'ios', framework: 'rspec', examples: false)
268
243
  )
269
244
  end
270
245
  end