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
data/bin/mini_autobot ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mini_autobot'
3
+
4
+ MiniAutobot::Runner.after_run { MiniAutobot::Connector.finalize!(:force) }
5
+ MiniAutobot::Runner.run!(ARGV)
@@ -0,0 +1,44 @@
1
+ require 'bundler/setup'
2
+
3
+ envs = [:default]
4
+ envs << ENV['AUTOBOT_BUNDLE'].to_sym if ENV.key?('AUTOBOT_BUNDLE')
5
+ envs << ENV['APPLICATION_ENV'].to_sym if ENV.key?('APPLICATION_ENV')
6
+
7
+ Bundler.setup(*envs)
8
+ require 'minitest'
9
+ require 'yaml'
10
+ require 'erb'
11
+ require 'faker'
12
+ require 'selenium/webdriver'
13
+ require 'rest-client'
14
+
15
+ require 'cgi'
16
+ require 'pathname'
17
+
18
+ require 'active_support/core_ext/date_time/conversions'
19
+ require 'active_support/core_ext/hash'
20
+ require 'active_support/core_ext/module/attr_internal'
21
+ require 'active_support/core_ext/module/attribute_accessors'
22
+ require 'active_support/core_ext/numeric/time'
23
+ require 'active_support/core_ext/object/blank'
24
+ require 'active_support/core_ext/object/conversions'
25
+ require 'active_support/core_ext/object/try'
26
+ require 'active_support/core_ext/object/with_options'
27
+ require 'active_support/core_ext/string/access'
28
+ require 'active_support/core_ext/string/conversions'
29
+ require 'active_support/core_ext/string/inflections'
30
+ require 'active_support/core_ext/string/starts_ends_with'
31
+ require 'active_support/core_ext/string/strip'
32
+ require 'active_support/inflector'
33
+ require 'active_support/logger'
34
+
35
+ require_relative 'minitap/minitest5_rent'
36
+
37
+ ActiveSupport::Inflector.inflections(:en) do |inflector|
38
+ inflector.acronym 'PDP'
39
+ inflector.acronym 'SRP'
40
+ end
41
+
42
+ Time::DATE_FORMATS[:month_day_year] = "%m/%d/%Y"
43
+
44
+ require_relative 'mini_autobot/init'
@@ -0,0 +1,288 @@
1
+ module MiniAutobot
2
+
3
+ # A connector provides a thin layer that combines configuration files and
4
+ # access to the WebDriver. It's a thin layer in that, other than #initialize,
5
+ # it is a drop-in replacement for WebDriver calls.
6
+ #
7
+ # For example, if you usually access a method as `@driver.find_element`, you
8
+ # can still access them as the same method under `@connector.find_element`.
9
+ class Connector
10
+
11
+ # Simple configuration container for all profiles. Struct is not used here
12
+ # because it contaminates the class with Enumerable methods, which will
13
+ # cause #method_missing in Connector to get confused.
14
+ class Config
15
+ attr_accessor :connector, :env
16
+
17
+ def ==(other)
18
+ self.class == other.class && self.connector == other.connector && self.env == other.env
19
+ end
20
+
21
+ alias_method :eql?, :==
22
+
23
+ # Hashing mechanism should only look at the connector and environment values
24
+ def hash
25
+ @connector.hash ^ @env.hash
26
+ end
27
+
28
+ # Initialize a new configuration object. This object should never be
29
+ # instantiated directly.
30
+ #
31
+ # @api private
32
+ def initialize(connector, env)
33
+ @connector = connector
34
+ @env = env
35
+ end
36
+
37
+ end
38
+
39
+ class <<self # :nodoc:
40
+ protected
41
+ attr_accessor :finalization_queue
42
+ end
43
+
44
+ self.finalization_queue = Queue.new
45
+
46
+ # Finalize connectors in the pool that are no longer used, and then clear
47
+ # the pool if it should be empty.
48
+ def self.finalize!(force = false)
49
+ return if MiniAutobot.settings.reuse_driver? && !force
50
+
51
+ if Thread.current[:active_connector]
52
+ self.finalization_queue << Thread.current[:active_connector]
53
+ Thread.current[:active_connector] = nil
54
+ end
55
+
56
+ return unless MiniAutobot.settings.auto_finalize?
57
+
58
+ Thread.new do
59
+ while connector = self.finalization_queue.pop
60
+ begin
61
+ connector.finalize!
62
+ rescue => e
63
+ MiniAutobot.logger.error("Could not finalize Connector(##{connector.object_id}): #{e.message}")
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # Given a connector profile and an environment profile, this method will
70
+ # instantiate a connector object with the correct WebDriver instance and
71
+ # settings.
72
+ #
73
+ # @raise ArgumentError
74
+ # @param connector [#to_s] the name of the connector profile to use.
75
+ # @param env [#to_s] the name of the environment profile to use.
76
+ # @return [Connector] an initialized connector object
77
+ def self.get(connector_id, env_id)
78
+ # Ensure arguments are at least provided
79
+ raise ArgumentError, "A connector must be provided" if connector_id.blank?
80
+ raise ArgumentError, "An environment must be provided" if env_id.blank?
81
+
82
+ # Find the connector and environment profiles
83
+ connector_cfg = self.load(MiniAutobot.root.join('config/mini_autobot', 'connectors'), connector_id)
84
+ env_cfg = self.load(MiniAutobot.root.join('config/mini_autobot', 'environments'), env_id)
85
+ cfg = Config.new(connector_cfg, env_cfg)
86
+
87
+ if Thread.current[:active_connector] && !MiniAutobot.settings.reuse_driver?
88
+ self.finalization_queue << active_connector
89
+ Thread.current[:active_connector] = nil
90
+ end
91
+
92
+ # If the current thread already has an active connector, and the connector
93
+ # is of the same type requested, reuse it after calling `reset!`
94
+ active_connector = Thread.current[:active_connector]
95
+ if active_connector.present?
96
+ if active_connector.config == cfg
97
+ active_connector.reset!
98
+ else
99
+ self.finalization_queue << active_connector
100
+ active_connector = nil
101
+ end
102
+ end
103
+
104
+ # Reuse or instantiate
105
+ Thread.current[:active_connector] = active_connector || Connector.new(cfg)
106
+ end
107
+
108
+ # Retrieve the default connector for the current environment.
109
+ #
110
+ # @raise ArgumentError
111
+ # @return [Connector] an initialized connector object
112
+ def self.get_default
113
+ connector = MiniAutobot.settings.connector
114
+ env = MiniAutobot.settings.env
115
+ MiniAutobot.logger.debug("Retrieving connector with settings (#{connector}, #{env})")
116
+
117
+ # Get a connector instance and use it in the new page object
118
+ self.get(connector, env)
119
+ end
120
+
121
+ # Equivalent to @driver.browser
122
+ def self.browser_name
123
+ Thread.current[:active_connector].browser
124
+ end
125
+
126
+ # Load profile from a specific path using the selector(s) specified.
127
+ #
128
+ # @raise ArgumentError
129
+ # @param path [#to_path, #to_s] the path in which to find the profile
130
+ # @param selector [String] semicolon-delimited selector set
131
+ # @return [Hash] immutable configuration values
132
+ def self.load(path, selector)
133
+ overrides = selector.to_s.split(/:/)
134
+ name = overrides.shift
135
+ filepath = path.join("#{name}.yml")
136
+ raise ArgumentError, "Cannot load profile #{name.inspect} because #{filepath.inspect} does not exist" unless filepath.exist?
137
+
138
+ cfg = YAML.load(ERB.new(File.read(filepath)).result)
139
+ cfg = self.resolve(cfg, overrides)
140
+ cfg.freeze
141
+ end
142
+
143
+ # Resolve a set of profile overrides.
144
+ #
145
+ # @param cfg [Hash] the configuration structure optionally containing a
146
+ # key of `:overrides`
147
+ # @param overrides [Enumerable<String>]
148
+ # @return [Hash] the resolved configuration
149
+ def self.resolve(cfg, overrides)
150
+ cfg = cfg.dup.with_indifferent_access
151
+
152
+ if options = cfg.delete(:overrides)
153
+ # Evaluate each override in turn, allowing each override to--well,
154
+ # override--anything coming before it
155
+ overrides.each do |override|
156
+ if tree = options[override]
157
+ cfg.deep_merge!(tree)
158
+ end
159
+ end
160
+ end
161
+
162
+ cfg
163
+ end
164
+
165
+ attr_reader :config
166
+
167
+ # Perform cleanup on the connector and driver.
168
+ def finalize!
169
+ @driver.quit
170
+ true
171
+ end
172
+
173
+ # Initialize a new connector with a set of configuration files.
174
+ #
175
+ # @see Connector.get
176
+ # @api private
177
+ def initialize(config)
178
+ @config = config
179
+
180
+ # Load and configure the WebDriver, if necessary
181
+ if concon = config.connector
182
+ driver_config = { }
183
+ driver = concon[:driver]
184
+ raise ArgumentError, "Connector driver must not be empty" if driver.nil?
185
+
186
+ # Handle hub-related options, like hub URLs (for remote execution)
187
+ if hub = concon[:hub]
188
+ builder = URI.parse(hub[:url])
189
+ builder.user = hub[:user] if hub.has_key?(:user)
190
+ builder.password = hub[:pass] if hub.has_key?(:pass)
191
+
192
+ MiniAutobot.logger.debug("Connector(##{self.object_id}): targeting remote #{builder.to_s}")
193
+ driver_config[:url] = builder.to_s
194
+ end
195
+
196
+ # Handle driver-related timeouts
197
+ if timeouts = concon[:timeouts]
198
+ client = Selenium::WebDriver::Remote::Http::Default.new
199
+ client.timeout = timeouts[:driver]
200
+ driver_config[:http_client] = client
201
+ end
202
+
203
+ # Handle archetypal capability lists
204
+ if archetype = concon[:archetype]
205
+ MiniAutobot.logger.debug("Connector(##{self.object_id}): using #{archetype.inspect} as capabilities archetype")
206
+ caps = Selenium::WebDriver::Remote::Capabilities.send(archetype)
207
+ if caps_set = concon[:capabilities]
208
+ caps.merge!(caps_set)
209
+ end
210
+ driver_config[:desired_capabilities] = caps
211
+ end
212
+
213
+ # Load Firefox profile if specified - applicable only when using the firefoxdriver
214
+ if profile = concon[:profile]
215
+ driver_config[:profile] = profile
216
+ end
217
+
218
+ # Initialize the driver and declare explicit browser timeouts
219
+ MiniAutobot.logger.debug("Connector(##{self.object_id}): using WebDriver(#{driver.inspect}, #{driver_config.inspect})")
220
+ @driver = Selenium::WebDriver.for(driver.to_sym, driver_config)
221
+
222
+ # Resize browser window for local browser with 'resolution'
223
+ if concon[:resolution]
224
+ width = concon[:resolution].split(/x/)[0].to_i
225
+ height = concon[:resolution].split(/x/)[1].to_i
226
+ @driver.manage.window.resize_to(width, height)
227
+ end
228
+
229
+ # setTimeout is undefined for safari driver so skip these steps for it
230
+ unless @driver.browser == :safari
231
+ if timeouts = concon[:timeouts]
232
+ @driver.manage.timeouts.implicit_wait = timeouts[:implicit_wait] if timeouts[:implicit_wait]
233
+ @driver.manage.timeouts.page_load = timeouts[:page_load] if timeouts[:page_load]
234
+ @driver.manage.timeouts.script_timeout = timeouts[:script_timeout] if timeouts[:script_timeout]
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ # Forward any other method call to the configuration container; if that
241
+ # fails, forward it to the WebDriver. The WebDriver will take care of any
242
+ # method resolution errors.
243
+ #
244
+ # @param name [#to_sym] symbol representing the method call
245
+ # @param args [*Object] arguments to be passed along
246
+ def method_missing(name, *args, &block)
247
+ if @config.respond_to?(name)
248
+ @config.send(name, *args, *block)
249
+ else
250
+ MiniAutobot.logger.debug("Connector(##{self.object_id})->#{name}(#{args.map { |a| a.inspect }.join(', ')})")
251
+ @driver.send(name, *args, &block)
252
+ end
253
+ end
254
+
255
+ # Resets the current session by deleting all cookies and clearing all local
256
+ # and session storage. Local and session storage are only cleared if the
257
+ # underlying driver supports it, and even then, only if the storage
258
+ # supports atomic clearing.
259
+ #
260
+ # @return [Boolean]
261
+ def reset!
262
+ @driver.manage.delete_all_cookies
263
+ @driver.try(:local_storage).try(:clear)
264
+ @driver.try(:session_storage).try(:clear)
265
+ true
266
+ end
267
+
268
+ # Forward unhandled message checks to the configuration and driver.
269
+ #
270
+ # @param name [#to_sym]
271
+ # @return [Boolean]
272
+ def respond_to?(name)
273
+ super || @config.respond_to?(name) || @driver.respond_to?(name)
274
+ end
275
+
276
+ # Compose a URL from the provided +path+ and the environment profile. The
277
+ # latter contains things like the hostname, port, SSL settings.
278
+ #
279
+ # @param path [#to_s] the path to append after the root URL.
280
+ # @return [URI] the composed URL.
281
+ def url_for(path)
282
+ root = @config.env[:root]
283
+ raise ArgumentError, "The 'root' attribute is missing from the environment profile" unless root
284
+ URI.join(root, path)
285
+ end
286
+
287
+ end
288
+ end
@@ -0,0 +1,15 @@
1
+ module MiniAutobot
2
+ class Console < TestCase
3
+
4
+ def self.bootstrap!
5
+ MiniAutobot.settings.tags << [:__dummy__]
6
+ end
7
+
8
+ test :dummy, tags: [:__dummy__, :non_regression], serial: 'DUMMY' do
9
+ require 'pry'
10
+ assert_respond_to binding, :pry
11
+ binding.pry
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module MiniAutobot
2
+ module Emails; end
3
+ end
4
+
5
+ require_relative 'emails/mailbox'
@@ -0,0 +1,15 @@
1
+ require 'net/imap'
2
+
3
+ module MiniAutobot
4
+ module Emails
5
+
6
+ class Mailbox
7
+
8
+ def initialize(**opts)
9
+ end
10
+
11
+ end
12
+
13
+ end
14
+ end
15
+
@@ -0,0 +1,6 @@
1
+ module MiniAutobot
2
+ module Endeca
3
+ class Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,63 @@
1
+ # The base module for everything MiniAutobot and is the container for other
2
+ # modules and classes in the hierarchy:
3
+ #
4
+ # * `Connector` provides support for drivers and connector profiles;
5
+ # * `Emails` introduces email-specific drivers for MiniAutobot;
6
+ # * `PageObjects` provides a hierarchy of page objects, page modules, widgets,
7
+ # and overlays;
8
+ # * `Settings` provides support for internal MiniAutobot settings; and
9
+ # * `Utils` provides an overarching module for miscellaneous helper modules.
10
+ module MiniAutobot
11
+
12
+ def self.logger
13
+ @@logger ||= MiniAutobot::Logger.new($stdout)
14
+ end
15
+
16
+ def self.logger=(value)
17
+ @@logger = value
18
+ end
19
+
20
+ def self.settings
21
+ @@settings ||= Settings.new
22
+ end
23
+
24
+ def self.settings=(options)
25
+ self.settings.merge!(options)
26
+ end
27
+
28
+ # Root directory of the automation repository.
29
+ # Automation repo can use it to refer to files within itself,
30
+ # and this gem also uses it to refer to config files of automation,
31
+ # for example:
32
+ #
33
+ # File.read(MiniAutobot.root.join('config/mini_autobot', 'data.yml'))
34
+ #
35
+ # will return the contents of `automation_root/config/mini_autobot/data.yml`.
36
+ #
37
+ # @return [Pathname] A reference to the root directory, ready to be used
38
+ # in directory and file path calculations.
39
+ def self.root
40
+ @@__root__ ||= Pathname.new(File.expand_path('.'))
41
+ end
42
+
43
+ # Absolute path of root directory of this gem
44
+ # can be used both within this gem and in automation repo
45
+ def self.gem_root
46
+ @@__gem_root__ ||= Pathname.new(File.realpath(File.join(File.dirname(__FILE__), '..', '..')))
47
+ end
48
+
49
+ end
50
+
51
+ require_relative 'runner'
52
+ require_relative 'logger'
53
+ require_relative 'utils'
54
+
55
+ require_relative 'connector'
56
+ require_relative 'page_objects'
57
+ require_relative 'parallel'
58
+ require_relative 'settings'
59
+
60
+ require_relative 'emails'
61
+
62
+ require_relative 'test_case'
63
+ require_relative 'console'