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.
- checksums.yaml +4 -4
- data/.github/workflows/runtime.yml +38 -0
- data/.github/workflows/system_tests.yml +1 -23
- data/.reek.yml +1 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/README.md +16 -49
- data/RELEASE.md +22 -0
- data/bin/raider +5 -0
- data/lib/commands/scaffolding_commands.rb +2 -25
- data/lib/commands/utility_commands.rb +23 -3
- data/lib/generators/automation/automation_generator.rb +0 -5
- data/lib/generators/automation/templates/component.tt +1 -0
- data/lib/generators/automation/templates/home.tt +2 -2
- data/lib/generators/automation/templates/login.tt +6 -26
- data/lib/generators/automation/templates/page.tt +23 -18
- data/lib/generators/automation/templates/partials/home_page_selector.tt +1 -2
- data/lib/generators/automation/templates/partials/initialize_selector.tt +0 -8
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +1 -2
- data/lib/generators/automation/templates/partials/url_methods.tt +8 -2
- data/lib/generators/automation/templates/partials/visit_method.tt +0 -5
- data/lib/generators/automation/templates/pdp.tt +1 -9
- data/lib/generators/common_generator.rb +0 -2
- data/lib/generators/generator.rb +12 -19
- data/lib/generators/helper_generator.rb +21 -19
- data/lib/generators/infrastructure/templates/azure_pipelines.tt +1 -1
- data/lib/generators/infrastructure/templates/github.tt +2 -3
- data/lib/generators/infrastructure/templates/github_appium.tt +0 -8
- data/lib/generators/invoke_generators.rb +4 -23
- data/lib/generators/menu_generator.rb +1 -9
- data/lib/generators/rspec/templates/accessibility_spec.tt +1 -9
- data/lib/generators/rspec/templates/spec.tt +8 -24
- data/lib/generators/template_renderer/partial_cache.rb +3 -1
- data/lib/generators/template_renderer.rb +1 -1
- data/lib/generators/templates/common/gemfile.tt +3 -10
- data/lib/generators/templates/common/git_ignore.tt +0 -5
- data/lib/generators/templates/common/partials/mobile_config.tt +3 -1
- data/lib/generators/templates/common/partials/web_config.tt +10 -1
- data/lib/generators/templates/common/rakefile.tt +0 -15
- data/lib/generators/templates/common/read_me.tt +357 -250
- data/lib/generators/templates/common/rubocop.tt +0 -2
- data/lib/generators/templates/helpers/driver_helper.tt +8 -0
- data/lib/generators/templates/helpers/gesture_helper.tt +78 -0
- data/lib/generators/templates/helpers/network_helper.tt +521 -0
- data/lib/generators/templates/helpers/partials/quit_driver.tt +0 -2
- data/lib/generators/templates/helpers/partials/screenshot.tt +0 -2
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +5 -2
- data/lib/generators/templates/helpers/screenshot_helper.tt +490 -0
- data/lib/generators/templates/helpers/spec_helper.tt +39 -29
- data/lib/generators/templates/helpers/trace_helper.tt +240 -95
- data/lib/generators/templates/helpers/video_helper.tt +3 -8
- data/lib/ruby_raider/runtime/actionability.rb +129 -0
- data/lib/ruby_raider/runtime/actionable.rb +49 -0
- data/lib/ruby_raider/runtime/by_role.rb +55 -0
- data/lib/ruby_raider/runtime/config.rb +79 -0
- data/lib/ruby_raider/runtime/context.rb +38 -0
- data/lib/ruby_raider/runtime/errors.rb +69 -0
- data/lib/ruby_raider/runtime/finders.rb +73 -0
- data/lib/ruby_raider/runtime/js/actionability.js +134 -0
- data/lib/ruby_raider/runtime/js/role_engine.js +315 -0
- data/lib/ruby_raider/runtime/locator_converter.rb +72 -0
- data/lib/ruby_raider/runtime/matchers/base.rb +222 -0
- data/lib/ruby_raider/runtime/matchers/element.rb +183 -0
- data/lib/ruby_raider/runtime/matchers/page.rb +71 -0
- data/lib/ruby_raider/runtime/matchers/presence.rb +41 -0
- data/lib/ruby_raider/runtime/matchers/retrying_block.rb +104 -0
- data/lib/ruby_raider/runtime/observer.rb +170 -0
- data/lib/ruby_raider/runtime/role_finder.rb +83 -0
- data/lib/ruby_raider/runtime/rspec.rb +78 -0
- data/lib/ruby_raider/runtime/selectors.rb +60 -0
- data/lib/ruby_raider/runtime/strict.rb +142 -0
- data/lib/ruby_raider/runtime/wait.rb +84 -0
- data/lib/ruby_raider/runtime.rb +20 -0
- data/lib/scaffolding/name_normalizer.rb +1 -1
- data/lib/scaffolding/project_detector.rb +2 -28
- data/lib/scaffolding/scaffolding.rb +0 -20
- data/lib/scaffolding/templates/component.tt +0 -14
- data/lib/scaffolding/templates/page_object.tt +2 -14
- data/lib/scaffolding/templates/spec.tt +1 -9
- data/lib/utilities/utilities.rb +10 -8
- data/lib/version +1 -1
- data/ruby_raider.gemspec +2 -1
- data/sig/commands/scaffolding_commands.rbs +0 -2
- data/sig/commands/utility_commands.rbs +6 -0
- data/sig/generators/generator.rbs +0 -4
- data/sig/generators/helper_generator.rbs +0 -1
- data/sig/generators/menu_generator.rbs +0 -1
- data/sig/ruby_raider/runtime/actionability.rbs +25 -0
- data/sig/ruby_raider/runtime/actionable.rbs +13 -0
- data/sig/ruby_raider/runtime/by_role.rbs +15 -0
- data/sig/ruby_raider/runtime/config.rbs +26 -0
- data/sig/ruby_raider/runtime/context.rbs +12 -0
- data/sig/ruby_raider/runtime/errors.rbs +36 -0
- data/sig/ruby_raider/runtime/finders.rbs +26 -0
- data/sig/ruby_raider/runtime/locator_converter.rbs +21 -0
- data/sig/ruby_raider/runtime/matchers/base.rbs +64 -0
- data/sig/ruby_raider/runtime/matchers/element.rbs +78 -0
- data/sig/ruby_raider/runtime/matchers/page.rbs +38 -0
- data/sig/ruby_raider/runtime/matchers/presence.rbs +21 -0
- data/sig/ruby_raider/runtime/matchers/retrying_block.rbs +27 -0
- data/sig/ruby_raider/runtime/observer.rbs +46 -0
- data/sig/ruby_raider/runtime/role_finder.rbs +18 -0
- data/sig/ruby_raider/runtime/selectors.rbs +21 -0
- data/sig/ruby_raider/runtime/strict.rbs +35 -0
- data/sig/ruby_raider/runtime/wait.rbs +17 -0
- data/sig/ruby_raider/runtime.rbs +2 -0
- data/sig/scaffolding/project_detector.rbs +0 -4
- data/sig/scaffolding/scaffolding.rbs +0 -5
- data/sig/utilities/utilities.rbs +2 -1
- data/sig/vendor/selenium_webdriver.rbs +71 -0
- data/spec/commands/raider_commands_spec.rb +38 -3
- data/spec/commands/scaffolding_commands_spec.rb +27 -0
- data/spec/commands/utility_commands_spec.rb +53 -0
- data/spec/generators/gesture_helper_spec.rb +123 -0
- data/spec/generators/network_helper_spec.rb +634 -0
- data/spec/generators/screenshot_helper_spec.rb +776 -0
- data/spec/generators/template_renderer_spec.rb +0 -4
- data/spec/generators/trace_helper_spec.rb +489 -97
- data/spec/generators/video_helper_spec.rb +0 -9
- data/spec/integration/commands/browser_update_after_creation_spec.rb +0 -8
- data/spec/integration/commands/scaffolding_commands_spec.rb +12 -70
- data/spec/integration/commands/utility_commands_spec.rb +19 -41
- data/spec/integration/content/azure_content_spec.rb +2 -6
- data/spec/integration/content/ci_content_spec.rb +1 -55
- data/spec/integration/content/common_content_spec.rb +1 -118
- data/spec/integration/content/config_content_spec.rb +34 -31
- data/spec/integration/content/gemfile_content_spec.rb +7 -72
- data/spec/integration/content/helper_content_spec.rb +105 -99
- data/spec/integration/content/page_content_spec.rb +85 -87
- data/spec/integration/content/readme_content_spec.rb +134 -75
- data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
- data/spec/integration/content/rubocop_content_spec.rb +1 -1
- data/spec/integration/content/syntax_validation_spec.rb +2 -2
- data/spec/integration/content/test_content_spec.rb +23 -147
- data/spec/integration/end_to_end_features_spec.rb +1 -43
- data/spec/integration/end_to_end_spec.rb +319 -326
- data/spec/integration/generators/automation_generator_spec.rb +3 -37
- data/spec/integration/generators/axe_addon_spec.rb +2 -35
- data/spec/integration/generators/azure_generator_spec.rb +1 -1
- data/spec/integration/generators/common_generator_spec.rb +1 -40
- data/spec/integration/generators/config_features_spec.rb +1 -24
- data/spec/integration/generators/github_generator_spec.rb +0 -12
- data/spec/integration/generators/helpers_generator_spec.rb +74 -80
- data/spec/integration/generators/invoke_generators_spec.rb +54 -45
- data/spec/integration/generators/rspec_generator_spec.rb +0 -6
- data/spec/integration/scaffolding_e2e_spec.rb +17 -179
- data/spec/integration/spec_helper.rb +4 -6
- data/spec/integration/trace_format_checker.rb +11 -2
- data/spec/integration/trace_page_server.rb +72 -5
- data/spec/menus/menu_generator_spec.rb +14 -39
- data/spec/runtime/actionability_spec.rb +207 -0
- data/spec/runtime/actionable_spec.rb +125 -0
- data/spec/runtime/browser/actionability_script_browser_spec.rb +150 -0
- data/spec/runtime/browser/actionable_browser_spec.rb +98 -0
- data/spec/runtime/browser/finders_browser_spec.rb +148 -0
- data/spec/runtime/browser/matchers_browser_spec.rb +67 -0
- data/spec/runtime/browser/retrying_block_browser_spec.rb +37 -0
- data/spec/runtime/browser/role_engine_browser_spec.rb +184 -0
- data/spec/runtime/browser/strict_browser_spec.rb +38 -0
- data/spec/runtime/by_role_spec.rb +52 -0
- data/spec/runtime/config_spec.rb +79 -0
- data/spec/runtime/context_spec.rb +73 -0
- data/spec/runtime/errors_spec.rb +89 -0
- data/spec/runtime/finders_spec.rb +165 -0
- data/spec/runtime/fixtures/actionability.html +70 -0
- data/spec/runtime/fixtures/locators.html +46 -0
- data/spec/runtime/fixtures/matchers.html +33 -0
- data/spec/runtime/fixtures/roles.html +162 -0
- data/spec/runtime/matchers/base_spec.rb +165 -0
- data/spec/runtime/matchers/element_spec.rb +277 -0
- data/spec/runtime/matchers/page_spec.rb +102 -0
- data/spec/runtime/matchers/presence_spec.rb +59 -0
- data/spec/runtime/matchers/retrying_block_spec.rb +165 -0
- data/spec/runtime/observer_spec.rb +341 -0
- data/spec/runtime/role_finder_spec.rb +156 -0
- data/spec/runtime/role_query_spec.rb +75 -0
- data/spec/runtime/selectors_spec.rb +135 -0
- data/spec/runtime/strict_spec.rb +257 -0
- data/spec/runtime/support/browser.rb +48 -0
- data/spec/runtime/support/fake_bridge.rb +117 -0
- data/spec/runtime/wait_spec.rb +71 -0
- data/spec/scaffolding/name_normalizer_spec.rb +3 -2
- data/spec/scaffolding/scaffold_project_detector_spec.rb +4 -43
- data/spec/system/selenium_spec.rb +1 -5
- data/spec/system/support/system_test_helper.rb +2 -5
- data/spec/utilities/headless_config_spec.rb +0 -7
- data/spec/utilities/utilities_spec.rb +24 -0
- metadata +113 -48
- data/lib/generators/automation/templates/header.tt +0 -9
- data/lib/generators/cucumber/cucumber_generator.rb +0 -58
- data/lib/generators/cucumber/templates/accessibility_feature.tt +0 -5
- data/lib/generators/cucumber/templates/accessibility_steps.tt +0 -17
- data/lib/generators/cucumber/templates/cucumber.tt +0 -1
- data/lib/generators/cucumber/templates/env.tt +0 -7
- data/lib/generators/cucumber/templates/feature.tt +0 -21
- data/lib/generators/cucumber/templates/partials/appium_env.tt +0 -26
- data/lib/generators/cucumber/templates/partials/driver_world.tt +0 -18
- data/lib/generators/cucumber/templates/partials/mobile_steps.tt +0 -20
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +0 -120
- data/lib/generators/cucumber/templates/partials/watir_env.tt +0 -48
- data/lib/generators/cucumber/templates/partials/watir_world.tt +0 -4
- data/lib/generators/cucumber/templates/partials/web_steps.tt +0 -28
- data/lib/generators/cucumber/templates/steps.tt +0 -5
- data/lib/generators/cucumber/templates/user_factory.tt +0 -15
- data/lib/generators/cucumber/templates/world.tt +0 -7
- data/lib/generators/templates/helpers/browser_helper.tt +0 -31
- data/lib/generators/templates/helpers/raider_element.tt +0 -159
- data/lib/generators/templates/helpers/wait_helper.tt +0 -611
- data/lib/scaffolding/templates/feature.tt +0 -6
- data/lib/scaffolding/templates/steps.tt +0 -19
- data/sig/generators/cucumber/cucumber_generator.rbs +0 -12
- data/spec/fixtures/watir_master/cucumber_watir/Readme.md.snapshot +0 -175
- data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +0 -24
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +0 -37
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/pages/login.rb.snapshot +0 -33
- data/spec/fixtures/watir_master/rspec_watir/Readme.md.snapshot +0 -174
- data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +0 -37
- data/spec/fixtures/watir_master/rspec_watir/page_objects/pages/login.rb.snapshot +0 -33
- data/spec/fixtures/watir_master/rspec_watir/spec/login_page_spec.rb.snapshot +0 -49
- data/spec/integration/content/cucumber_reporter_content_spec.rb +0 -161
- data/spec/integration/generators/cucumber_generator_spec.rb +0 -100
- data/spec/integration/helpers/wait_helper_mobile_spec.rb +0 -281
- data/spec/integration/helpers/wait_helper_spec.rb +0 -679
- data/spec/integration/helpers/wait_helper_support.rb +0 -183
- 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
|
-
|
|
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'
|
|
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
|
|
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 '
|
|
163
|
-
expect(spec_content).to include('
|
|
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
|
|
291
|
-
expect(component_content).to include('
|
|
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
|
|
322
|
-
%w[rspec
|
|
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
|
-
|
|
370
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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",
|
|
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
|
|
9
|
-
FRAMEWORKS = %w[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
134
|
+
headers = read_headers(socket)
|
|
70
135
|
path = request_line.split[1].to_s.split('?').first
|
|
71
|
-
|
|
72
|
-
|
|
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 '
|
|
223
|
-
allow(prompt).to receive(:select)
|
|
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, '
|
|
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: '
|
|
242
|
+
hash_including(automation: 'ios', framework: 'rspec', examples: false)
|
|
268
243
|
)
|
|
269
244
|
end
|
|
270
245
|
end
|