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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +191 -0
  7. data/LICENSE +22 -0
  8. data/README.md +632 -0
  9. data/bin/mini_autobot +5 -0
  10. data/lib/mini_autobot.rb +44 -0
  11. data/lib/mini_autobot/connector.rb +288 -0
  12. data/lib/mini_autobot/console.rb +15 -0
  13. data/lib/mini_autobot/emails.rb +5 -0
  14. data/lib/mini_autobot/emails/mailbox.rb +15 -0
  15. data/lib/mini_autobot/endeca/base.rb +6 -0
  16. data/lib/mini_autobot/init.rb +63 -0
  17. data/lib/mini_autobot/logger.rb +12 -0
  18. data/lib/mini_autobot/page_objects.rb +22 -0
  19. data/lib/mini_autobot/page_objects/base.rb +264 -0
  20. data/lib/mini_autobot/page_objects/overlay/base.rb +76 -0
  21. data/lib/mini_autobot/page_objects/widgets/base.rb +47 -0
  22. data/lib/mini_autobot/parallel.rb +197 -0
  23. data/lib/mini_autobot/runner.rb +91 -0
  24. data/lib/mini_autobot/settings.rb +78 -0
  25. data/lib/mini_autobot/test_case.rb +233 -0
  26. data/lib/mini_autobot/test_cases.rb +7 -0
  27. data/lib/mini_autobot/utils.rb +10 -0
  28. data/lib/mini_autobot/utils/assertion_helper.rb +35 -0
  29. data/lib/mini_autobot/utils/castable.rb +103 -0
  30. data/lib/mini_autobot/utils/data_generator_helper.rb +145 -0
  31. data/lib/mini_autobot/utils/endeca_helper.rb +46 -0
  32. data/lib/mini_autobot/utils/loggable.rb +16 -0
  33. data/lib/mini_autobot/utils/overlay_and_widget_helper.rb +78 -0
  34. data/lib/mini_autobot/utils/page_object_helper.rb +209 -0
  35. data/lib/mini_autobot/version.rb +3 -0
  36. data/lib/minitap/minitest5_rent.rb +22 -0
  37. data/lib/minitest/autobot_settings_plugin.rb +77 -0
  38. data/lib/tapout/custom_reporters/fancy_tap_reporter.rb +94 -0
  39. data/lib/yard/tagged_test_case_handler.rb +61 -0
  40. data/mini_autobot.gemspec +38 -0
  41. 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,7 @@
1
+ module MiniAutobot
2
+
3
+ # An empty container for actual test cases and test classes.
4
+ module TestCases
5
+ end
6
+
7
+ 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
+