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
data/bin/mini_autobot
ADDED
data/lib/mini_autobot.rb
ADDED
@@ -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,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'
|