ruby_raider 3.10.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 +44 -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/feature.tt +5 -0
- 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 +20 -0
- data/lib/generators/cucumber/templates/partials/watir_env.tt +16 -0
- data/lib/generators/cucumber/templates/partials/web_steps.tt +8 -0
- data/lib/generators/generator.rb +15 -4
- data/lib/generators/helper_generator.rb +15 -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 +27 -1
- data/lib/generators/template_renderer/template_error.rb +8 -8
- data/lib/generators/templates/common/git_ignore.tt +4 -1
- data/lib/generators/templates/common/partials/mobile_config.tt +1 -0
- data/lib/generators/templates/common/partials/web_config.tt +5 -2
- data/lib/generators/templates/common/read_me.tt +508 -0
- 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 +246 -0
- data/lib/generators/templates/helpers/spec_helper.tt +28 -0
- 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/ruby_raider.gemspec +1 -0
- 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/azure_content_spec.rb +4 -0
- data/spec/integration/content/ci_content_spec.rb +11 -0
- data/spec/integration/content/common_content_spec.rb +27 -0
- data/spec/integration/content/config_content_spec.rb +62 -0
- data/spec/integration/content/cucumber_reporter_content_spec.rb +25 -0
- data/spec/integration/content/helper_content_spec.rb +111 -0
- data/spec/integration/content/page_content_spec.rb +75 -4
- data/spec/integration/content/readme_content_spec.rb +149 -0
- data/spec/integration/content/syntax_validation_spec.rb +38 -0
- data/spec/integration/content/test_content_spec.rb +82 -0
- data/spec/integration/end_to_end_spec.rb +73 -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 +60 -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 +474 -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 +31 -2
|
@@ -139,6 +139,21 @@ describe 'Common file content' do
|
|
|
139
139
|
end
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
+
shared_examples 'web gitignore for saved logins' do |project_name|
|
|
143
|
+
subject(:lines) { read_generated(project_name, '.gitignore').lines.map(&:strip) }
|
|
144
|
+
|
|
145
|
+
it 'ignores .raider/auth/ but not the whole .raider/' do
|
|
146
|
+
expect(lines).to include('.raider/auth/')
|
|
147
|
+
expect(lines).not_to include('.raider/', '.raider')
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
shared_examples 'mobile gitignore without saved logins' do |project_name|
|
|
152
|
+
it 'has no .raider line' do
|
|
153
|
+
expect(read_generated(project_name, '.gitignore')).not_to include('.raider')
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
142
157
|
# --- Spec helper retry config (C4) ---
|
|
143
158
|
|
|
144
159
|
shared_examples 'spec helper with retry config' do |project_name|
|
|
@@ -207,6 +222,7 @@ describe 'Common file content' do
|
|
|
207
222
|
it_behaves_like 'rspec gitignore', name
|
|
208
223
|
it_behaves_like 'spec helper with retry config', name
|
|
209
224
|
it_behaves_like 'gemfile with rspec-retry', name
|
|
225
|
+
it_behaves_like 'web gitignore for saved logins', name
|
|
210
226
|
end
|
|
211
227
|
|
|
212
228
|
context 'with rspec and watir' do
|
|
@@ -217,6 +233,7 @@ describe 'Common file content' do
|
|
|
217
233
|
it_behaves_like 'rspec gitignore', name
|
|
218
234
|
it_behaves_like 'spec helper with retry config', name
|
|
219
235
|
it_behaves_like 'gemfile with rspec-retry', name
|
|
236
|
+
it_behaves_like 'web gitignore for saved logins', name
|
|
220
237
|
end
|
|
221
238
|
|
|
222
239
|
# --- Cucumber contexts ---
|
|
@@ -229,6 +246,7 @@ describe 'Common file content' do
|
|
|
229
246
|
it_behaves_like 'non-rspec gitignore', name
|
|
230
247
|
it_behaves_like 'gemfile without rspec-retry', name
|
|
231
248
|
it_behaves_like 'cucumber config with retry', name
|
|
249
|
+
it_behaves_like 'web gitignore for saved logins', name
|
|
232
250
|
end
|
|
233
251
|
|
|
234
252
|
context 'with cucumber and watir' do
|
|
@@ -239,5 +257,14 @@ describe 'Common file content' do
|
|
|
239
257
|
it_behaves_like 'non-rspec gitignore', name
|
|
240
258
|
it_behaves_like 'gemfile without rspec-retry', name
|
|
241
259
|
it_behaves_like 'cucumber config with retry', name
|
|
260
|
+
it_behaves_like 'web gitignore for saved logins', name
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
%w[android ios cross_platform].each do |automation|
|
|
264
|
+
%w[rspec cucumber].each do |framework|
|
|
265
|
+
context "with #{framework} and #{automation}" do
|
|
266
|
+
it_behaves_like 'mobile gitignore without saved logins', "#{framework}_#{automation}"
|
|
267
|
+
end
|
|
268
|
+
end
|
|
242
269
|
end
|
|
243
270
|
end
|
|
@@ -59,6 +59,13 @@ describe 'Config file content' do
|
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
shared_examples 'login state config' do |project_name|
|
|
63
|
+
it 'has a login_state max_age of an hour' do
|
|
64
|
+
config = YAML.safe_load(read_generated(project_name, 'config/config.yml'), permitted_classes: [Symbol])
|
|
65
|
+
expect(config['login_state']).to eq('max_age' => 3600)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
62
69
|
# --- Web config per automation ---
|
|
63
70
|
|
|
64
71
|
context 'with rspec and selenium' do
|
|
@@ -66,18 +73,21 @@ describe 'Config file content' do
|
|
|
66
73
|
include_examples 'valid web config', name
|
|
67
74
|
include_examples 'selenium config with timeout', name
|
|
68
75
|
include_examples 'video config', name
|
|
76
|
+
include_examples 'login state config', name
|
|
69
77
|
end
|
|
70
78
|
|
|
71
79
|
context 'with rspec and watir' do
|
|
72
80
|
name = "#{FrameworkIndex::RSPEC}_#{AutomationIndex::WATIR}"
|
|
73
81
|
include_examples 'valid web config', name
|
|
74
82
|
include_examples 'config without driver_options', name
|
|
83
|
+
include_examples 'login state config', name
|
|
75
84
|
end
|
|
76
85
|
|
|
77
86
|
context 'with cucumber and selenium' do
|
|
78
87
|
name = "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
79
88
|
include_examples 'valid web config', name
|
|
80
89
|
include_examples 'selenium config with timeout', name
|
|
90
|
+
include_examples 'login state config', name
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
# --- Mobile config ---
|
|
@@ -100,4 +110,56 @@ describe 'Config file content' do
|
|
|
100
110
|
expect(caps).to match(/platformName/i)
|
|
101
111
|
end
|
|
102
112
|
end
|
|
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
|
+
|
|
131
|
+
%w[android ios cross_platform].each do |automation|
|
|
132
|
+
it "has no login_state in the #{automation} config" do
|
|
133
|
+
Dir.glob(File.join("#{FrameworkIndex::RSPEC}_#{automation}", 'config', '*.yml')).each do |file|
|
|
134
|
+
expect(File.read(file)).not_to include('login_state')
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
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
|
|
103
165
|
end
|
|
@@ -83,3 +83,28 @@ describe 'Cucumber raider_reporter wiring' do
|
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
|
+
|
|
87
|
+
describe 'Cucumber login reuse wiring' do
|
|
88
|
+
{ 'selenium' => 'driver', 'watir' => 'browser.driver' }.each do |automation, target|
|
|
89
|
+
context "with cucumber and #{automation}" do
|
|
90
|
+
let(:env) { read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'features/support/env.rb') }
|
|
91
|
+
|
|
92
|
+
it 'requires session_state' do
|
|
93
|
+
expect(env).to include("require_relative '../../helpers/session_state'")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "reuses the saved login in Before('@logged_in'), defined after the plain Before" do
|
|
97
|
+
expect(env).to include("Before('@logged_in') do", "SessionState.new(#{target}).reuse(",
|
|
98
|
+
"UserFactory.for('registered user')")
|
|
99
|
+
expect(env.index("Before('@logged_in')")).to be > env.index(/^Before do/)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
%w[android ios cross_platform].each do |automation|
|
|
105
|
+
it "does not mention session_state with cucumber and #{automation}" do
|
|
106
|
+
env = read_generated("#{FrameworkIndex::CUCUMBER}_#{automation}", 'features/support/env.rb')
|
|
107
|
+
expect(env).not_to include('session_state')
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -365,4 +365,115 @@ describe 'Helper file content' do
|
|
|
365
365
|
end
|
|
366
366
|
end
|
|
367
367
|
end
|
|
368
|
+
|
|
369
|
+
# --- Opt-in login reuse ---
|
|
370
|
+
|
|
371
|
+
{ 'selenium' => 'driver', 'watir' => 'browser.driver' }.each do |automation, target|
|
|
372
|
+
context "with rspec and #{automation} login reuse" do
|
|
373
|
+
subject(:helper) { read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'helpers/spec_helper.rb') }
|
|
374
|
+
|
|
375
|
+
it 'requires session_state and opts in through the :logged_in tag' do
|
|
376
|
+
expect(helper).to include("require_relative 'session_state'", 'config.before(:each, :logged_in)')
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
it 'reuses the saved login for the driver, falling back to the real login' do
|
|
380
|
+
expect(helper).to include("SessionState.new(#{target}).reuse(")
|
|
381
|
+
expect(helper).to include("user = ModelFactory.for('users')['registered user']", ".login(user['username'], user['password'])")
|
|
382
|
+
expect(helper).to include("header.customer_name == \"Welcome back \#{user['name']}\"")
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
it 'requires the models and pages the login needs' do
|
|
386
|
+
expect(helper).to include("require_relative '../models/model_factory'",
|
|
387
|
+
"require_relative '../page_objects/pages/login'",
|
|
388
|
+
"require_relative '../page_objects/pages/account'")
|
|
389
|
+
end
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
%w[android ios cross_platform].each do |automation|
|
|
394
|
+
context "with rspec and #{automation} without login reuse" do
|
|
395
|
+
it 'does not mention session_state' do
|
|
396
|
+
expect(read_generated("#{FrameworkIndex::RSPEC}_#{automation}", 'helpers/spec_helper.rb')).not_to include('session_state')
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
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
|
|
368
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
|
|
@@ -112,4 +112,153 @@ describe 'Generated Readme content' do
|
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
end
|
|
115
|
+
|
|
116
|
+
FRAMEWORKS.product(AUTOMATION_TYPES).each do |framework, automation|
|
|
117
|
+
context "with #{framework} and #{automation}, the login reuse section" do
|
|
118
|
+
subject(:readme) { read_generated("#{framework}_#{automation}", 'Readme.md') }
|
|
119
|
+
|
|
120
|
+
if %w[selenium watir].include?(automation)
|
|
121
|
+
it 'documents the tags, other roles, max_age, the env switch and the secrets warning' do
|
|
122
|
+
section = readme[/^## Reusing the login\n.*?(?=^##)/m].gsub(/\s+/, ' ')
|
|
123
|
+
expect(section).to include('`:logged_in`', '`@logged_in`', "SessionState.new(driver, name: 'admin')",
|
|
124
|
+
'browser.driver', 'login_state', 'max_age', 'RAIDER_LOGIN_STATE=off',
|
|
125
|
+
'RAIDER_LOGIN_STATE=refresh', '.raider/auth/', 'git-ignored',
|
|
126
|
+
'live session tokens', 'must not be committed, cached or uploaded')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'is its own ## section, straight before Reporting, so Sponsors does not nest under it' do
|
|
130
|
+
heading = readme.index("\n## Reusing the login\n")
|
|
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
|
+
end
|
|
136
|
+
else
|
|
137
|
+
it 'does not mention login reuse' do
|
|
138
|
+
expect(readme).not_to include('Reusing the login', 'RAIDER_LOGIN_STATE', '.raider/auth/')
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
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
|
|
115
264
|
end
|
|
@@ -24,6 +24,44 @@ describe 'Ruby syntax validation' do
|
|
|
24
24
|
expect(content).to have_frozen_string_literal
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
login_reuse_files = %w[helpers/session_state.rb]
|
|
29
|
+
login_reuse_files += if framework == 'rspec'
|
|
30
|
+
%w[helpers/spec_helper.rb]
|
|
31
|
+
else
|
|
32
|
+
%w[features/support/env.rb features/step_definitions/login_steps.rb]
|
|
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'
|
|
36
|
+
|
|
37
|
+
login_reuse_files.each do |relative|
|
|
38
|
+
it "#{relative} is generated as valid Ruby with frozen_string_literal" do
|
|
39
|
+
content = File.read(File.join(project_name, relative))
|
|
40
|
+
expect(content).to have_valid_ruby_syntax
|
|
41
|
+
expect(content).to have_frozen_string_literal
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
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
|
|
27
65
|
end
|
|
28
66
|
end
|
|
29
67
|
end
|