mini_autobot 0.0.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 +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
|
+
|