mini_autobot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+