ruby_raider 3.12.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/.ruby-version +1 -1
- data/lib/generators/automation/templates/page.tt +8 -3
- data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -3
- 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/cucumber/cucumber_generator.rb +1 -1
- data/lib/generators/cucumber/templates/env.tt +3 -3
- 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 +15 -11
- data/lib/generators/rspec/templates/accessibility_spec.tt +10 -10
- data/lib/generators/rspec/templates/spec.tt +2 -1
- data/lib/generators/templates/common/gemfile.tt +9 -9
- 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/session_state.tt +1 -1
- data/lib/generators/templates/helpers/spec_helper.tt +1 -1
- data/lib/ruby_raider.rb +9 -2
- data/lib/version +1 -1
- data/sig/generators/generator.rbs +2 -0
- data/sig/ruby_raider.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +25 -1
- data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +1 -1
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +2 -4
- data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +2 -4
- data/spec/integration/content/content_helper.rb +1 -5
- data/spec/integration/content/rubocop_content_spec.rb +87 -0
- data/spec/integration/content/syntax_validation_spec.rb +31 -51
- data/spec/integration/content/test_content_spec.rb +5 -2
- data/spec/integration/end_to_end_spec.rb +23 -0
- data/spec/integration/frozen_string_literal_matcher.rb +13 -0
- data/spec/integration/generators/invoke_generators_spec.rb +60 -0
- data/spec/integration/scaffolding_e2e_spec.rb +1 -5
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e1312bb43aceb9be9a70d5e3d09e6694d901f6b5dc3ec80f27b6973de6c8d60c
|
|
4
|
+
data.tar.gz: ad4e86e9f7dd6bbbacac033fe48f18a93d6397040ab29490d0add8456f6ad2cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b913021e191c5ff9fddc114590bc7b6a4a1b0e340a0fb2257da5477c2e3344dcd73088ea1cbac4eb1b28953b7bc3c250d56c648f15e85977088b8d96d3ddbc2b
|
|
7
|
+
data.tar.gz: c1d12da3f4447bbabecc6638895129dc0dd9d23dc08389a8b9d6ee9077c3936855b28746b9a6d9d049b3ced2c2e296462154c88e1845d6357078bb41ab8f75f6
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.4.
|
|
1
|
+
3.4.12
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
<%- if web? && examples? -%>
|
|
2
4
|
require_relative '../components/header'
|
|
3
5
|
<%- end -%>
|
|
@@ -12,9 +14,12 @@ class Page
|
|
|
12
14
|
<%- if wait_helper? -%>
|
|
13
15
|
include WaitHelper
|
|
14
16
|
<%- end -%>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
<%- if cross_platform? || wait_helper? -%>
|
|
18
|
+
|
|
19
|
+
<%- end -%>
|
|
20
|
+
<%= partial('initialize_selector') -%>
|
|
21
|
+
<%= partial('visit_method') -%>
|
|
22
|
+
<%= partial('url_methods') -%>
|
|
18
23
|
|
|
19
24
|
def to_s
|
|
20
25
|
self.class.to_s.sub('Page', ' Page')
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
<%- if watir? -%>
|
|
2
2
|
attr_reader :browser
|
|
3
3
|
|
|
4
4
|
def initialize(browser)
|
|
5
5
|
@browser = browser
|
|
6
6
|
end
|
|
7
|
-
|
|
7
|
+
<%- else -%>
|
|
8
8
|
attr_reader :driver
|
|
9
9
|
|
|
10
10
|
def initialize(driver)
|
|
11
11
|
@driver = driver
|
|
12
12
|
end
|
|
13
|
-
|
|
13
|
+
<%- end -%>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<%- if selenium_based? -%>
|
|
2
|
+
|
|
3
3
|
def visit(*page)
|
|
4
4
|
@driver.navigate.to full_url(page.first)
|
|
5
5
|
end
|
|
6
|
-
|
|
6
|
+
<%- elsif watir? -%>
|
|
7
|
+
|
|
7
8
|
def visit(*page)
|
|
8
9
|
@browser.goto full_url(page.first)
|
|
9
10
|
end
|
|
10
|
-
|
|
11
|
-
<% end %>
|
|
11
|
+
<%- end -%>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<%- if selenium_based? -%>
|
|
2
|
-
|
|
2
|
+
<%= partial('selenium_env') -%>
|
|
3
3
|
<%- elsif mobile? -%>
|
|
4
|
-
|
|
4
|
+
<%= partial('appium_env') -%>
|
|
5
5
|
<%- else -%>
|
|
6
|
-
|
|
6
|
+
<%= partial('watir_env') -%>
|
|
7
7
|
<%- end -%>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
<%- if watir? -%>
|
|
2
|
-
|
|
4
|
+
<%= partial('watir_world', strip: true) %>
|
|
3
5
|
<%- else -%>
|
|
4
|
-
|
|
5
|
-
<%- end -%>
|
|
6
|
+
<%= partial('driver_world', strip: true) %>
|
|
7
|
+
<%- end -%>
|
data/lib/generators/generator.rb
CHANGED
|
@@ -23,37 +23,36 @@ class Generator < Thor::Group
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def cucumber?
|
|
26
|
-
|
|
26
|
+
framework == 'cucumber'
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# The framework is cross platform when it supports Android and iOS
|
|
30
30
|
def cross_platform?
|
|
31
|
-
|
|
31
|
+
automation == 'cross_platform'
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def mobile?
|
|
35
|
-
|
|
35
|
+
%w[android ios cross_platform].include?(automation)
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def single_platform?
|
|
39
|
-
|
|
39
|
+
%w[android ios].include?(automation)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def ios?
|
|
43
|
-
|
|
43
|
+
automation == 'ios'
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def android?
|
|
47
|
-
|
|
47
|
+
automation == 'android'
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def rspec?
|
|
51
|
-
|
|
51
|
+
framework == 'rspec'
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
# Only the options after automation, framework and name, so a project named "azure" is not Azure CI
|
|
55
54
|
def azure?
|
|
56
|
-
|
|
55
|
+
options_after_name.include?('azure')
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
def github_ci?
|
|
@@ -76,7 +75,7 @@ class Generator < Thor::Group
|
|
|
76
75
|
|
|
77
76
|
# False for `raider new -p examples:false`: an empty skeleton without the example pages, tests and wiring
|
|
78
77
|
def examples?
|
|
79
|
-
!
|
|
78
|
+
!options_after_name.include?('no_examples')
|
|
80
79
|
end
|
|
81
80
|
|
|
82
81
|
def web?
|
|
@@ -84,7 +83,7 @@ class Generator < Thor::Group
|
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
def axe_addon?
|
|
87
|
-
|
|
86
|
+
options_after_name.include?('axe_addon')
|
|
88
87
|
end
|
|
89
88
|
|
|
90
89
|
def selenium_based?
|
|
@@ -98,6 +97,11 @@ class Generator < Thor::Group
|
|
|
98
97
|
|
|
99
98
|
private
|
|
100
99
|
|
|
100
|
+
# Option flags only: everything after automation, framework and name
|
|
101
|
+
def options_after_name
|
|
102
|
+
args.drop(3)
|
|
103
|
+
end
|
|
104
|
+
|
|
101
105
|
def _initializer
|
|
102
106
|
@_initializer ||= super
|
|
103
107
|
end
|
|
@@ -4,18 +4,18 @@ require_relative '../helpers/spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe 'Accessibility' do
|
|
6
6
|
before do
|
|
7
|
-
|
|
8
|
-
browser.goto 'https://raider-test-site.onrender.com/'
|
|
9
|
-
|
|
10
|
-
driver.get 'https://raider-test-site.onrender.com/'
|
|
11
|
-
|
|
7
|
+
<%- if watir? -%>
|
|
8
|
+
browser.goto 'https://raider-test-site.onrender.com/'
|
|
9
|
+
<%- else -%>
|
|
10
|
+
driver.get 'https://raider-test-site.onrender.com/'
|
|
11
|
+
<%- end -%>
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
it 'has no accessibility violations on the page' do
|
|
15
|
-
|
|
16
|
-
expect(browser.driver).to be_axe_clean
|
|
17
|
-
|
|
18
|
-
expect(driver).to be_axe_clean
|
|
19
|
-
|
|
15
|
+
<%- if watir? -%>
|
|
16
|
+
expect(browser.driver).to be_axe_clean
|
|
17
|
+
<%- else -%>
|
|
18
|
+
expect(driver).to be_axe_clean
|
|
19
|
+
<%- end -%>
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -63,6 +63,8 @@ describe 'Account', :logged_in do
|
|
|
63
63
|
end
|
|
64
64
|
# rubocop:enable RSpec/MultipleDescribes
|
|
65
65
|
<%- else -%>
|
|
66
|
+
# frozen_string_literal: true
|
|
67
|
+
|
|
66
68
|
<% if cross_platform? -%>
|
|
67
69
|
require_relative '../helpers/appium_helper'
|
|
68
70
|
<%- end -%>
|
|
@@ -72,7 +74,6 @@ require_relative '../page_objects/pages/pdp'
|
|
|
72
74
|
|
|
73
75
|
class PdpSpec
|
|
74
76
|
describe 'PDP' do
|
|
75
|
-
|
|
76
77
|
let(:home_page) { Home.new(driver) }
|
|
77
78
|
let(:pdp_page) { Pdp.new(driver) }
|
|
78
79
|
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
source 'https://rubygems.org'
|
|
2
2
|
|
|
3
3
|
gem 'activesupport'
|
|
4
|
-
|
|
5
|
-
gem 'appium_lib'
|
|
4
|
+
<%- if mobile? -%>
|
|
6
5
|
gem 'appium_console'
|
|
7
|
-
|
|
6
|
+
gem 'appium_lib'
|
|
7
|
+
<%- end -%>
|
|
8
8
|
<%- if axe_addon? -%>
|
|
9
9
|
gem 'axe-core-rspec'
|
|
10
10
|
gem 'axe-core-selenium'
|
|
11
11
|
<%- end -%>
|
|
12
|
+
<%- if cucumber? -%>
|
|
13
|
+
gem 'cucumber'
|
|
14
|
+
<%- end -%>
|
|
12
15
|
gem 'parallel_split_test'
|
|
13
16
|
gem 'parallel_tests'
|
|
17
|
+
gem 'raider_reporter', '~> 0.10'
|
|
14
18
|
gem 'rake'
|
|
15
19
|
gem 'reek'
|
|
16
|
-
gem '
|
|
20
|
+
gem 'rspec'
|
|
17
21
|
<%- if rspec? -%>
|
|
18
22
|
gem 'rspec-retry'
|
|
19
23
|
<%- end -%>
|
|
20
|
-
gem '<%= framework %>'
|
|
21
|
-
<%- if cucumber? -%>
|
|
22
|
-
gem 'rspec'
|
|
23
|
-
<%- end -%>
|
|
24
24
|
gem 'rubocop'
|
|
25
25
|
<%- if rspec? -%>
|
|
26
26
|
gem 'rubocop-rspec'
|
|
27
27
|
<%- end -%>
|
|
28
28
|
gem 'ruby_raider', '<%= ruby_raider_version_requirement %>'
|
|
29
|
-
<%- if selenium_based?
|
|
29
|
+
<%- if selenium_based? -%>
|
|
30
30
|
gem 'selenium-webdriver'
|
|
31
31
|
<%- end -%>
|
|
32
32
|
<%- if watir? -%>
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module AppiumHelper
|
|
2
4
|
def element(opts = {})
|
|
3
|
-
|
|
5
|
+
return driver.find_element(strategy(opts) => selector(opts)) if opts[os]
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
driver.find_element(opts)
|
|
6
8
|
end
|
|
7
9
|
|
|
8
10
|
def elements(opts = {})
|
|
9
|
-
|
|
11
|
+
return driver.find_elements(strategy(opts) => selector(opts)) if opts[os]
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
driver.find_elements(opts)
|
|
14
|
+
end
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
def os
|
|
17
|
+
driver.appium_device
|
|
18
|
+
end
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
private
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
def strategy(opts)
|
|
23
|
+
opts[os].keys.first
|
|
24
|
+
end
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
def selector(opts)
|
|
27
|
+
opts[os][strategy(opts)]
|
|
28
|
+
end
|
|
27
29
|
end
|
|
@@ -6,7 +6,6 @@ require 'watir'
|
|
|
6
6
|
require_relative 'video_helper'
|
|
7
7
|
|
|
8
8
|
module BrowserHelper
|
|
9
|
-
|
|
10
9
|
def browser(*args)
|
|
11
10
|
@browser ||= create_browser(*args)
|
|
12
11
|
end
|
|
@@ -23,7 +22,7 @@ module BrowserHelper
|
|
|
23
22
|
args = args.empty? ? config['browser_arguments'][browser_name] : args
|
|
24
23
|
args += ['--headless'] if (ENV['HEADLESS'] || config['headless']) && !args.include?('--headless')
|
|
25
24
|
capitalized = browser_name == 'ie' ? browser_name.upcase : browser_name.capitalize
|
|
26
|
-
browser_options = "Selenium::WebDriver::#{capitalized}::Options".constantize.new(args:)
|
|
25
|
+
browser_options = "Selenium::WebDriver::#{capitalized}::Options".constantize.new(args: args)
|
|
27
26
|
arguments = { options: browser_options }
|
|
28
27
|
listener = VideoHelper.listener
|
|
29
28
|
arguments[:listener] = listener if listener
|
|
@@ -17,7 +17,7 @@ require 'yaml'
|
|
|
17
17
|
class SessionState
|
|
18
18
|
FORMAT = 1
|
|
19
19
|
DEFAULT_MAX_AGE = 3600
|
|
20
|
-
NAME_PATTERN =
|
|
20
|
+
NAME_PATTERN = Regexp.new('\A[a-z0-9_-]+\z').freeze
|
|
21
21
|
DIR = File.join('.raider', 'auth')
|
|
22
22
|
READ_STORAGE = 'return Object.assign({}, window[arguments[0]]);'
|
|
23
23
|
WRITE_STORAGE = 'var s = window[arguments[0]]; s.clear(); var d = arguments[1]; ' \
|
data/lib/ruby_raider.rb
CHANGED
|
@@ -52,12 +52,19 @@ module RubyRaider
|
|
|
52
52
|
map 'u' => 'utility'
|
|
53
53
|
|
|
54
54
|
no_tasks do
|
|
55
|
-
# -p values arrive as strings;
|
|
55
|
+
# -p values arrive as strings; examples must be true or false, in any case
|
|
56
56
|
def generate_from_parameters(params)
|
|
57
|
-
params[:examples] = params[:examples]
|
|
57
|
+
params[:examples] = parse_examples(params[:examples]) if params.key?(:examples)
|
|
58
58
|
InvokeGenerators.generate_framework(params)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
def parse_examples(value)
|
|
62
|
+
normalised = value.to_s.downcase
|
|
63
|
+
return normalised == 'true' if %w[true false].include?(normalised)
|
|
64
|
+
|
|
65
|
+
raise Thor::Error, "Invalid examples #{value.to_s.inspect}. Allowed values: true, false"
|
|
66
|
+
end
|
|
67
|
+
|
|
61
68
|
def merge_flags(params)
|
|
62
69
|
params[:accessibility] = true if options[:accessibility]
|
|
63
70
|
end
|
data/lib/version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.12.
|
|
1
|
+
3.12.1
|
data/sig/ruby_raider.rbs
CHANGED
|
@@ -33,7 +33,31 @@ RSpec.describe RubyRaider::Raider do # rubocop:disable RSpec/FilePath
|
|
|
33
33
|
expect(InvokeGenerators).to have_received(:generate_framework).with(hash_excluding(:examples))
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
it '
|
|
36
|
+
it 'accepts examples:FALSE in any case as false' do
|
|
37
|
+
allow(InvokeGenerators).to receive(:generate_framework)
|
|
38
|
+
|
|
39
|
+
described_class.new.invoke(:new, nil, %w[my_project -p framework:rspec automation:selenium examples:FALSE])
|
|
40
|
+
|
|
41
|
+
expect(InvokeGenerators).to have_received(:generate_framework).with(hash_including(examples: false))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'accepts examples:True in any case as true' do
|
|
45
|
+
allow(InvokeGenerators).to receive(:generate_framework)
|
|
46
|
+
|
|
47
|
+
described_class.new.invoke(:new, nil, %w[my_project -p framework:rspec automation:selenium examples:True])
|
|
48
|
+
|
|
49
|
+
expect(InvokeGenerators).to have_received(:generate_framework).with(hash_including(examples: true))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'rejects any other examples value before generating' do
|
|
53
|
+
allow(InvokeGenerators).to receive(:generate_framework)
|
|
54
|
+
|
|
55
|
+
expect { described_class.new.invoke(:new, nil, %w[my_project -p framework:rspec automation:selenium examples:no]) }
|
|
56
|
+
.to raise_error(Thor::Error, 'Invalid examples "no". Allowed values: true, false')
|
|
57
|
+
expect(InvokeGenerators).not_to have_received(:generate_framework)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'keeps the examples for true' do
|
|
37
61
|
allow(InvokeGenerators).to receive(:generate_framework)
|
|
38
62
|
|
|
39
63
|
described_class.new.invoke(:new, nil, %w[my_project -p framework:rspec automation:selenium examples:true])
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../components/header'
|
|
2
4
|
|
|
3
5
|
class Page
|
|
4
|
-
|
|
5
6
|
attr_reader :browser
|
|
6
7
|
|
|
7
8
|
def initialize(browser)
|
|
8
9
|
@browser = browser
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
def visit(*page)
|
|
13
13
|
@browser.goto full_url(page.first)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
16
|
def full_url(*page)
|
|
19
17
|
"#{base_url}#{url(*page)}"
|
|
20
18
|
end
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../components/header'
|
|
2
4
|
|
|
3
5
|
class Page
|
|
4
|
-
|
|
5
6
|
attr_reader :browser
|
|
6
7
|
|
|
7
8
|
def initialize(browser)
|
|
8
9
|
@browser = browser
|
|
9
10
|
end
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
def visit(*page)
|
|
13
13
|
@browser.goto full_url(page.first)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
16
|
def full_url(*page)
|
|
19
17
|
"#{base_url}#{url(*page)}"
|
|
20
18
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../spec_helper'
|
|
4
|
+
require_relative '../frozen_string_literal_matcher'
|
|
4
5
|
|
|
5
6
|
module ContentHelper
|
|
6
7
|
def read_generated(project, relative_path)
|
|
@@ -12,11 +13,6 @@ module ContentHelper
|
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
|
|
15
|
-
RSpec::Matchers.define :have_frozen_string_literal do
|
|
16
|
-
match { |content| content.include?('# frozen_string_literal: true') }
|
|
17
|
-
failure_message { 'expected file to contain frozen_string_literal magic comment' }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
16
|
RSpec::Matchers.define :have_valid_ruby_syntax do
|
|
21
17
|
match do |content|
|
|
22
18
|
RubyVM::InstructionSequence.compile(content)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'rubocop'
|
|
5
|
+
require 'stringio'
|
|
6
|
+
require 'tmpdir'
|
|
7
|
+
require_relative 'content_helper'
|
|
8
|
+
|
|
9
|
+
RUBOCOP_WEB_AUTOMATIONS = %w[selenium watir].freeze
|
|
10
|
+
|
|
11
|
+
# Every generated project must pass the RuboCop it ships: its own .rubocop.yml, run in-process with
|
|
12
|
+
# this gem's bundle. The project is linted from a copy outside this repo, so RuboCop resolves the
|
|
13
|
+
# target Ruby the way a user's fresh project does (no TargetRubyVersion, so its 2.7 default) instead
|
|
14
|
+
# of picking up this repo's .ruby-version or gemspec. The end-to-end spec repeats the check with the
|
|
15
|
+
# project's own resolved gems.
|
|
16
|
+
describe 'Generated projects pass their own RuboCop' do
|
|
17
|
+
def rubocop_report(project_name)
|
|
18
|
+
Dir.mktmpdir('raider_rubocop') do |tmp|
|
|
19
|
+
dir = File.join(tmp, File.basename(project_name))
|
|
20
|
+
FileUtils.cp_r(project_name, dir)
|
|
21
|
+
with_project_root(dir) { yield lint(dir), target_ruby(dir) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with_project_root(dir)
|
|
26
|
+
RuboCop::ConfigFinder.project_root = dir
|
|
27
|
+
yield
|
|
28
|
+
ensure
|
|
29
|
+
RuboCop::ConfigFinder.project_root = nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def target_ruby(dir)
|
|
33
|
+
RuboCop::TargetRuby.new(RuboCop::ConfigLoader.configuration_from_file(File.join(dir, '.rubocop.yml'))).version
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def lint(dir)
|
|
37
|
+
report, exit_code = capture_rubocop_json(['--config', File.join(dir, '.rubocop.yml'), '--cache', 'false',
|
|
38
|
+
'--format', 'json', dir])
|
|
39
|
+
offenses = report.fetch('files').flat_map do |file|
|
|
40
|
+
path = file.fetch('path').delete_prefix("#{dir}/")
|
|
41
|
+
file.fetch('offenses').map do |offense|
|
|
42
|
+
"#{path}:#{offense.dig('location', 'line')}: #{offense.fetch('cop_name')}: #{offense.fetch('message')}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
{ offenses: offenses, inspected: report.fetch('summary').fetch('inspected_file_count'), exit_code: exit_code }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def capture_rubocop_json(args)
|
|
49
|
+
original = $stdout
|
|
50
|
+
$stdout = StringIO.new
|
|
51
|
+
exit_code = RuboCop::CLI.new.run(args)
|
|
52
|
+
[JSON.parse($stdout.string), exit_code]
|
|
53
|
+
ensure
|
|
54
|
+
$stdout = original
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def expect_no_offenses(project_name)
|
|
58
|
+
expect(File).to exist(File.join(project_name, '.rubocop.yml'))
|
|
59
|
+
rubocop_report(project_name) do |summary, target|
|
|
60
|
+
offenses = summary.fetch(:offenses)
|
|
61
|
+
expect(target).to eq(RuboCop::TargetRuby::DEFAULT_VERSION)
|
|
62
|
+
expect(summary.fetch(:inspected)).to be_positive, "#{project_name}: RuboCop inspected no files"
|
|
63
|
+
expect(offenses).to be_empty, "#{project_name} has #{offenses.size} RuboCop offenses:\n#{offenses.join("\n")}"
|
|
64
|
+
expect(summary.fetch(:exit_code)).to eq(0)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
FRAMEWORKS.each do |framework|
|
|
69
|
+
AUTOMATION_TYPES.each do |automation|
|
|
70
|
+
{ 'with examples' => true, 'without examples' => false }.each do |label, examples|
|
|
71
|
+
it "has no offenses for #{framework} and #{automation} (#{label})" do
|
|
72
|
+
expect_no_offenses(ensure_project(framework:, automation:, examples:))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
RUBOCOP_WEB_AUTOMATIONS.each do |automation|
|
|
78
|
+
it "has no offenses for #{framework} and #{automation} with accessibility" do
|
|
79
|
+
name = "rubocop_axe_#{framework}_#{automation}"
|
|
80
|
+
InvokeGenerators.generate_framework(automation:, framework:, name:, accessibility: true)
|
|
81
|
+
expect_no_offenses(name)
|
|
82
|
+
ensure
|
|
83
|
+
FileUtils.rm_rf(name)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -2,66 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'content_helper'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
SYNTAX_ALL_FRAMEWORKS = %w[cucumber rspec].freeze
|
|
5
|
+
SYNTAX_MOBILE_AUTOMATIONS = %w[android ios cross_platform].freeze
|
|
7
6
|
|
|
7
|
+
# Projects are generated by spec_helper's before(:all), so the file list is read inside the
|
|
8
|
+
# examples (after generation), never when this file loads.
|
|
8
9
|
describe 'Ruby syntax validation' do
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
project_name = "#{framework}_#{automation}"
|
|
13
|
-
|
|
14
|
-
Dir.glob("#{project_name}/**/*.rb").each do |file|
|
|
15
|
-
relative = file.sub("#{project_name}/", '')
|
|
16
|
-
|
|
17
|
-
it "#{relative} has valid Ruby syntax" do
|
|
18
|
-
content = File.read(file)
|
|
19
|
-
expect(content).to have_valid_ruby_syntax
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it "#{relative} has frozen_string_literal" do
|
|
23
|
-
content = File.read(file)
|
|
24
|
-
expect(content).to have_frozen_string_literal
|
|
25
|
-
end
|
|
26
|
-
end
|
|
10
|
+
def generated_ruby_files(project_name)
|
|
11
|
+
Dir.glob('**/*.rb', base: project_name).sort
|
|
12
|
+
end
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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'
|
|
14
|
+
def syntax_error_for(content)
|
|
15
|
+
RubyVM::InstructionSequence.compile(content)
|
|
16
|
+
nil
|
|
17
|
+
rescue SyntaxError => e
|
|
18
|
+
e.message
|
|
19
|
+
end
|
|
36
20
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
FRAMEWORKS.each do |framework|
|
|
22
|
+
AUTOMATION_TYPES.each do |automation|
|
|
23
|
+
{ 'with examples' => true, 'without examples' => false }.each do |label, examples|
|
|
24
|
+
it "generates only valid Ruby with frozen_string_literal for #{framework} and #{automation} (#{label})" do
|
|
25
|
+
project_name = ensure_project(framework:, automation:, examples:)
|
|
26
|
+
files = generated_ruby_files(project_name)
|
|
27
|
+
|
|
28
|
+
expect(files).not_to be_empty
|
|
29
|
+
aggregate_failures do
|
|
30
|
+
files.each do |relative|
|
|
31
|
+
content = File.read(File.join(project_name, relative))
|
|
32
|
+
expect(syntax_error_for(content)).to be_nil, "#{project_name}/#{relative} has a syntax error"
|
|
33
|
+
expect(content).to have_frozen_string_literal, "#{project_name}/#{relative} lacks frozen_string_literal"
|
|
34
|
+
end
|
|
42
35
|
end
|
|
43
36
|
end
|
|
44
37
|
end
|
|
45
38
|
end
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
40
|
+
SYNTAX_MOBILE_AUTOMATIONS.each do |automation|
|
|
41
|
+
it "#{framework}_#{automation} wait_helper has the mobile gestures and no web-only methods" do
|
|
42
|
+
content = File.read(File.join(ensure_project(framework:, automation:), 'helpers/wait_helper.rb'))
|
|
43
|
+
expect(content).to include("'mobile: swipeGesture'").and include('def scroll_to(')
|
|
44
|
+
expect(content).not_to include('def drag_and_drop(')
|
|
65
45
|
end
|
|
66
46
|
end
|
|
67
47
|
end
|
|
@@ -212,10 +212,13 @@ describe 'Example tests on the wait helper' do
|
|
|
212
212
|
end
|
|
213
213
|
end
|
|
214
214
|
|
|
215
|
-
# The Watir output must stay as master generated it (1274a66); the snapshots in
|
|
215
|
+
# The Watir output must stay as master generated it (1274a66), plus the 3.12.1 lint fixes; the snapshots in
|
|
216
216
|
# spec/fixtures/watir_master were generated from that commit's lib.
|
|
217
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)
|
|
218
|
+
# then apply only the Readme deprecation-line delta (the `> **Deprecated:**` line under the heading block) and
|
|
219
|
+
# the 3.12.1 changes: RuboCop lint fixes (page.rb loses its extra blank lines, login_steps.rb ends with a newline)
|
|
220
|
+
# plus the frozen_string_literal comments the syntax spec requires (Style/FrozenStringLiteralComment is disabled
|
|
221
|
+
# in the generated .rubocop.yml, so RuboCop does not ask for them) on the Watir page, world and user_factory files.
|
|
219
222
|
describe 'Watir example output' do
|
|
220
223
|
snapshots = File.expand_path('../../fixtures/watir_master', __dir__)
|
|
221
224
|
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -120,3 +121,62 @@ describe 'Projects named like a generator option' do
|
|
|
120
121
|
expect(File.read('selenium/page_objects/abstract/page.rb')).not_to include('find_element')
|
|
121
122
|
end
|
|
122
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
|
|
182
|
+
end
|
|
@@ -4,13 +4,9 @@ require 'fileutils'
|
|
|
4
4
|
require 'pathname'
|
|
5
5
|
require_relative '../../lib/commands/scaffolding_commands'
|
|
6
6
|
require_relative '../../lib/scaffolding/scaffolding'
|
|
7
|
+
require_relative 'frozen_string_literal_matcher'
|
|
7
8
|
|
|
8
9
|
# Custom matchers (defined inline to avoid pulling in integration spec_helper)
|
|
9
|
-
RSpec::Matchers.define :have_frozen_string_literal do
|
|
10
|
-
match { |content| content.include?('# frozen_string_literal: true') }
|
|
11
|
-
failure_message { 'expected file to contain frozen_string_literal magic comment' }
|
|
12
|
-
end
|
|
13
|
-
|
|
14
10
|
RSpec::Matchers.define :have_valid_ruby_syntax do
|
|
15
11
|
match do |content|
|
|
16
12
|
RubyVM::InstructionSequence.compile(content)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_raider
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.12.
|
|
4
|
+
version: 3.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Agustin Pequeno
|
|
@@ -368,10 +368,12 @@ files:
|
|
|
368
368
|
- spec/integration/content/page_content_spec.rb
|
|
369
369
|
- spec/integration/content/readme_content_spec.rb
|
|
370
370
|
- spec/integration/content/rspec_reporter_content_spec.rb
|
|
371
|
+
- spec/integration/content/rubocop_content_spec.rb
|
|
371
372
|
- spec/integration/content/syntax_validation_spec.rb
|
|
372
373
|
- spec/integration/content/test_content_spec.rb
|
|
373
374
|
- spec/integration/end_to_end_features_spec.rb
|
|
374
375
|
- spec/integration/end_to_end_spec.rb
|
|
376
|
+
- spec/integration/frozen_string_literal_matcher.rb
|
|
375
377
|
- spec/integration/generators/automation_generator_spec.rb
|
|
376
378
|
- spec/integration/generators/axe_addon_spec.rb
|
|
377
379
|
- spec/integration/generators/azure_generator_spec.rb
|