mini_autobot 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +26 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +191 -0
- data/LICENSE +22 -0
- data/README.md +632 -0
- data/bin/mini_autobot +5 -0
- data/lib/mini_autobot.rb +44 -0
- data/lib/mini_autobot/connector.rb +288 -0
- data/lib/mini_autobot/console.rb +15 -0
- data/lib/mini_autobot/emails.rb +5 -0
- data/lib/mini_autobot/emails/mailbox.rb +15 -0
- data/lib/mini_autobot/endeca/base.rb +6 -0
- data/lib/mini_autobot/init.rb +63 -0
- data/lib/mini_autobot/logger.rb +12 -0
- data/lib/mini_autobot/page_objects.rb +22 -0
- data/lib/mini_autobot/page_objects/base.rb +264 -0
- data/lib/mini_autobot/page_objects/overlay/base.rb +76 -0
- data/lib/mini_autobot/page_objects/widgets/base.rb +47 -0
- data/lib/mini_autobot/parallel.rb +197 -0
- data/lib/mini_autobot/runner.rb +91 -0
- data/lib/mini_autobot/settings.rb +78 -0
- data/lib/mini_autobot/test_case.rb +233 -0
- data/lib/mini_autobot/test_cases.rb +7 -0
- data/lib/mini_autobot/utils.rb +10 -0
- data/lib/mini_autobot/utils/assertion_helper.rb +35 -0
- data/lib/mini_autobot/utils/castable.rb +103 -0
- data/lib/mini_autobot/utils/data_generator_helper.rb +145 -0
- data/lib/mini_autobot/utils/endeca_helper.rb +46 -0
- data/lib/mini_autobot/utils/loggable.rb +16 -0
- data/lib/mini_autobot/utils/overlay_and_widget_helper.rb +78 -0
- data/lib/mini_autobot/utils/page_object_helper.rb +209 -0
- data/lib/mini_autobot/version.rb +3 -0
- data/lib/minitap/minitest5_rent.rb +22 -0
- data/lib/minitest/autobot_settings_plugin.rb +77 -0
- data/lib/tapout/custom_reporters/fancy_tap_reporter.rb +94 -0
- data/lib/yard/tagged_test_case_handler.rb +61 -0
- data/mini_autobot.gemspec +38 -0
- metadata +299 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
module MiniAutobot
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
attr_accessor :options
|
5
|
+
@after_hooks = []
|
6
|
+
|
7
|
+
def self.after_run(&blk)
|
8
|
+
@after_hooks << blk
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.run!(args)
|
12
|
+
exit_code = self.run(args)
|
13
|
+
@after_hooks.reverse_each(&:call)
|
14
|
+
Kernel.exit(exit_code || false)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.run args = []
|
18
|
+
Minitest.load_plugins
|
19
|
+
|
20
|
+
@options = Minitest.process_args args
|
21
|
+
|
22
|
+
self.before_run
|
23
|
+
|
24
|
+
reporter = Minitest::CompositeReporter.new
|
25
|
+
reporter << Minitest::SummaryReporter.new(@options[:io], @options)
|
26
|
+
reporter << Minitest::ProgressReporter.new(@options[:io], @options)
|
27
|
+
|
28
|
+
Minitest.reporter = reporter # this makes it available to plugins
|
29
|
+
Minitest.init_plugins @options
|
30
|
+
Minitest.reporter = nil # runnables shouldn't depend on the reporter, ever
|
31
|
+
|
32
|
+
reporter.start
|
33
|
+
Minitest.__run reporter, @options
|
34
|
+
Minitest.parallel_executor.shutdown
|
35
|
+
reporter.report
|
36
|
+
|
37
|
+
reporter.passed?
|
38
|
+
end
|
39
|
+
|
40
|
+
# before hook where you have parsed @options when loading tests
|
41
|
+
def self.before_run
|
42
|
+
host_env = @options[:env]
|
43
|
+
if host_env.nil?
|
44
|
+
# TODO(phu): default host needs to be set in a user's local env file
|
45
|
+
host = 'rent'
|
46
|
+
puts "No argument given for option -e \nLoading tests using default host: #{host}"
|
47
|
+
else
|
48
|
+
host = host_env.split(/_/)[0]
|
49
|
+
end
|
50
|
+
self.load_tests(host)
|
51
|
+
end
|
52
|
+
|
53
|
+
# only load tests you need by specifying env option in command line
|
54
|
+
def self.load_tests(host)
|
55
|
+
tests_yml_full_path = MiniAutobot.root.join('config/mini_autobot', 'tests.yml').to_s
|
56
|
+
if File.exist? tests_yml_full_path
|
57
|
+
tests_yml = YAML.load_file tests_yml_full_path
|
58
|
+
tests_dir_relative_path = tests_yml['tests_dir']['relative_path']
|
59
|
+
multi_host_flag = tests_yml['tests_dir']['multi-host']
|
60
|
+
else
|
61
|
+
puts "Config file #{tests_yml_full_path} doesn't exist"
|
62
|
+
puts "mini_autobot doesn't know where your tests are located and how they are structured"
|
63
|
+
end
|
64
|
+
|
65
|
+
self.configure_load_path(tests_dir_relative_path)
|
66
|
+
|
67
|
+
# load page_objects.rb first
|
68
|
+
Dir.glob("#{tests_dir_relative_path}/#{multi_host_flag ? host+'/' : ''}*.rb") do |f|
|
69
|
+
f.sub!(/^#{tests_dir_relative_path}\//, '')
|
70
|
+
require f
|
71
|
+
end
|
72
|
+
|
73
|
+
# files under subdirectories shouldn't be loaded, eg. archive/
|
74
|
+
Dir.glob("#{tests_dir_relative_path}/#{multi_host_flag ? host+'/' : ''}test_cases/*.rb") do |f|
|
75
|
+
f.sub!(/^#{tests_dir_relative_path}\//, '')
|
76
|
+
require f
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.configure_load_path(tests_dir_relative_path)
|
81
|
+
tests_dir_full_path = MiniAutobot.root.join(tests_dir_relative_path).to_s
|
82
|
+
if Dir.exist? tests_dir_full_path
|
83
|
+
$LOAD_PATH << tests_dir_full_path
|
84
|
+
else
|
85
|
+
puts "Tests directory #{tests_dir_full_path} doesn't exist"
|
86
|
+
puts "No test will run."
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module MiniAutobot
|
2
|
+
|
3
|
+
# An object that holds runtime settings.
|
4
|
+
#
|
5
|
+
# Furthermore, Minitest doesn't provide any good way of passing a hash of
|
6
|
+
# options to each test.
|
7
|
+
#
|
8
|
+
# TODO: We're importing ActiveSupport's extensions to Hash, which means that
|
9
|
+
# we'll be amending the way Hash objects work; once AS updates themselves to
|
10
|
+
# ruby 2.0 refinements, let's move towards that.
|
11
|
+
class Settings
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@hsh = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
settings = self.class.public_instance_methods(false).sort.map(&:inspect).join(', ')
|
19
|
+
"#<MiniAutobot::Settings #{settings}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
def auto_finalize?
|
23
|
+
hsh.fetch(:auto_finalize, true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def connector
|
27
|
+
hsh.fetch(:connector, :firefox).to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def env
|
31
|
+
# add a gitignored env file which stores a default env
|
32
|
+
# pass the default env in as default
|
33
|
+
hsh.fetch(:env, :rent_qa).to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def io
|
37
|
+
hsh[:io]
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge!(other)
|
41
|
+
hsh.merge!(other.symbolize_keys)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def parallel?
|
46
|
+
hsh.fetch(:parallel, false)
|
47
|
+
end
|
48
|
+
|
49
|
+
def raw_arguments
|
50
|
+
hsh.fetch(:args, nil).to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def reuse_driver?
|
54
|
+
hsh.fetch(:reuse_driver, false)
|
55
|
+
end
|
56
|
+
|
57
|
+
def seed
|
58
|
+
hsh.fetch(:seed, nil).to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
def tags
|
62
|
+
hsh[:tags] ||= []
|
63
|
+
end
|
64
|
+
|
65
|
+
def verbose?
|
66
|
+
verbosity_level > 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def verbosity_level
|
70
|
+
hsh.fetch(:verbosity_level, 0).to_i
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
attr_reader :hsh
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
module MiniAutobot
|
2
|
+
|
3
|
+
# An MiniAutobot-specific test case container, which extends the default ones,
|
4
|
+
# adds convenience helper methods, and manages page objects automatically.
|
5
|
+
class TestCase < Minitest::Test
|
6
|
+
@@regression_suite = Array.new
|
7
|
+
@@already_executed = false
|
8
|
+
@@serials = Array.new
|
9
|
+
@@test_suite_data = if File.exist?(MiniAutobot.root.join("config/mini_autobot/test_suite.yml"))
|
10
|
+
YAML.load_file(MiniAutobot.root.join("config/mini_autobot/test_suite.yml"))
|
11
|
+
else
|
12
|
+
default = {"regression"=>{"tag_to_exclude"=>:non_regression}}
|
13
|
+
puts "config/mini_autobot/test_suite.yml doesn't exist, using default:\n#{default}"
|
14
|
+
default
|
15
|
+
end
|
16
|
+
|
17
|
+
# Standard exception class that signals that the test with that name has
|
18
|
+
# already been defined.
|
19
|
+
class TestAlreadyDefined < ::StandardError; end
|
20
|
+
|
21
|
+
# Include helper modules
|
22
|
+
include MiniAutobot::Utils::AssertionHelper
|
23
|
+
include MiniAutobot::Utils::DataGeneratorHelper
|
24
|
+
include MiniAutobot::Utils::Loggable
|
25
|
+
include MiniAutobot::Utils::PageObjectHelper
|
26
|
+
|
27
|
+
class <<self
|
28
|
+
|
29
|
+
# @!attribute [rw] options
|
30
|
+
# @return [Hash] test case options
|
31
|
+
attr_accessor :options
|
32
|
+
|
33
|
+
# Explicitly remove _all_ tests from the current class. This will also
|
34
|
+
# remove inherited test cases.
|
35
|
+
#
|
36
|
+
# @return [TestCase] self
|
37
|
+
def remove_tests
|
38
|
+
klass = class <<self; self; end
|
39
|
+
public_instance_methods.grep(/^test_/).each do |method|
|
40
|
+
klass.send(:undef_method, method.to_sym)
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# Call this at the top of your test case class in order to run all tests
|
46
|
+
# in alphabetical order
|
47
|
+
#
|
48
|
+
# @return [TestCase] self
|
49
|
+
# @example
|
50
|
+
# class SomeName < TestCase
|
51
|
+
# run_in_order!
|
52
|
+
#
|
53
|
+
# test :feature_search_01 { ... }
|
54
|
+
# test :feature_search_02 { ... }
|
55
|
+
# end
|
56
|
+
def run_in_order!
|
57
|
+
# `self` is the class, so we want to reopen the metaclass instead, and
|
58
|
+
# redefine the methods there
|
59
|
+
class <<self
|
60
|
+
undef_method :test_order if method_defined? :test_order
|
61
|
+
define_method :test_order do
|
62
|
+
:alpha
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Be nice and return the class back
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Filter out anything not matching our tag selection, if any.
|
71
|
+
#
|
72
|
+
# @return [Enumerable<Symbol>] the methods marked runnable
|
73
|
+
def runnable_methods
|
74
|
+
methods = super
|
75
|
+
selected = MiniAutobot.settings.tags
|
76
|
+
|
77
|
+
if MiniAutobot.settings.parallel?
|
78
|
+
# check this because I don't know why this runnable_methods gets called three times consecutively when one starts running tests
|
79
|
+
if @@already_executed
|
80
|
+
exit
|
81
|
+
end
|
82
|
+
|
83
|
+
# todo get the number value from "--parallel=" and replace nil with it
|
84
|
+
parallel = Parallel.new(nil, @@regression_suite)
|
85
|
+
parallel.run_in_parallel!
|
86
|
+
|
87
|
+
@@already_executed = true
|
88
|
+
end
|
89
|
+
|
90
|
+
# If no tags are selected, run all tests
|
91
|
+
return methods if selected.nil? || selected.empty?
|
92
|
+
|
93
|
+
return methods.select do |method|
|
94
|
+
# If the method's tags match any of the tag sets, allow it to run
|
95
|
+
selected.any? do |tag_set|
|
96
|
+
# Retrieve the tags for that method
|
97
|
+
method_options = self.options[method.to_sym] rescue nil
|
98
|
+
tags = method_options[:tags] rescue nil
|
99
|
+
|
100
|
+
# If the method's tags match ALL of the tags in the tag set, allow
|
101
|
+
# it to run; in the event of a problem, allow the test to run
|
102
|
+
tag_set.all? do |tag|
|
103
|
+
if tag =~ %r/^!/
|
104
|
+
!tags.include?(tag[%r/^!(.*)/,1].to_sym) || nil
|
105
|
+
else
|
106
|
+
tags.include?(tag.to_sym) || nil
|
107
|
+
end rescue true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Install a setup method that runs before every test.
|
114
|
+
#
|
115
|
+
# @return [void]
|
116
|
+
def setup(&block)
|
117
|
+
define_method(:setup) do
|
118
|
+
super()
|
119
|
+
instance_eval(&block)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Install a teardown method that runs after every test.
|
124
|
+
#
|
125
|
+
# @return [void]
|
126
|
+
def teardown(&block)
|
127
|
+
define_method(:teardown) do
|
128
|
+
super()
|
129
|
+
instance_eval(&block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Defines a test case.
|
134
|
+
#
|
135
|
+
# It can take the following options:
|
136
|
+
#
|
137
|
+
# * `tags`: An array of any number of tags associated with the test case.
|
138
|
+
# When not specified, the test will always be run even when only
|
139
|
+
# certain tags are run. When specified but an empty array, the
|
140
|
+
# test will only be run if all tags are set to run. When the array
|
141
|
+
# contains one or more tags, then the test will only be run if at
|
142
|
+
# least one tag matches.
|
143
|
+
# * `serial`: An arbitrary string that is used to refer to all a specific
|
144
|
+
# test case. For example, this can be used to store the serial
|
145
|
+
# number for the test case.
|
146
|
+
#
|
147
|
+
# @param name [String, Symbol] an arbitrary but unique name for the test,
|
148
|
+
# preferably unique across all test classes, but not required
|
149
|
+
# @param opts [Hash]
|
150
|
+
# @param block [Proc] the testing logic
|
151
|
+
# @return [void]
|
152
|
+
def test(name, **opts, &block)
|
153
|
+
# Ensure that the test isn't already defined to prevent tests from being
|
154
|
+
# swallowed silently
|
155
|
+
method_name = test_name(name)
|
156
|
+
check_not_defined!(method_name)
|
157
|
+
|
158
|
+
# Add an additional tag, which is unique for each test class, to all tests
|
159
|
+
# To allow user to run tests with option '-t class_name_of_the_test' without
|
160
|
+
# duplicate run for all tests in NameOfTheTest. The namespace of the class
|
161
|
+
# is ignored here.
|
162
|
+
opts[:tags] << ('class_'+ self.name.demodulize.underscore).to_sym
|
163
|
+
|
164
|
+
# Flunk unless a logic block was provided
|
165
|
+
if block_given?
|
166
|
+
self.options ||= {}
|
167
|
+
self.options[method_name.to_sym] = opts.deep_symbolize_keys
|
168
|
+
define_method(method_name, &block)
|
169
|
+
else
|
170
|
+
flunk "No implementation was provided for test '#{method_name}' in #{self}"
|
171
|
+
end
|
172
|
+
|
173
|
+
# add all tests to @@regression_suite
|
174
|
+
# excluding the ones with tags in tags_to_exclude defined in config
|
175
|
+
unless exclude_by_tag?('regression', opts[:tags])
|
176
|
+
@@regression_suite << method_name
|
177
|
+
@@serials << opts[:serial]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# @param suite [String] type of test suite
|
182
|
+
# @param tags [Array] an array of tags a test has
|
183
|
+
# @return [Boolean]
|
184
|
+
def exclude_by_tag?(suite, tags)
|
185
|
+
tag_to_exclude = @@test_suite_data[suite]['tag_to_exclude']
|
186
|
+
if tags.include? tag_to_exclude
|
187
|
+
true
|
188
|
+
else
|
189
|
+
false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Check that +method_name+ hasn't already been defined as an instance
|
194
|
+
# method in the current class, or in any superclasses.
|
195
|
+
#
|
196
|
+
# @param method_name [Symbol] the method name to check
|
197
|
+
# @return [void]
|
198
|
+
protected
|
199
|
+
def check_not_defined!(method_name)
|
200
|
+
already_defined = instance_method(method_name) rescue false
|
201
|
+
raise TestAlreadyDefined, "Test #{method_name} already exists in #{self}" if already_defined
|
202
|
+
end
|
203
|
+
|
204
|
+
# Transform the test +name+ into a snake-case name, prefixed with "test_".
|
205
|
+
#
|
206
|
+
# @param name [#to_s] the test name
|
207
|
+
# @return [Symbol] the transformed test name symbol
|
208
|
+
# @example
|
209
|
+
# test_name(:search_zip) # => :test_search_zip
|
210
|
+
private
|
211
|
+
def test_name(name)
|
212
|
+
undercased_name = sanitize_name(name).gsub(/\s+/, '_')
|
213
|
+
"test_#{undercased_name}".to_sym
|
214
|
+
end
|
215
|
+
|
216
|
+
# Sanitize the +name+ by removing consecutive non-word characters into a
|
217
|
+
# single whitespace.
|
218
|
+
#
|
219
|
+
# @param name [#to_s] the name to sanitize
|
220
|
+
# @return [String] the sanitized value
|
221
|
+
# @example
|
222
|
+
# sanitize_name('The Best Thing [#5]') # => 'The Best Thing 5'
|
223
|
+
# sanitize_name(:ReallySuper___awesome) # => 'ReallySuper Awesome'
|
224
|
+
private
|
225
|
+
def sanitize_name(name)
|
226
|
+
name.to_s.gsub(/\W+/, ' ').strip
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module MiniAutobot
|
2
|
+
module Utils; end
|
3
|
+
end
|
4
|
+
|
5
|
+
require_relative 'utils/assertion_helper'
|
6
|
+
require_relative 'utils/castable'
|
7
|
+
require_relative 'utils/data_generator_helper'
|
8
|
+
require_relative 'utils/loggable'
|
9
|
+
require_relative 'utils/page_object_helper'
|
10
|
+
require_relative 'utils/overlay_and_widget_helper'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'minitest/assertions'
|
2
|
+
|
3
|
+
module MiniAutobot
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
# A collection of custom, but frequently-used assertions.
|
7
|
+
module AssertionHelper
|
8
|
+
|
9
|
+
# Assert that an element, specified by `how` and `what`, are absent from
|
10
|
+
# the current page's context.
|
11
|
+
#
|
12
|
+
# @param how [:class, :class_name, :css, :id, :link_text, :link,
|
13
|
+
# :partial_link_text, :name, :tag_name, :xpath]
|
14
|
+
# @param what [String, Symbol]
|
15
|
+
def assert_element_absent(how, what)
|
16
|
+
assert_raises Selenium::WebDriver::Error::NoSuchElementError do
|
17
|
+
@driver.find_element(how, what)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Assert that an element, specified by `how` and `what`, are present from
|
22
|
+
# the current page's context.
|
23
|
+
#
|
24
|
+
# @param how [:class, :class_name, :css, :id, :link_text, :link,
|
25
|
+
# :partial_link_text, :name, :tag_name, :xpath]
|
26
|
+
# @param what [String, Symbol]
|
27
|
+
def assert_element_present(how, what)
|
28
|
+
@driver.find_element(how, what)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|