ruby_raider 3.11.0 → 3.12.1
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/.ruby-version +1 -1
- 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 +18 -6
- data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -6
- data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -3
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -6
- data/lib/generators/automation/templates/partials/url_methods.tt +3 -2
- data/lib/generators/automation/templates/partials/visit_method.tt +5 -5
- data/lib/generators/automation/templates/pdp.tt +3 -2
- data/lib/generators/cucumber/cucumber_generator.rb +16 -3
- data/lib/generators/cucumber/templates/env.tt +3 -3
- 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/cucumber/templates/steps.tt +2 -2
- data/lib/generators/cucumber/templates/user_factory.tt +2 -0
- data/lib/generators/cucumber/templates/world.tt +5 -3
- data/lib/generators/generator.rb +29 -14
- 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/accessibility_spec.tt +10 -10
- data/lib/generators/rspec/templates/spec.tt +17 -2
- data/lib/generators/template_renderer/template_error.rb +8 -8
- data/lib/generators/templates/common/gemfile.tt +9 -9
- 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/appium_helper.tt +17 -15
- data/lib/generators/templates/helpers/browser_helper.tt +1 -2
- data/lib/generators/templates/helpers/partials/appium_driver.tt +1 -1
- 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 +46 -6
- data/lib/generators/templates/helpers/spec_helper.tt +10 -2
- data/lib/generators/templates/helpers/wait_helper.tt +470 -0
- data/lib/ruby_raider/ffmpeg.rb +7 -7
- data/lib/ruby_raider.rb +9 -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 +4 -0
- data/sig/ruby_raider.rbs +1 -0
- data/sig/scaffolding/project_detector.rbs +1 -0
- data/sig/scaffolding/scaffolding.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +48 -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 +37 -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 +37 -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/content_helper.rb +1 -5
- 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/rubocop_content_spec.rb +87 -0
- data/spec/integration/content/syntax_validation_spec.rb +33 -30
- data/spec/integration/content/test_content_spec.rb +60 -0
- data/spec/integration/end_to_end_spec.rb +73 -28
- data/spec/integration/frozen_string_literal_matcher.rb +13 -0
- 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 +130 -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/scaffolding_e2e_spec.rb +1 -5
- 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 +17 -2
|
@@ -265,11 +265,30 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
265
265
|
FileUtils.rm_f(File.join(project, 'features/step_definitions/reporter_failure_steps.rb'))
|
|
266
266
|
end
|
|
267
267
|
|
|
268
|
+
# Runs the project's own RuboCop with the gems its bundle resolved (newer than this gem's dev bundle).
|
|
269
|
+
# It runs from a copy outside this repo, as a user's project would: inside it RuboCop would also read
|
|
270
|
+
# this repo's .rubocop.yml, gemspec and .ruby-version.
|
|
271
|
+
def expect_project_rubocop_clean(project)
|
|
272
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
273
|
+
|
|
274
|
+
Dir.mktmpdir('raider_rubocop') do |tmp|
|
|
275
|
+
copy = File.join(tmp, project)
|
|
276
|
+
FileUtils.cp_r(project, copy)
|
|
277
|
+
puts run_in_project(copy, 'bundle exec rubocop -V', timeout: 60)[:stdout]
|
|
278
|
+
result = run_in_project(copy, 'bundle exec rubocop --cache false', timeout: 180)
|
|
279
|
+
expect(result[:exit_code]).to eq(0), "STDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
268
283
|
# Test Web Frameworks (these can run without external services)
|
|
269
284
|
context 'Web Automation Frameworks' do
|
|
270
285
|
describe 'Selenium + RSpec' do
|
|
271
286
|
include_examples 'executable rspec project', 'rspec_selenium'
|
|
272
287
|
|
|
288
|
+
it 'passes its own RuboCop with its bundled gems', :slow do
|
|
289
|
+
expect_project_rubocop_clean('rspec_selenium')
|
|
290
|
+
end
|
|
291
|
+
|
|
273
292
|
it 'saves the login once and reuses it', :aggregate_failures, :slow do
|
|
274
293
|
skip 'Bundle install failed' unless bundle_install('rspec_selenium')
|
|
275
294
|
|
|
@@ -300,6 +319,10 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
300
319
|
|
|
301
320
|
describe 'Selenium + Cucumber' do
|
|
302
321
|
include_examples 'executable cucumber project', 'cucumber_selenium'
|
|
322
|
+
|
|
323
|
+
it 'passes its own RuboCop with its bundled gems', :slow do
|
|
324
|
+
expect_project_rubocop_clean('cucumber_selenium')
|
|
325
|
+
end
|
|
303
326
|
end
|
|
304
327
|
|
|
305
328
|
describe 'Watir + Cucumber' do
|
|
@@ -398,6 +421,40 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
398
421
|
end
|
|
399
422
|
end
|
|
400
423
|
|
|
424
|
+
# An empty skeleton (examples: false) still passes its run, so its CI is green until tests are added
|
|
425
|
+
def generate_skeleton_project(framework)
|
|
426
|
+
name = "skeleton_e2e_#{framework}_selenium"
|
|
427
|
+
unless LazyProjectGenerator::GENERATED.include?(name)
|
|
428
|
+
generate_framework(framework:, automation: 'selenium', name:, examples: false)
|
|
429
|
+
LazyProjectGenerator::GENERATED.add(name)
|
|
430
|
+
end
|
|
431
|
+
name
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
context 'Empty skeleton projects (examples: false)' do
|
|
435
|
+
it 'runs bundle exec rspec on a selenium rspec skeleton with 0 examples', :aggregate_failures do
|
|
436
|
+
project = generate_skeleton_project('rspec')
|
|
437
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
438
|
+
|
|
439
|
+
['rspec', 'rspec spec/'].each do |command|
|
|
440
|
+
result = run_in_project(project, "bundle exec #{command}", timeout: 120)
|
|
441
|
+
expect(result[:exit_code]).to eq(0), "#{command}\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
442
|
+
expect(result[:stdout]).to include('0 examples, 0 failures')
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
it 'runs bundle exec cucumber on a selenium cucumber skeleton with 0 scenarios', :aggregate_failures do
|
|
447
|
+
project = generate_skeleton_project('cucumber')
|
|
448
|
+
skip 'Bundle install failed' unless bundle_install(project)
|
|
449
|
+
|
|
450
|
+
['cucumber', 'cucumber features'].each do |command|
|
|
451
|
+
result = run_in_project(project, "bundle exec #{command}", timeout: 120)
|
|
452
|
+
expect(result[:exit_code]).to eq(0), "#{command}\nSTDOUT: #{result[:stdout]}\nSTDERR: #{result[:stderr]}"
|
|
453
|
+
expect(result[:stdout]).to include('0 scenarios')
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
401
458
|
# Failure video replay against real browsers. Needs an ffmpeg with libvpx (RAIDER_FFMPEG_PATH), because the
|
|
402
459
|
# RaiderHQ ffmpeg release has no checksums yet. Skipped when it, or the browser, is missing. Firefox can be
|
|
403
460
|
# pointed at a specific binary with FIREFOX_BINARY.
|
|
@@ -683,38 +740,26 @@ describe 'End-to-End Project Generation and Execution' do
|
|
|
683
740
|
|
|
684
741
|
# Verify template system performance (caching working)
|
|
685
742
|
context 'Template System Performance' do
|
|
686
|
-
it '
|
|
743
|
+
it 'compiles each partial once and reuses it on the second generation' do
|
|
687
744
|
require_relative '../../lib/generators/generator'
|
|
688
745
|
|
|
689
|
-
# Clear cache
|
|
690
746
|
Generator.clear_template_cache
|
|
747
|
+
compiled = []
|
|
748
|
+
allow_any_instance_of(TemplateRenderer::PartialCache).to(
|
|
749
|
+
receive(:compile_template).and_wrap_original { |original, *args| compiled << args.first && original.call(*args) }
|
|
750
|
+
)
|
|
691
751
|
|
|
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"
|
|
752
|
+
%w[test_cache_1 test_cache_2].map do |name|
|
|
753
|
+
before = compiled.size
|
|
754
|
+
generate_framework(create_settings(framework: 'rspec', automation: 'selenium').merge(name:))
|
|
755
|
+
compiled.size - before
|
|
756
|
+
end => [first, second]
|
|
757
|
+
|
|
758
|
+
expect(first).to be_positive
|
|
759
|
+
expect(second).to eq(0)
|
|
760
|
+
expect(Generator.template_cache_stats[:size]).to be_positive
|
|
761
|
+
ensure
|
|
762
|
+
FileUtils.rm_rf(%w[test_cache_1 test_cache_2])
|
|
718
763
|
end
|
|
719
764
|
end
|
|
720
765
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec/expectations'
|
|
4
|
+
|
|
5
|
+
# Ruby only reads the magic comment in the comment block before the first line of code. Ruby would
|
|
6
|
+
# accept it indented, but the generated project's RuboCop (Layout/CommentIndentation) would not.
|
|
7
|
+
RSpec::Matchers.define :have_frozen_string_literal do
|
|
8
|
+
match do |content|
|
|
9
|
+
leading_comments = content.lines.take_while { |line| line.strip.empty? || line.start_with?('#') }
|
|
10
|
+
leading_comments.any? { |line| line.chomp == '# frozen_string_literal: true' }
|
|
11
|
+
end
|
|
12
|
+
failure_message { 'expected an unindented frozen_string_literal magic comment before the first line of code' }
|
|
13
|
+
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
|
|
@@ -61,4 +61,40 @@ describe CucumberGenerator do
|
|
|
61
61
|
include_examples 'creates cucumber files', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::WATIR}", 'login'
|
|
62
62
|
include_examples 'creates factories for web projects', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::WATIR}", 'login'
|
|
63
63
|
end
|
|
64
|
+
|
|
65
|
+
describe 'without example tests' do
|
|
66
|
+
%w[selenium ios cross_platform].each do |automation|
|
|
67
|
+
context "with cucumber and #{automation}" do
|
|
68
|
+
let(:name) { ensure_project(framework: 'cucumber', automation:, examples: false) }
|
|
69
|
+
|
|
70
|
+
it 'creates no feature files' do
|
|
71
|
+
expect(Dir.glob("#{name}/features/**/*.feature")).to be_empty
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'keeps an empty step_definitions folder' do
|
|
75
|
+
expect(Dir.children("#{name}/features/step_definitions")).to eq(['.keep'])
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'creates the support files' do
|
|
79
|
+
expect(Dir.children("#{name}/features/support")).to contain_exactly('env.rb', 'world.rb')
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it 'creates the cucumber.yml file' do
|
|
83
|
+
expect(File).to exist("#{name}/cucumber.yml")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'creates no user factory' do
|
|
87
|
+
expect(File).not_to exist("#{name}/models/user_factory.rb")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'creates no accessibility example' do
|
|
93
|
+
InvokeGenerators.generate_framework(framework: 'cucumber', automation: 'selenium', name: 'skeleton_axe_cucumber',
|
|
94
|
+
examples: false, accessibility: true)
|
|
95
|
+
expect(Dir.glob('skeleton_axe_cucumber/features/**/accessibility*')).to be_empty
|
|
96
|
+
ensure
|
|
97
|
+
FileUtils.rm_rf('skeleton_axe_cucumber')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
64
100
|
end
|
|
@@ -32,6 +32,18 @@ describe HelpersGenerator do
|
|
|
32
32
|
it 'creates a browser helper file' do
|
|
33
33
|
expect(File).to exist("#{name}/helpers/browser_helper.rb")
|
|
34
34
|
end
|
|
35
|
+
|
|
36
|
+
it 'does not create the wait helper or the element wrapper' do
|
|
37
|
+
expect(File).not_to exist("#{name}/helpers/wait_helper.rb")
|
|
38
|
+
expect(File).not_to exist("#{name}/helpers/raider_element.rb")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
shared_examples 'creates the wait helper' do |name|
|
|
43
|
+
it 'creates the wait helper and the element wrapper' do
|
|
44
|
+
expect(File).to exist("#{name}/helpers/wait_helper.rb")
|
|
45
|
+
expect(File).to exist("#{name}/helpers/raider_element.rb")
|
|
46
|
+
end
|
|
35
47
|
end
|
|
36
48
|
|
|
37
49
|
shared_examples 'creates rspec helpers' do |name|
|
|
@@ -56,6 +68,7 @@ describe HelpersGenerator do
|
|
|
56
68
|
include_examples 'creates common helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
57
69
|
include_examples 'creates a session state helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
58
70
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
71
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
59
72
|
include_examples 'creates rspec helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::SELENIUM}"
|
|
60
73
|
end
|
|
61
74
|
|
|
@@ -70,6 +83,7 @@ describe HelpersGenerator do
|
|
|
70
83
|
include_examples 'creates common helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
71
84
|
include_examples 'creates a session state helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
72
85
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
86
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
73
87
|
include_examples 'creates cucumber helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::SELENIUM}"
|
|
74
88
|
end
|
|
75
89
|
|
|
@@ -84,6 +98,7 @@ describe HelpersGenerator do
|
|
|
84
98
|
include_examples 'creates common helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::ANDROID}"
|
|
85
99
|
include_examples 'skips the session state helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::ANDROID}"
|
|
86
100
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::ANDROID}"
|
|
101
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::ANDROID}"
|
|
87
102
|
include_examples 'creates rspec helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::ANDROID}"
|
|
88
103
|
end
|
|
89
104
|
|
|
@@ -91,6 +106,7 @@ describe HelpersGenerator do
|
|
|
91
106
|
include_examples 'creates common helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
|
|
92
107
|
include_examples 'skips the session state helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
|
|
93
108
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
|
|
109
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
|
|
94
110
|
include_examples 'creates rspec helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::IOS}"
|
|
95
111
|
end
|
|
96
112
|
|
|
@@ -98,6 +114,7 @@ describe HelpersGenerator do
|
|
|
98
114
|
include_examples 'creates common helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::ANDROID}"
|
|
99
115
|
include_examples 'skips the session state helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::ANDROID}"
|
|
100
116
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::ANDROID}"
|
|
117
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::ANDROID}"
|
|
101
118
|
include_examples 'creates cucumber helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::ANDROID}"
|
|
102
119
|
end
|
|
103
120
|
|
|
@@ -105,6 +122,7 @@ describe HelpersGenerator do
|
|
|
105
122
|
include_examples 'creates common helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::IOS}"
|
|
106
123
|
include_examples 'skips the session state helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::IOS}"
|
|
107
124
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::IOS}"
|
|
125
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::IOS}"
|
|
108
126
|
include_examples 'creates cucumber helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::IOS}"
|
|
109
127
|
end
|
|
110
128
|
|
|
@@ -112,6 +130,7 @@ describe HelpersGenerator do
|
|
|
112
130
|
include_examples 'creates common helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
113
131
|
include_examples 'skips the session state helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
114
132
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
133
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
115
134
|
include_examples 'creates rspec helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
116
135
|
include_examples 'creates cross platform helpers', "#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
117
136
|
end
|
|
@@ -120,7 +139,26 @@ describe HelpersGenerator do
|
|
|
120
139
|
include_examples 'creates common helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
121
140
|
include_examples 'skips the session state helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
122
141
|
include_examples 'creates selenium helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
142
|
+
include_examples 'creates the wait helper', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
123
143
|
include_examples 'creates cucumber helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
124
144
|
include_examples 'creates cross platform helpers', "#{FrameworkIndex::CUCUMBER}_#{AutomationIndex::CROSS_PLATFORM}"
|
|
125
145
|
end
|
|
146
|
+
|
|
147
|
+
describe 'without example tests' do
|
|
148
|
+
%w[rspec cucumber].product(%w[selenium ios cross_platform]).each do |framework, automation|
|
|
149
|
+
context "with #{framework} and #{automation}" do
|
|
150
|
+
let(:name) { ensure_project(framework:, automation:, examples: false) }
|
|
151
|
+
|
|
152
|
+
it 'creates the wait helper and RaiderElement' do
|
|
153
|
+
expect(File).to exist("#{name}/helpers/wait_helper.rb")
|
|
154
|
+
expect(File).to exist("#{name}/helpers/raider_element.rb")
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'keeps the session state for web' do
|
|
160
|
+
name = ensure_project(framework: 'rspec', automation: 'selenium', examples: false)
|
|
161
|
+
expect(File).to exist("#{name}/helpers/session_state.rb")
|
|
162
|
+
end
|
|
163
|
+
end
|
|
126
164
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'fileutils'
|
|
4
|
+
require 'tmpdir'
|
|
4
5
|
require 'open3'
|
|
5
6
|
require_relative '../spec_helper'
|
|
6
7
|
|
|
@@ -49,4 +50,133 @@ describe InvokeGenerators do
|
|
|
49
50
|
expect(File).not_to exist('case_bad_ci')
|
|
50
51
|
end
|
|
51
52
|
end
|
|
53
|
+
|
|
54
|
+
describe '.collect_flags' do
|
|
55
|
+
it 'adds no_examples when examples is false' do
|
|
56
|
+
expect(described_class.collect_flags(automation: 'selenium', examples: false)).to include('no_examples')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'keeps the examples by default' do
|
|
60
|
+
expect(described_class.collect_flags(automation: 'selenium')).not_to include('no_examples')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'keeps the examples for watir, whose templates are frozen' do
|
|
64
|
+
expect(described_class.collect_flags(automation: 'watir', examples: false)).not_to include('no_examples')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context 'with the Watir deprecation notice' do
|
|
69
|
+
let(:notice) do
|
|
70
|
+
'Watir is deprecated and will be removed in the next major version of ruby_raider; new projects should use Selenium.'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'prints it for watir' do
|
|
74
|
+
expect { described_class.generate_framework(framework: 'rspec', automation: 'watir', name: 'case_dep') }
|
|
75
|
+
.to output(/#{Regexp.escape(notice)}/).to_stdout
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'does not print it for selenium' do
|
|
79
|
+
expect { described_class.generate_framework(framework: 'rspec', automation: 'selenium', name: 'case_nodep') }
|
|
80
|
+
.not_to output(/Watir is deprecated/).to_stdout
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'still prints it for raider new with automation:watir' do
|
|
84
|
+
root = File.expand_path('../../..', __dir__)
|
|
85
|
+
out, status = Open3.capture2e('ruby', "#{root}/bin/raider", 'new', 'case_cli_watir', '-p',
|
|
86
|
+
'framework:rspec', 'automation:watir')
|
|
87
|
+
expect(status.success?).to be(true)
|
|
88
|
+
expect(out).to include(notice)
|
|
89
|
+
expect(File).to exist('case_cli_watir/.rspec')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context 'with examples false for watir' do
|
|
94
|
+
it 'says the examples are kept' do
|
|
95
|
+
expect { described_class.generate_framework(framework: 'rspec', automation: 'watir', name: 'case_watir', examples: false) }
|
|
96
|
+
.to output(/Watir projects always include the example tests/).to_stdout
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe 'Projects named like a generator option' do
|
|
102
|
+
after { FileUtils.rm_rf(%w[no_examples watir selenium]) }
|
|
103
|
+
|
|
104
|
+
it 'keeps the example tests in a project named no_examples' do
|
|
105
|
+
InvokeGenerators.generate_framework(automation: 'selenium', framework: 'rspec', name: 'no_examples')
|
|
106
|
+
expect(File).to exist('no_examples/spec/login_page_spec.rb')
|
|
107
|
+
expect(File).to exist('no_examples/page_objects/pages/login.rb')
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'gives a Selenium project named watir the wait helper it wires in' do
|
|
111
|
+
InvokeGenerators.generate_framework(automation: 'selenium', framework: 'cucumber', name: 'watir')
|
|
112
|
+
expect(File).to exist('watir/helpers/wait_helper.rb')
|
|
113
|
+
expect(File.read('watir/features/support/env.rb')).not_to include('Watir')
|
|
114
|
+
expect(File.read('watir/page_objects/abstract/page.rb')).to include('include WaitHelper')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'keeps a Watir project named selenium on Watir' do
|
|
118
|
+
InvokeGenerators.generate_framework(automation: 'watir', framework: 'rspec', name: 'selenium')
|
|
119
|
+
expect(File).not_to exist('selenium/helpers/wait_helper.rb')
|
|
120
|
+
expect(File.read('selenium/page_objects/abstract/page.rb')).to include('browser.element')
|
|
121
|
+
expect(File.read('selenium/page_objects/abstract/page.rb')).not_to include('find_element')
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe 'Option-named projects generate what a neutral name generates' do
|
|
126
|
+
option_names = %w[ios android cucumber rspec cross_platform axe_addon selenium watir no_examples azure]
|
|
127
|
+
option_sets = [
|
|
128
|
+
{ automation: 'selenium', framework: 'rspec' },
|
|
129
|
+
{ automation: 'selenium', framework: 'cucumber', accessibility: true },
|
|
130
|
+
{ automation: 'selenium', framework: 'rspec', examples: false, ci: 'azure' },
|
|
131
|
+
{ automation: 'watir', framework: 'cucumber' },
|
|
132
|
+
{ automation: 'ios', framework: 'rspec' },
|
|
133
|
+
{ automation: 'android', framework: 'cucumber' },
|
|
134
|
+
{ automation: 'cross_platform', framework: 'rspec' }
|
|
135
|
+
].freeze
|
|
136
|
+
|
|
137
|
+
def quietly
|
|
138
|
+
result = nil
|
|
139
|
+
expect { result = yield }.to output.to_stdout
|
|
140
|
+
result
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def snapshot(name, options)
|
|
144
|
+
Dir.mktmpdir do |dir|
|
|
145
|
+
Dir.chdir(dir) do
|
|
146
|
+
InvokeGenerators.generate_framework(options.merge(name:))
|
|
147
|
+
Dir.glob("#{name}/**/*", File::FNM_DOTMATCH).select { |f| File.file?(f) }
|
|
148
|
+
.to_h { |f| [f.delete_prefix("#{name}/"), File.read(f)] }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
option_sets.each do |options|
|
|
154
|
+
context "with #{options}" do
|
|
155
|
+
let(:neutral) { quietly { snapshot('demo', options) } }
|
|
156
|
+
|
|
157
|
+
option_names.each do |name|
|
|
158
|
+
it "generates the same files for a project named #{name}" do
|
|
159
|
+
actual = quietly { snapshot(name, options) }
|
|
160
|
+
expect(actual.keys.sort).to eq(neutral.keys.sort)
|
|
161
|
+
expect(actual).to eq(neutral)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe 'raider new -p examples: validation' do
|
|
169
|
+
after { FileUtils.rm_rf(%w[bad_examples]) }
|
|
170
|
+
|
|
171
|
+
let(:root) { File.expand_path('../../..', __dir__) }
|
|
172
|
+
|
|
173
|
+
it 'rejects an unknown value, names the allowed values and generates nothing' do
|
|
174
|
+
Dir.mktmpdir do |dir|
|
|
175
|
+
_out, err, status = Open3.capture3('ruby', "#{root}/bin/raider", 'new', 'x', '-p',
|
|
176
|
+
'framework:rspec', 'automation:selenium', 'examples:no', chdir: dir)
|
|
177
|
+
expect(status.exitstatus).to eq(1)
|
|
178
|
+
expect(err).to include('Invalid examples "no". Allowed values: true, false')
|
|
179
|
+
expect(File).not_to exist(File.join(dir, 'x'))
|
|
180
|
+
end
|
|
181
|
+
end
|
|
52
182
|
end
|
|
@@ -46,4 +46,24 @@ describe RspecGenerator do
|
|
|
46
46
|
include_examples 'creates rspec files examples',
|
|
47
47
|
"#{FrameworkIndex::RSPEC}_#{AutomationIndex::CROSS_PLATFORM}", 'pdp'
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
describe 'without example tests' do
|
|
51
|
+
%w[selenium ios cross_platform].each do |automation|
|
|
52
|
+
context "with rspec and #{automation}" do
|
|
53
|
+
let(:name) { ensure_project(framework: 'rspec', automation:, examples: false) }
|
|
54
|
+
|
|
55
|
+
it 'creates an empty spec folder' do
|
|
56
|
+
expect(Dir.children("#{name}/spec")).to eq(['.keep'])
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'creates no accessibility example' do
|
|
62
|
+
InvokeGenerators.generate_framework(framework: 'rspec', automation: 'selenium', name: 'skeleton_axe_rspec',
|
|
63
|
+
examples: false, accessibility: true)
|
|
64
|
+
expect(Dir.children('skeleton_axe_rspec/spec')).to eq(['.keep'])
|
|
65
|
+
ensure
|
|
66
|
+
FileUtils.rm_rf('skeleton_axe_rspec')
|
|
67
|
+
end
|
|
68
|
+
end
|
|
49
69
|
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'erb'
|
|
4
4
|
require 'json'
|
|
5
|
+
require 'timeout'
|
|
5
6
|
require 'selenium-webdriver'
|
|
6
7
|
require 'time'
|
|
7
8
|
require 'tmpdir'
|
|
@@ -409,6 +410,51 @@ describe 'SessionState' do
|
|
|
409
410
|
expect(Time.iso8601(saved['saved_at'])).to be > before
|
|
410
411
|
end
|
|
411
412
|
|
|
413
|
+
it 'lets concurrent callers restore a fresh login at the same time' do
|
|
414
|
+
write_state('cookies' => [saved_cookie('session')])
|
|
415
|
+
arrived = Queue.new
|
|
416
|
+
checked = Queue.new
|
|
417
|
+
threads = Array.new(2) do
|
|
418
|
+
Thread.new do
|
|
419
|
+
valid = lambda do
|
|
420
|
+
arrived << 1
|
|
421
|
+
Timeout.timeout(2) { sleep 0.01 until arrived.size == 2 }
|
|
422
|
+
checked << 1
|
|
423
|
+
end
|
|
424
|
+
SessionState.new(SessionStateSpec::FakeDriver.new(cookies:)).reuse(valid:) { raise 'logged in' }
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
threads.each(&:join)
|
|
428
|
+
expect(checked.size).to eq(2)
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
it 'restores a login another worker saved meanwhile, without a fallback warning' do
|
|
432
|
+
write_state('cookies' => [saved_cookie('session')])
|
|
433
|
+
checks = 0
|
|
434
|
+
valid = lambda do
|
|
435
|
+
checks += 1
|
|
436
|
+
write_state('cookies' => [saved_cookie('session')], 'saved_at' => Time.now.utc.iso8601) if checks == 1
|
|
437
|
+
checks > 1
|
|
438
|
+
end
|
|
439
|
+
logins = 0
|
|
440
|
+
expect { state.reuse(valid:) { logins += 1 } }.not_to output.to_stderr
|
|
441
|
+
expect([logins, checks]).to eq([0, 2])
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
it 'warns once when the second restore after a peer save is also invalid' do
|
|
445
|
+
write_state('cookies' => [saved_cookie('session')])
|
|
446
|
+
checks = 0
|
|
447
|
+
valid = lambda do
|
|
448
|
+
checks += 1
|
|
449
|
+
write_state('cookies' => [saved_cookie('session')], 'saved_at' => Time.now.utc.iso8601) if checks == 1
|
|
450
|
+
false
|
|
451
|
+
end
|
|
452
|
+
logins = 0
|
|
453
|
+
expect { state.reuse(valid:) { logins += 1 } }
|
|
454
|
+
.to output(/\A\[login_state\] login restored from \S+ is no longer valid, logging in again\n\z/).to_stderr
|
|
455
|
+
expect(logins).to eq(1)
|
|
456
|
+
end
|
|
457
|
+
|
|
412
458
|
it 'serializes concurrent callers on the lock' do
|
|
413
459
|
count = 0
|
|
414
460
|
drivers = Array.new(2) { SessionStateSpec::FakeDriver.new(cookies:) }
|