ruby_raider 3.11.0 ā 3.12.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/reek.yml +5 -1
- data/.github/workflows/release.yml +2 -1
- data/.github/workflows/system_tests.yml +1 -1
- data/.reek.yml +13 -0
- data/README.md +43 -4
- data/lib/commands/utility_commands.rb +1 -1
- data/lib/generators/automation/automation_generator.rb +15 -10
- data/lib/generators/automation/templates/appium_caps.tt +2 -4
- data/lib/generators/automation/templates/home.tt +1 -2
- data/lib/generators/automation/templates/login.tt +6 -6
- data/lib/generators/automation/templates/page.tt +10 -3
- data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -6
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -6
- data/lib/generators/automation/templates/pdp.tt +3 -2
- data/lib/generators/cucumber/cucumber_generator.rb +15 -2
- data/lib/generators/cucumber/templates/partials/driver_world.tt +9 -0
- data/lib/generators/cucumber/templates/partials/mobile_steps.tt +1 -1
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +4 -0
- data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -0
- data/lib/generators/generator.rb +15 -4
- data/lib/generators/helper_generator.rb +8 -0
- data/lib/generators/invoke_generators.rb +23 -0
- data/lib/generators/menu_generator.rb +26 -14
- data/lib/generators/rspec/rspec_generator.rb +10 -3
- data/lib/generators/rspec/templates/spec.tt +15 -1
- data/lib/generators/template_renderer/template_error.rb +8 -8
- data/lib/generators/templates/common/partials/mobile_config.tt +1 -0
- data/lib/generators/templates/common/partials/web_config.tt +1 -1
- data/lib/generators/templates/common/read_me.tt +508 -24
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +1 -3
- data/lib/generators/templates/helpers/raider_element.tt +159 -0
- data/lib/generators/templates/helpers/session_state.tt +45 -5
- data/lib/generators/templates/helpers/spec_helper.tt +9 -1
- data/lib/generators/templates/helpers/wait_helper.tt +470 -0
- data/lib/ruby_raider/ffmpeg.rb +7 -7
- data/lib/ruby_raider.rb +2 -0
- data/lib/scaffolding/project_detector.rb +6 -0
- data/lib/scaffolding/scaffolding.rb +4 -0
- data/lib/scaffolding/templates/component.tt +4 -0
- data/lib/scaffolding/templates/page_object.tt +8 -0
- data/lib/version +1 -1
- data/sig/generators/generator.rbs +2 -0
- data/sig/scaffolding/project_detector.rbs +1 -0
- data/sig/scaffolding/scaffolding.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +24 -0
- data/spec/fixtures/watir_master/cucumber_watir/Readme.md.snapshot +175 -0
- data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +24 -0
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +39 -0
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/pages/login.rb.snapshot +33 -0
- data/spec/fixtures/watir_master/rspec_watir/Readme.md.snapshot +174 -0
- data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +39 -0
- data/spec/fixtures/watir_master/rspec_watir/page_objects/pages/login.rb.snapshot +33 -0
- data/spec/fixtures/watir_master/rspec_watir/spec/login_page_spec.rb.snapshot +49 -0
- data/spec/integration/commands/scaffolding_commands_spec.rb +49 -0
- data/spec/integration/commands/utility_commands_spec.rb +8 -0
- data/spec/integration/content/config_content_spec.rb +44 -0
- data/spec/integration/content/helper_content_spec.rb +79 -0
- data/spec/integration/content/page_content_spec.rb +75 -4
- data/spec/integration/content/readme_content_spec.rb +126 -5
- data/spec/integration/content/syntax_validation_spec.rb +23 -0
- data/spec/integration/content/test_content_spec.rb +57 -0
- data/spec/integration/end_to_end_spec.rb +50 -28
- data/spec/integration/generators/automation_generator_spec.rb +50 -0
- data/spec/integration/generators/common_generator_spec.rb +20 -0
- data/spec/integration/generators/config_features_spec.rb +2 -2
- data/spec/integration/generators/cucumber_generator_spec.rb +36 -0
- data/spec/integration/generators/helpers_generator_spec.rb +38 -0
- data/spec/integration/generators/invoke_generators_spec.rb +70 -0
- data/spec/integration/generators/rspec_generator_spec.rb +20 -0
- data/spec/integration/helpers/session_state_spec.rb +46 -0
- data/spec/integration/helpers/wait_helper_mobile_spec.rb +281 -0
- data/spec/integration/helpers/wait_helper_spec.rb +496 -0
- data/spec/integration/helpers/wait_helper_support.rb +183 -0
- data/spec/integration/settings_helper.rb +4 -1
- data/spec/integration/spec_helper.rb +2 -2
- data/spec/menus/menu_generator_spec.rb +64 -2
- data/spec/scaffolding/scaffold_project_detector_spec.rb +12 -0
- metadata +15 -2
|
@@ -111,6 +111,23 @@ describe 'Config file content' do
|
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
# appium_lib connects to http://127.0.0.1:4723 and ignores a url capability, so none is generated.
|
|
115
|
+
%w[rspec cucumber].product(%w[android ios cross_platform]).each do |framework, automation|
|
|
116
|
+
it "has no unused url capability or /wd/hub path in #{framework} #{automation} capabilities" do
|
|
117
|
+
caps = read_generated("#{framework}_#{automation}", 'config/capabilities.yml')
|
|
118
|
+
local = caps.split(/^browserstack:/).first
|
|
119
|
+
expect(local).not_to match(/^\s*url:/)
|
|
120
|
+
expect(local).not_to include('/wd/hub')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it "parses as YAML, BrowserStack block included, in #{framework} #{automation} capabilities" do
|
|
124
|
+
caps = YAML.safe_load(read_generated("#{framework}_#{automation}", 'config/capabilities.yml'))
|
|
125
|
+
browserstack = caps.fetch('browserstack')
|
|
126
|
+
expect(browserstack).to have_key('app')
|
|
127
|
+
expect(browserstack['app'].to_s).not_to start_with('app:')
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
114
131
|
%w[android ios cross_platform].each do |automation|
|
|
115
132
|
it "has no login_state in the #{automation} config" do
|
|
116
133
|
Dir.glob(File.join("#{FrameworkIndex::RSPEC}_#{automation}", 'config', '*.yml')).each do |file|
|
|
@@ -119,3 +136,30 @@ describe 'Config file content' do
|
|
|
119
136
|
end
|
|
120
137
|
end
|
|
121
138
|
end
|
|
139
|
+
|
|
140
|
+
describe 'Config timeout wiring' do
|
|
141
|
+
%w[rspec cucumber].each do |framework|
|
|
142
|
+
# Single-platform Appium projects have no config.yml; WaitHelper falls back to its default of 10.
|
|
143
|
+
%w[selenium cross_platform].each do |automation|
|
|
144
|
+
it "sets timeout 10 in #{framework} #{automation}" do
|
|
145
|
+
config = YAML.safe_load(read_generated("#{framework}_#{automation}", 'config/config.yml'), permitted_classes: [Symbol])
|
|
146
|
+
expect(config['timeout']).to eq(10)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'describes the selenium timeout as an action and eventually wait' do
|
|
152
|
+
expect(read_generated('rspec_selenium', 'config/config.yml'))
|
|
153
|
+
.to include('# seconds that actions and eventually wait before failing')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'describes the mobile timeout as an action and eventually wait' do
|
|
157
|
+
expect(read_generated('rspec_cross_platform', 'config/config.yml'))
|
|
158
|
+
.to include('timeout: 10 # seconds that actions and eventually wait before failing')
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'leaves the watir timeout comment unchanged' do
|
|
162
|
+
expect(read_generated('rspec_watir', 'config/config.yml'))
|
|
163
|
+
.to include('timeout: 10 # seconds ā used for implicit waits and max wait time')
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -398,3 +398,82 @@ describe 'Helper file content' do
|
|
|
398
398
|
end
|
|
399
399
|
end
|
|
400
400
|
end
|
|
401
|
+
|
|
402
|
+
describe 'Auto-waiting helper wiring' do
|
|
403
|
+
%w[selenium ios android cross_platform].each do |automation|
|
|
404
|
+
context "with rspec and #{automation}" do
|
|
405
|
+
it 'includes WaitHelper in the spec helper' do
|
|
406
|
+
helper = read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'helpers/spec_helper.rb')
|
|
407
|
+
expect(helper).to include("require_relative 'wait_helper'", 'config.include(WaitHelper)')
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
%w[ios android cross_platform].each do |automation|
|
|
413
|
+
%w[rspec cucumber].each do |framework|
|
|
414
|
+
it "does not pass a wait to appium_lib in #{framework} #{automation}" do
|
|
415
|
+
driver = read_generated("#{framework}_#{automation}", 'helpers/driver_helper.rb')
|
|
416
|
+
expect(driver).not_to match(/\bwait:/)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
%w[rspec cucumber].each do |framework|
|
|
422
|
+
it "returns the new driver without a redundant assignment in #{framework} selenium" do
|
|
423
|
+
driver = read_generated("#{framework}_selenium", 'helpers/driver_helper.rb')
|
|
424
|
+
expect(driver).to include(" Selenium::WebDriver.for(browser.to_sym, **arguments)\n end")
|
|
425
|
+
expect(driver).not_to include('driver = Selenium::WebDriver.for')
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
it "leaves no implicit wait in #{framework} selenium" do
|
|
429
|
+
driver = read_generated("#{framework}_selenium", 'helpers/driver_helper.rb')
|
|
430
|
+
expect(driver).not_to include('implicit_wait')
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it "keeps Watir untouched in #{framework} watir" do
|
|
434
|
+
project = "#{framework}_watir"
|
|
435
|
+
expect(File.exist?(File.join(project, 'helpers/wait_helper.rb'))).to be(false)
|
|
436
|
+
expect(read_generated(project, 'helpers/browser_helper.rb')).to include('Watir.default_timeout')
|
|
437
|
+
files = Dir.glob(File.join(project, '**/*.rb')).map { |f| File.read(f) }
|
|
438
|
+
expect(files.grep(/WaitHelper/)).to be_empty
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
describe 'without example tests' do
|
|
443
|
+
%w[selenium ios cross_platform].each do |automation|
|
|
444
|
+
context "with rspec and #{automation}" do
|
|
445
|
+
subject(:helper) { read_generated(ensure_project(framework: 'rspec', automation:, examples: false), 'helpers/spec_helper.rb') }
|
|
446
|
+
|
|
447
|
+
it 'names no example class or hook' do
|
|
448
|
+
expect(helper).not_to match(/Login|Account|logged_in|model_factory/)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
it 'has valid Ruby syntax' do
|
|
452
|
+
expect(helper).to have_valid_ruby_syntax
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
context "with cucumber and #{automation}" do
|
|
457
|
+
let(:name) { ensure_project(framework: 'cucumber', automation:, examples: false) }
|
|
458
|
+
|
|
459
|
+
it 'names no example class or hook in the env and the World' do
|
|
460
|
+
%w[env.rb world.rb].each do |file|
|
|
461
|
+
expect(read_generated(name, "features/support/#{file}")).not_to match(/Login|Account|logged_in|factory/)
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
it 'has a valid env and World' do
|
|
466
|
+
%w[env.rb world.rb].each do |file|
|
|
467
|
+
expect(read_generated(name, "features/support/#{file}")).to have_valid_ruby_syntax
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
it 'keeps the session state require in the selenium rspec spec helper' do
|
|
474
|
+
helper = read_generated(ensure_project(framework: 'rspec', automation: 'selenium', examples: false),
|
|
475
|
+
'helpers/spec_helper.rb')
|
|
476
|
+
expect(helper).to include("require_relative 'session_state'")
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
end
|
|
@@ -36,12 +36,16 @@ describe 'Page object content' do
|
|
|
36
36
|
shared_examples 'selenium login page' do |project_name|
|
|
37
37
|
subject(:login) { read_generated(project_name, 'page_objects/pages/login.rb') }
|
|
38
38
|
|
|
39
|
-
it '
|
|
40
|
-
expect(login).to include('
|
|
39
|
+
it 'returns locator hashes from the element methods' do
|
|
40
|
+
expect(login).to include("{ id: 'loginFrm_loginname' }", "{ id: 'loginFrm_password' }",
|
|
41
|
+
%q({ xpath: "//button[@title='Login']" }))
|
|
42
|
+
expect(login).not_to include('driver.find_element')
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
it '
|
|
44
|
-
expect(login).to include('send_keys')
|
|
45
|
+
it 'logs in through the wait helper' do
|
|
46
|
+
expect(login).to include('send_keys(username_field, username)', 'send_keys(password_field, password)',
|
|
47
|
+
'click(login_button)')
|
|
48
|
+
expect(login).not_to include('.send_keys', '.click')
|
|
45
49
|
end
|
|
46
50
|
|
|
47
51
|
it 'has private element methods' do
|
|
@@ -167,3 +171,70 @@ describe 'Page object content' do
|
|
|
167
171
|
include_examples 'watir abstract page', name
|
|
168
172
|
end
|
|
169
173
|
end
|
|
174
|
+
|
|
175
|
+
describe 'Page WaitHelper wiring' do
|
|
176
|
+
%w[rspec cucumber].each do |framework|
|
|
177
|
+
%w[selenium ios android cross_platform].each do |automation|
|
|
178
|
+
it "includes WaitHelper in #{framework} #{automation}" do
|
|
179
|
+
page = read_generated("#{framework}_#{automation}", 'page_objects/abstract/page.rb')
|
|
180
|
+
expect(page).to include("require_relative '../../helpers/wait_helper'", 'include WaitHelper')
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
it "does not include WaitHelper in #{framework} watir" do
|
|
185
|
+
expect(read_generated("#{framework}_watir", 'page_objects/abstract/page.rb')).not_to include('WaitHelper')
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
describe 'Abstract page layout' do
|
|
191
|
+
%w[rspec cucumber].each do |framework|
|
|
192
|
+
%w[selenium ios android cross_platform].each do |automation|
|
|
193
|
+
[true, false].each do |examples|
|
|
194
|
+
it "has no blank line before the class end in #{framework} #{automation}#{' (skeleton)' unless examples}" do
|
|
195
|
+
page = read_generated(ensure_project(framework:, automation:, examples:), 'page_objects/abstract/page.rb')
|
|
196
|
+
expect(page).not_to match(/\n\n\s*end\s*\z/)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe 'Example pages on the wait helper' do
|
|
204
|
+
%w[rspec cucumber].each do |framework|
|
|
205
|
+
it "finds the header through the wait helper in #{framework} selenium" do
|
|
206
|
+
page = read_generated("#{framework}_selenium", 'page_objects/abstract/page.rb')
|
|
207
|
+
expect(page).to include("Header.new(find_element(css: '.welcome-msg'))")
|
|
208
|
+
expect(page).not_to include('driver.find_element')
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
%w[ios android cross_platform].each do |automation|
|
|
212
|
+
context "with #{framework} and #{automation}" do
|
|
213
|
+
let(:home) { read_generated("#{framework}_#{automation}", 'page_objects/pages/home.rb') }
|
|
214
|
+
let(:pdp) { read_generated("#{framework}_#{automation}", 'page_objects/pages/pdp.rb') }
|
|
215
|
+
|
|
216
|
+
it 'taps the first product and reads the add to cart text through the wait helper' do
|
|
217
|
+
expect(home).to include('tap(first_product)')
|
|
218
|
+
expect(pdp).to include('text(add_to_cart_button)')
|
|
219
|
+
expect(home + pdp).not_to include('driver.wait', 'driver.find_element')
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'returns locator hashes from the element methods' do
|
|
223
|
+
if automation == 'cross_platform'
|
|
224
|
+
expect(home).to include("{ ios: { accessibility_id: 'product-item-1' },")
|
|
225
|
+
expect(pdp).to include("{ ios: { accessibility_id: 'add-to-cart-button' },")
|
|
226
|
+
expect(home + pdp).not_to include('element(ios:')
|
|
227
|
+
else
|
|
228
|
+
expect(home).to include("{ accessibility_id: 'product-item-1' }")
|
|
229
|
+
expect(pdp).to include("{ accessibility_id: 'add-to-cart-button' }")
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it 'has valid Ruby syntax' do
|
|
234
|
+
expect(home).to have_valid_ruby_syntax
|
|
235
|
+
expect(pdp).to have_valid_ruby_syntax
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
@@ -126,12 +126,12 @@ describe 'Generated Readme content' do
|
|
|
126
126
|
'live session tokens', 'must not be committed, cached or uploaded')
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
-
it 'is its own ## section, straight
|
|
129
|
+
it 'is its own ## section, straight before Reporting, so Sponsors does not nest under it' do
|
|
130
130
|
heading = readme.index("\n## Reusing the login\n")
|
|
131
|
-
|
|
132
|
-
expect(
|
|
133
|
-
|
|
134
|
-
expect(readme[(
|
|
131
|
+
expect(heading).not_to be_nil
|
|
132
|
+
expect(readme[(heading + 1)..][/\n## .*/]).to eq("\n## Reporting")
|
|
133
|
+
sponsors = readme.index("\n### Sponsors") || readme.size
|
|
134
|
+
expect(readme[0...sponsors].scan(/\n## .*/).last).not_to eq("\n## Reusing the login")
|
|
135
135
|
end
|
|
136
136
|
else
|
|
137
137
|
it 'does not mention login reuse' do
|
|
@@ -141,3 +141,124 @@ describe 'Generated Readme content' do
|
|
|
141
141
|
end
|
|
142
142
|
end
|
|
143
143
|
end
|
|
144
|
+
|
|
145
|
+
describe 'Generated Readme: How the framework works' do
|
|
146
|
+
def section_of(readme)
|
|
147
|
+
readme[/^## How the framework works\n.*?(?=^## )/m]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
FRAMEWORKS.each do |framework|
|
|
151
|
+
context "with #{framework} and selenium" do
|
|
152
|
+
subject(:readme) { read_generated("#{framework}_selenium", 'Readme.md') }
|
|
153
|
+
|
|
154
|
+
it 'sits between Getting Started and Reusing the login' do
|
|
155
|
+
works = readme.index("\n## How the framework works\n")
|
|
156
|
+
expect(works).not_to be_nil
|
|
157
|
+
expect(readme.index("\n## Getting Started\n")).to be < works
|
|
158
|
+
expect(readme.index("\n## Reusing the login\n")).to be > works
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'covers the web wait helper, chaining, .native, absence checks and how to remove it' do
|
|
162
|
+
section = section_of(readme)
|
|
163
|
+
expect(section).to include('### Removing the wait helper', '.native',
|
|
164
|
+
"find_element(css: '.form').find_element(id: 'email')",
|
|
165
|
+
'select_by', 'hover', 'drag_and_drop', 'eventually', 'timeout: 0',
|
|
166
|
+
'find_elements', 'config/config.yml', '[Reporting](#reporting)',
|
|
167
|
+
'[Reusing the login](#reusing-the-login)', 'parallel_tests', 'HEADLESS',
|
|
168
|
+
framework == 'rspec' ? 'RETRY_COUNT' : '--retry 2')
|
|
169
|
+
expect(section).not_to include('scroll_to', 'swipe')
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it 'explains that the waits inside eventually stretch to its time and are retried' do
|
|
173
|
+
eventually = section_of(readme)[/^### eventually\n.*?(?=^### )/m]
|
|
174
|
+
expect(eventually).to include('a wait inside it that timed out', 'whatever is left of the block',
|
|
175
|
+
'`timeout: 2` gets 2 seconds per try', '(still failing after 10s)')
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'replaces eventually with a true/false Wait block and points at the generators that check' do
|
|
179
|
+
removal = section_of(readme)[/^### Removing the wait helper\n.*/m]
|
|
180
|
+
expect(removal).to include('a plain `expect`, or `Selenium::WebDriver::Wait.new(timeout: 10).until { condition }`',
|
|
181
|
+
'a block that returns true or false', '`raider g page` and `raider g component`')
|
|
182
|
+
expect(removal).not_to include('raider g spec')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'lists the five steps to remove the wait helper' do
|
|
186
|
+
removal = section_of(readme)[/^### Removing the wait helper\n.*/m]
|
|
187
|
+
expect(removal).to include('helpers/wait_helper.rb', 'helpers/raider_element.rb', 'include WaitHelper',
|
|
188
|
+
'driver.find_element(**', 'Selenium::WebDriver::Wait.new', 'implicit_wait',
|
|
189
|
+
'raider g')
|
|
190
|
+
expect(removal.scan(/^\d\. /).size).to eq(5)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
%w[ios android cross_platform].each do |automation|
|
|
195
|
+
context "with #{framework} and #{automation}" do
|
|
196
|
+
subject(:readme) { read_generated("#{framework}_#{automation}", 'Readme.md') }
|
|
197
|
+
|
|
198
|
+
it 'sits between Getting Started and Reporting' do
|
|
199
|
+
works = readme.index("\n## How the framework works\n")
|
|
200
|
+
expect(works).not_to be_nil
|
|
201
|
+
expect(readme.index("\n## Getting Started\n")).to be < works
|
|
202
|
+
expect(readme.index("\n## Reporting\n")).to be > works
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it 'covers the mobile method set, per-OS locators, Appium setup and how to remove the wait helper' do
|
|
206
|
+
section = section_of(readme)
|
|
207
|
+
expect(section).to include('swipe', 'scroll_to', 'tap', 'capabilities.yml', 'raider u start_appium',
|
|
208
|
+
'### Removing the wait helper', '.native', '[Reporting](#reporting)',
|
|
209
|
+
'timeout: 15', 'http://127.0.0.1:4723')
|
|
210
|
+
expect(section).not_to include('select_by', 'hover', 'drag_and_drop', 'Reusing the login', '/wd/hub')
|
|
211
|
+
expect(section).to include('ios: {') if automation == 'cross_platform'
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'says the device capabilities sit under appium:options' do
|
|
215
|
+
settings = section_of(readme)[/^### Settings\n.*?(?=^### )/m]
|
|
216
|
+
expect(settings).to include('under `appium:options`')
|
|
217
|
+
expect(settings).to include('under the `ios:` and `android:` parts') if automation == 'cross_platform'
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it "marks the #{framework} watir Readme as deprecated and not the selenium one" do
|
|
223
|
+
watir = read_generated("#{framework}_watir", 'Readme.md')
|
|
224
|
+
expect(watir).to include('> **Deprecated:** Watir is deprecated', 'only available through `-p automation:watir`',
|
|
225
|
+
'https://github.com/RaiderHQ/ruby_raider#migrating-from-watir')
|
|
226
|
+
expect(read_generated("#{framework}_selenium", 'Readme.md')).not_to include('**Deprecated:**')
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it "has no such section for #{framework} and watir" do
|
|
230
|
+
expect(read_generated("#{framework}_watir", 'Readme.md')).not_to include('How the framework works')
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe 'without example tests' do
|
|
235
|
+
%w[rspec cucumber].product(%w[selenium ios cross_platform]).each do |framework, automation|
|
|
236
|
+
context "with #{framework} and #{automation}" do
|
|
237
|
+
subject(:readme) { read_generated(ensure_project(framework:, automation:, examples: false), 'Readme.md') }
|
|
238
|
+
|
|
239
|
+
it 'still explains how the framework works' do
|
|
240
|
+
expect(readme).to include('## How the framework works', '### The wait helper and RaiderElement')
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it 'points at no example file' do
|
|
244
|
+
expect(readme).not_to match(/login\.rb|account\.rb|header\.rb|home\.rb|pdp\.rb|users\.yml|model_factory|
|
|
245
|
+
user_factory|login_page_spec|pdp_page_spec|login\.feature|home\.feature|
|
|
246
|
+
login_steps|home_steps|account\ example|files\ in\ this\ project/x)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'says it has no example tests and how it grows with scaffolding' do
|
|
250
|
+
expect(readme).to include('generated without example tests', 'raider g scaffold checkout')
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
%w[rspec cucumber].each do |framework|
|
|
256
|
+
it "shows how to add the login hook for the project's own login page in #{framework}" do
|
|
257
|
+
readme = read_generated(ensure_project(framework:, automation: 'selenium', examples: false), 'Readme.md')
|
|
258
|
+
section = readme[/^## Reusing the login\n.*?(?=^## )/m]
|
|
259
|
+
hook = framework == 'rspec' ? 'config.before(:each, :logged_in) do' : "Before('@logged_in') do"
|
|
260
|
+
expect(section).to include(hook, 'SessionState.new(driver).reuse(')
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -31,6 +31,8 @@ describe 'Ruby syntax validation' do
|
|
|
31
31
|
else
|
|
32
32
|
%w[features/support/env.rb features/step_definitions/login_steps.rb]
|
|
33
33
|
end
|
|
34
|
+
# The glob above runs at load time, before the projects exist, so new helpers are listed here.
|
|
35
|
+
login_reuse_files += %w[helpers/wait_helper.rb helpers/raider_element.rb] if automation == 'selenium'
|
|
34
36
|
|
|
35
37
|
login_reuse_files.each do |relative|
|
|
36
38
|
it "#{relative} is generated as valid Ruby with frozen_string_literal" do
|
|
@@ -41,5 +43,26 @@ describe 'Ruby syntax validation' do
|
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
end
|
|
46
|
+
|
|
47
|
+
# Mobile projects get the mobile branch of the wait helper templates.
|
|
48
|
+
%w[android ios cross_platform].each do |automation|
|
|
49
|
+
context "with #{framework} and #{automation}" do
|
|
50
|
+
project_name = "#{framework}_#{automation}"
|
|
51
|
+
|
|
52
|
+
%w[helpers/wait_helper.rb helpers/raider_element.rb].each do |relative|
|
|
53
|
+
it "#{relative} is generated as valid Ruby with frozen_string_literal" do
|
|
54
|
+
content = File.read(File.join(project_name, relative))
|
|
55
|
+
expect(content).to have_valid_ruby_syntax
|
|
56
|
+
expect(content).to have_frozen_string_literal
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'helpers/wait_helper.rb has the mobile gestures and no web-only methods' do
|
|
61
|
+
content = File.read(File.join(project_name, 'helpers/wait_helper.rb'))
|
|
62
|
+
expect(content).to include("'mobile: swipeGesture'").and include('def scroll_to(')
|
|
63
|
+
expect(content).not_to include('def drag_and_drop(')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
44
67
|
end
|
|
45
68
|
end
|
|
@@ -173,3 +173,60 @@ describe 'Test file content' do
|
|
|
173
173
|
include_examples 'logged in scenario', name
|
|
174
174
|
end
|
|
175
175
|
end
|
|
176
|
+
|
|
177
|
+
describe 'Cucumber world WaitHelper wiring' do
|
|
178
|
+
%w[selenium ios android cross_platform].each do |automation|
|
|
179
|
+
it "makes WaitHelper available to steps in #{automation}" do
|
|
180
|
+
world = read_generated("cucumber_#{automation}", 'features/support/world.rb')
|
|
181
|
+
expect(world).to include("require_relative '../../helpers/wait_helper'", 'World(WaitHelper)')
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'leaves the watir world without WaitHelper' do
|
|
186
|
+
expect(read_generated('cucumber_watir', 'features/support/world.rb')).not_to include('WaitHelper')
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
describe 'Example tests on the wait helper' do
|
|
191
|
+
it 'wraps every assertion of the selenium rspec spec in eventually' do
|
|
192
|
+
spec = read_generated('rspec_selenium', 'spec/login_page_spec.rb')
|
|
193
|
+
expect(spec.scan('eventually { expect(').size).to eq(3)
|
|
194
|
+
expect(spec.scan(/^ +expect\(/)).to be_empty
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
it "wraps the selenium cucumber Then step's assertion in eventually" do
|
|
198
|
+
steps = read_generated('cucumber_selenium', 'features/step_definitions/login_steps.rb')
|
|
199
|
+
expect(steps).to include('eventually { expect(account_page.header.customer_name).to eq ')
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
%w[ios android cross_platform].each do |automation|
|
|
203
|
+
it "wraps the #{automation} rspec assertion in eventually" do
|
|
204
|
+
spec = read_generated("rspec_#{automation}", 'spec/pdp_page_spec.rb')
|
|
205
|
+
expect(spec).to include("eventually { expect(pdp_page.add_to_cart_text).to eq 'Add to Cart' }")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it "wraps the #{automation} cucumber Then step's assertion in eventually" do
|
|
209
|
+
steps = read_generated("cucumber_#{automation}", 'features/step_definitions/home_steps.rb')
|
|
210
|
+
expect(steps).to include("eventually { expect(pdp_page.add_to_cart_text).to eq 'Add to Cart' }")
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The Watir output must stay as master generated it (1274a66); the snapshots in
|
|
216
|
+
# spec/fixtures/watir_master were generated from that commit's lib.
|
|
217
|
+
# To regenerate: `git archive 1274a66 lib` into a scratch dir, generate rspec and cucumber Watir projects there,
|
|
218
|
+
# then apply only the Readme deprecation-line delta (the `> **Deprecated:**` line under the heading block).
|
|
219
|
+
describe 'Watir example output' do
|
|
220
|
+
snapshots = File.expand_path('../../fixtures/watir_master', __dir__)
|
|
221
|
+
|
|
222
|
+
{ 'rspec_watir' => %w[page_objects/pages/login.rb page_objects/abstract/page.rb spec/login_page_spec.rb Readme.md],
|
|
223
|
+
'cucumber_watir' => %w[page_objects/pages/login.rb page_objects/abstract/page.rb
|
|
224
|
+
features/step_definitions/login_steps.rb Readme.md] }.each do |project, files|
|
|
225
|
+
files.each do |file|
|
|
226
|
+
it "keeps #{project}/#{file} as master generates it" do
|
|
227
|
+
expected = File.read(File.join(snapshots, project, "#{file}.snapshot"))
|
|
228
|
+
expect(read_generated(project, file)).to eq(expected)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
@@ -398,6 +398,40 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
398
398
|
end
|
|
399
399
|
end
|
|
400
400
|
|
|
401
|
+
# An empty skeleton (examples: false) still passes its run, so its CI is green until tests are added
|
|
402
|
+
def generate_skeleton_project(framework)
|
|
403
|
+
name = "skeleton_e2e_#{framework}_selenium"
|
|
404
|
+
unless LazyProjectGenerator::GENERATED.include?(name)
|
|
405
|
+
generate_framework(framework:, automation: 'selenium', name:, examples: false)
|
|
406
|
+
LazyProjectGenerator::GENERATED.add(name)
|
|
407
|
+
end
|
|
408
|
+
name
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
context 'Empty skeleton projects (examples: false)' do
|
|
412
|
+
it 'runs bundle exec rspec on a selenium rspec skeleton with 0 examples', :aggregate_failures do
|
|
413
|
+
project = generate_skeleton_project('rspec')
|
|
414
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
415
|
+
|
|
416
|
+
['rspec', 'rspec spec/'].each do |command|
|
|
417
|
+
result = run_in_project(project, "bundle exec #{command}", timeout: 120)
|
|
418
|
+
expect(result[:exit_code]).to eq(0), "#{command}\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
419
|
+
expect(result[:stdout]).to include('0 examples, 0 failures')
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
it 'runs bundle exec cucumber on a selenium cucumber skeleton with 0 scenarios', :aggregate_failures do
|
|
424
|
+
project = generate_skeleton_project('cucumber')
|
|
425
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
426
|
+
|
|
427
|
+
['cucumber', 'cucumber features'].each do |command|
|
|
428
|
+
result = run_in_project(project, "bundle exec #{command}", timeout: 120)
|
|
429
|
+
expect(result[:exit_code]).to eq(0), "#{command}\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
430
|
+
expect(result[:stdout]).to include('0 scenarios')
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
|
|
401
435
|
# Failure video replay against real browsers. Needs an ffmpeg with libvpx (RAIDER_FFMPEG_PATH), because the
|
|
402
436
|
# RaiderHQ ffmpeg release has no checksums yet. Skipped when it, or the browser, is missing. Firefox can be
|
|
403
437
|
# pointed at a specific binary with FIREFOX_BINARY.
|
|
@@ -683,38 +717,26 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
683
717
|
|
|
684
718
|
# Verify template system performance (caching working)
|
|
685
719
|
context 'Template System Performance' do
|
|
686
|
-
it '
|
|
720
|
+
it 'compiles each partial once and reuses it on the second generation' do
|
|
687
721
|
require_relative '../../lib/generators/generator'
|
|
688
722
|
|
|
689
|
-
# Clear cache
|
|
690
723
|
Generator.clear_template_cache
|
|
724
|
+
compiled = []
|
|
725
|
+
allow_any_instance_of(TemplateRenderer::PartialCache).to(
|
|
726
|
+
receive(:compile_template).and_wrap_original { |original, *args| compiled << args.first && original.call(*args) }
|
|
727
|
+
)
|
|
691
728
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
generate_framework(settings2)
|
|
704
|
-
second_duration = Time.now - start_time
|
|
705
|
-
|
|
706
|
-
# Second generation should be faster (or similar if I/O dominates)
|
|
707
|
-
# At minimum, cache should not slow things down
|
|
708
|
-
expect(second_duration).to be <= (first_duration * 1.2)
|
|
709
|
-
|
|
710
|
-
# Cleanup
|
|
711
|
-
FileUtils.rm_rf('test_cache_1')
|
|
712
|
-
FileUtils.rm_rf('test_cache_2')
|
|
713
|
-
|
|
714
|
-
# Verify cache has entries
|
|
715
|
-
stats = Generator.template_cache_stats
|
|
716
|
-
expect(stats[:size]).to be > 0
|
|
717
|
-
puts "\nš Template cache: #{stats[:size]} entries, ~#{stats[:memory_estimate] / 1024}KB"
|
|
729
|
+
%w[test_cache_1 test_cache_2].map do |name|
|
|
730
|
+
before = compiled.size
|
|
731
|
+
generate_framework(create_settings(framework: 'rspec', automation: 'selenium').merge(name:))
|
|
732
|
+
compiled.size - before
|
|
733
|
+
end => [first, second]
|
|
734
|
+
|
|
735
|
+
expect(first).to be_positive
|
|
736
|
+
expect(second).to eq(0)
|
|
737
|
+
expect(Generator.template_cache_stats[:size]).to be_positive
|
|
738
|
+
ensure
|
|
739
|
+
FileUtils.rm_rf(%w[test_cache_1 test_cache_2])
|
|
718
740
|
end
|
|
719
741
|
end
|
|
720
742
|
end
|
|
@@ -71,4 +71,54 @@ describe AutomationGenerator do
|
|
|
71
71
|
context 'with cucumber and appium cross platform' do
|
|
72
72
|
include_examples 'creates mobile automation framework', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
describe 'without example tests' do
|
|
76
|
+
%w[rspec cucumber].product(%w[selenium ios cross_platform]).each do |framework, automation|
|
|
77
|
+
context "with #{framework} and #{automation}" do
|
|
78
|
+
let(:name) { ensure_project(framework:, automation:, examples: false) }
|
|
79
|
+
|
|
80
|
+
it 'creates no example pages' do
|
|
81
|
+
expect(Dir.glob("#{name}/page_objects/pages/*.rb")).to be_empty
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it 'keeps the pages folder with a .keep' do
|
|
85
|
+
expect(File).to exist("#{name}/page_objects/pages/.keep")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'creates the abstract page' do
|
|
89
|
+
expect(File).to exist("#{name}/page_objects/abstract/page.rb")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'gives the abstract page no header component' do
|
|
93
|
+
expect(File.read("#{name}/page_objects/abstract/page.rb")).not_to include('Header')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'creates no models' do
|
|
97
|
+
expect(File).not_to exist("#{name}/models")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
context 'with a web project' do
|
|
103
|
+
let(:name) { ensure_project(framework: 'rspec', automation: 'selenium', examples: false) }
|
|
104
|
+
|
|
105
|
+
it 'creates the abstract component' do
|
|
106
|
+
expect(File).to exist("#{name}/page_objects/abstract/component.rb")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it 'keeps the components folder with a .keep and no example component' do
|
|
110
|
+
expect(Dir.children("#{name}/page_objects/components")).to eq(['.keep'])
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context 'with watir' do
|
|
115
|
+
it 'still creates the examples, because the Watir templates are frozen' do
|
|
116
|
+
InvokeGenerators.generate_framework(framework: 'rspec', automation: 'watir', name: 'skeleton_watir',
|
|
117
|
+
examples: false)
|
|
118
|
+
expect(File).to exist('skeleton_watir/page_objects/pages/login.rb')
|
|
119
|
+
ensure
|
|
120
|
+
FileUtils.rm_rf('skeleton_watir')
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
74
124
|
end
|
|
@@ -107,4 +107,24 @@ describe CommonGenerator do
|
|
|
107
107
|
include_examples 'creates a config file', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
108
108
|
include_examples 'creates a gitignore file', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
109
109
|
end
|
|
110
|
+
|
|
111
|
+
describe 'without example tests' do
|
|
112
|
+
%w[rspec cucumber].product(%w[selenium ios cross_platform]).each do |framework, automation|
|
|
113
|
+
context "with #{framework} and #{automation}" do
|
|
114
|
+
let(:name) { ensure_project(framework:, automation:, examples: false) }
|
|
115
|
+
let(:ci_file) { automation == 'selenium' ? 'test_pipeline.yml' : 'appium_pipeline.yml' }
|
|
116
|
+
|
|
117
|
+
it 'creates the README, Gemfile, Rakefile and CI file' do
|
|
118
|
+
%W[Readme.md Gemfile Rakefile .github/workflows/#{ci_file}].each do |file|
|
|
119
|
+
expect(File).to exist("#{name}/#{file}")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'creates the settings' do
|
|
124
|
+
settings = automation == 'ios' ? 'config/capabilities.yml' : 'config/config.yml'
|
|
125
|
+
expect(File).to exist("#{name}/#{settings}")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
110
130
|
end
|
|
@@ -35,9 +35,9 @@ describe 'Config features for generated projects' do
|
|
|
35
35
|
expect(config['driver_options']).to be_nil
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
it 'generates driver_helper
|
|
38
|
+
it 'generates driver_helper without an implicit wait' do
|
|
39
39
|
helper = File.read('config_selenium_test/helpers/driver_helper.rb')
|
|
40
|
-
expect(helper).
|
|
40
|
+
expect(helper).not_to include('implicit_wait')
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
it 'generates spec_helper with viewport-aware window setup' do
|