browsery 0.1.0 → 0.2.0
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 +4 -4
- data/.ruby-version +1 -0
- data/bin/browsery +5 -0
- data/browsery.gemspec +8 -0
- data/lib/browsery.rb +22 -0
- data/lib/browsery/connector.rb +287 -0
- data/lib/browsery/console.rb +15 -0
- data/lib/browsery/init.rb +60 -0
- data/lib/browsery/logger.rb +12 -0
- data/lib/browsery/page_objects.rb +23 -0
- data/lib/browsery/page_objects/base.rb +266 -0
- data/lib/browsery/page_objects/element_container.rb +50 -0
- data/lib/browsery/page_objects/overlay/base.rb +85 -0
- data/lib/browsery/page_objects/widgets/base.rb +52 -0
- data/lib/browsery/parallel.rb +265 -0
- data/lib/browsery/runner.rb +111 -0
- data/lib/browsery/settings.rb +114 -0
- data/lib/browsery/test_case.rb +266 -0
- data/lib/browsery/test_cases.rb +7 -0
- data/lib/browsery/utils.rb +10 -0
- data/lib/browsery/utils/assertion_helper.rb +35 -0
- data/lib/browsery/utils/castable.rb +103 -0
- data/lib/browsery/utils/data_generator_helper.rb +145 -0
- data/lib/browsery/utils/loggable.rb +16 -0
- data/lib/browsery/utils/overlay_and_widget_helper.rb +78 -0
- data/lib/browsery/utils/page_object_helper.rb +263 -0
- data/lib/browsery/version.rb +1 -1
- data/lib/minitap/minitest5_browsery.rb +22 -0
- data/lib/minitest/autobot_settings_plugin.rb +83 -0
- data/lib/selenium/webdriver/common/element_browsery.rb +21 -0
- data/lib/tapout/custom_reporters/fancy_tap_reporter.rb +94 -0
- data/lib/yard/tagged_test_case_handler.rb +61 -0
- metadata +131 -5
@@ -0,0 +1,266 @@
|
|
1
|
+
module Browsery
|
2
|
+
|
3
|
+
# An Browsery-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
|
+
@@selected_methods = []
|
7
|
+
@@runnables_count = 0
|
8
|
+
@@regression_suite = Array.new
|
9
|
+
@@serials = Array.new
|
10
|
+
@@test_suite_data = if File.exist?(Browsery.root.join("config/browsery/test_suite.yml"))
|
11
|
+
YAML.load_file(Browsery.root.join("config/browsery/test_suite.yml"))
|
12
|
+
else
|
13
|
+
default = {"regression"=>{"tag_to_exclude"=>:non_regression}}
|
14
|
+
if Browsery.root != Browsery.gem_root
|
15
|
+
# Only necessary to notify gem user, not gem developer
|
16
|
+
puts "config/browsery/test_suite.yml doesn't exist, using default:\n#{default}"
|
17
|
+
puts "It's recommended to have this config file as it'll avoid problem when using tapout"
|
18
|
+
end
|
19
|
+
default
|
20
|
+
end
|
21
|
+
|
22
|
+
# Standard exception class that signals that the test with that name has
|
23
|
+
# already been defined.
|
24
|
+
class TestAlreadyDefined < ::StandardError; end
|
25
|
+
|
26
|
+
# Include helper modules
|
27
|
+
include Browsery::Utils::AssertionHelper
|
28
|
+
include Browsery::Utils::DataGeneratorHelper
|
29
|
+
include Browsery::Utils::Loggable
|
30
|
+
include Browsery::Utils::PageObjectHelper
|
31
|
+
|
32
|
+
class <<self
|
33
|
+
|
34
|
+
# @!attribute [rw] options
|
35
|
+
# @return [Hash] test case options
|
36
|
+
attr_accessor :options
|
37
|
+
|
38
|
+
# Explicitly remove _all_ tests from the current class. This will also
|
39
|
+
# remove inherited test cases.
|
40
|
+
#
|
41
|
+
# @return [TestCase] self
|
42
|
+
def remove_tests
|
43
|
+
klass = class <<self; self; end
|
44
|
+
public_instance_methods.grep(/^test_/).each do |method|
|
45
|
+
klass.send(:undef_method, method.to_sym)
|
46
|
+
end
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Call this at the top of your test case class in order to run all tests
|
51
|
+
# in alphabetical order
|
52
|
+
#
|
53
|
+
# @return [TestCase] self
|
54
|
+
# @example
|
55
|
+
# class SomeName < TestCase
|
56
|
+
# run_in_order!
|
57
|
+
#
|
58
|
+
# test :feature_search_01 { ... }
|
59
|
+
# test :feature_search_02 { ... }
|
60
|
+
# end
|
61
|
+
def run_in_order!
|
62
|
+
# `self` is the class, so we want to reopen the metaclass instead, and
|
63
|
+
# redefine the methods there
|
64
|
+
class <<self
|
65
|
+
undef_method :test_order if method_defined? :test_order
|
66
|
+
define_method :test_order do
|
67
|
+
:alpha
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Be nice and return the class back
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Filter out anything not matching our tag selection, if any.
|
76
|
+
#
|
77
|
+
# If it's parallel run,
|
78
|
+
# only add filtered methods from each runnable to a list of to run methods,
|
79
|
+
# instead of running them one by one right away,
|
80
|
+
# and finally when all runnable methods are traversed, call parallel to run that list of methods.
|
81
|
+
#
|
82
|
+
# @return [Enumerable<Symbol>] the methods marked runnable
|
83
|
+
def runnable_methods
|
84
|
+
methods = super
|
85
|
+
selected = Browsery.settings.tags
|
86
|
+
|
87
|
+
filtered_methods = filter_methods(methods, selected)
|
88
|
+
|
89
|
+
if Browsery.settings.parallel
|
90
|
+
unless filtered_methods.empty?
|
91
|
+
if selected.nil? || selected.empty?
|
92
|
+
@@selected_methods = @@regression_suite
|
93
|
+
else
|
94
|
+
methods_to_add = filtered_methods.map { |method| method.to_sym if @@regression_suite.include?(method.to_sym) }
|
95
|
+
@@selected_methods += methods_to_add
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@@runnables_count += 1
|
100
|
+
browsery_runnables = Minitest::Runnable.runnables - [Minitest::Test, Minitest::Unit::TestCase]
|
101
|
+
|
102
|
+
if @@runnables_count == browsery_runnables.size
|
103
|
+
parallel = Parallel.new(Browsery.settings.parallel, @@selected_methods)
|
104
|
+
parallel.clean_result!
|
105
|
+
parallel.run_in_parallel!
|
106
|
+
parallel.remove_redundant_tap if Browsery.settings.rerun_failure
|
107
|
+
parallel.aggregate_tap_results
|
108
|
+
exit
|
109
|
+
end
|
110
|
+
|
111
|
+
return [] # no test will run
|
112
|
+
else
|
113
|
+
filtered_methods
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Filter methods in a runnable based on our tag selection
|
118
|
+
def filter_methods(methods, selected)
|
119
|
+
# If no tags are selected, run all tests
|
120
|
+
if selected.nil? || selected.empty?
|
121
|
+
return methods
|
122
|
+
end
|
123
|
+
|
124
|
+
selected_methods = methods.select do |method|
|
125
|
+
# If the method's tags match any of the tag sets, allow it to run
|
126
|
+
selected.any? do |tag_set|
|
127
|
+
# Retrieve the tags for that method
|
128
|
+
method_options = self.options[method.to_sym] rescue nil
|
129
|
+
tags = method_options[:tags] rescue nil
|
130
|
+
|
131
|
+
# If the method's tags match ALL of the tags in the tag set, allow
|
132
|
+
# it to run; in the event of a problem, allow the test to run
|
133
|
+
tag_set.all? do |tag|
|
134
|
+
if tag =~ %r/^!/
|
135
|
+
!tags.include?(tag[%r/^!(.*)/,1].to_sym) || nil
|
136
|
+
else
|
137
|
+
tags.include?(tag.to_sym) || nil
|
138
|
+
end rescue true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
selected_methods
|
144
|
+
end
|
145
|
+
|
146
|
+
# Install a setup method that runs before every test.
|
147
|
+
#
|
148
|
+
# @return [void]
|
149
|
+
def setup(&block)
|
150
|
+
define_method(:setup) do
|
151
|
+
super()
|
152
|
+
instance_eval(&block)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Install a teardown method that runs after every test.
|
157
|
+
#
|
158
|
+
# @return [void]
|
159
|
+
def teardown(&block)
|
160
|
+
define_method(:teardown) do
|
161
|
+
super()
|
162
|
+
instance_eval(&block)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Defines a test case.
|
167
|
+
#
|
168
|
+
# It can take the following options:
|
169
|
+
#
|
170
|
+
# * `tags`: An array of any number of tags associated with the test case.
|
171
|
+
# When not specified, the test will always be run even when only
|
172
|
+
# certain tags are run. When specified but an empty array, the
|
173
|
+
# test will only be run if all tags are set to run. When the array
|
174
|
+
# contains one or more tags, then the test will only be run if at
|
175
|
+
# least one tag matches.
|
176
|
+
# * `serial`: An arbitrary string that is used to refer to all a specific
|
177
|
+
# test case. For example, this can be used to store the serial
|
178
|
+
# number for the test case.
|
179
|
+
#
|
180
|
+
# @param name [String, Symbol] an arbitrary but unique name for the test,
|
181
|
+
# preferably unique across all test classes, but not required
|
182
|
+
# @param opts [Hash]
|
183
|
+
# @param block [Proc] the testing logic
|
184
|
+
# @return [void]
|
185
|
+
def test(name, **opts, &block)
|
186
|
+
# Ensure that the test isn't already defined to prevent tests from being
|
187
|
+
# swallowed silently
|
188
|
+
method_name = test_name(name)
|
189
|
+
check_not_defined!(method_name)
|
190
|
+
|
191
|
+
# Add an additional tag, which is unique for each test class, to all tests
|
192
|
+
# To allow user to run tests with option '-t class_name_of_the_test' without
|
193
|
+
# duplicate run for all tests in NameOfTheTest. The namespace of the class
|
194
|
+
# is ignored here.
|
195
|
+
opts[:tags] << ('class_'+ self.name.demodulize.underscore).to_sym
|
196
|
+
|
197
|
+
# Flunk unless a logic block was provided
|
198
|
+
if block_given?
|
199
|
+
self.options ||= {}
|
200
|
+
self.options[method_name.to_sym] = opts.deep_symbolize_keys
|
201
|
+
define_method(method_name, &block)
|
202
|
+
else
|
203
|
+
flunk "No implementation was provided for test '#{method_name}' in #{self}"
|
204
|
+
end
|
205
|
+
|
206
|
+
# add all tests to @@regression_suite
|
207
|
+
# excluding the ones with tags in tags_to_exclude defined in config
|
208
|
+
unless exclude_by_tag?('regression', opts[:tags])
|
209
|
+
@@regression_suite << method_name
|
210
|
+
@@serials << opts[:serial]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# @param suite [String] type of test suite
|
215
|
+
# @param tags [Array] an array of tags a test has
|
216
|
+
# @return [Boolean]
|
217
|
+
def exclude_by_tag?(suite, tags)
|
218
|
+
tag_to_exclude = @@test_suite_data[suite]['tag_to_exclude']
|
219
|
+
if tags.include? tag_to_exclude
|
220
|
+
true
|
221
|
+
else
|
222
|
+
false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Check that +method_name+ hasn't already been defined as an instance
|
227
|
+
# method in the current class, or in any superclasses.
|
228
|
+
#
|
229
|
+
# @param method_name [Symbol] the method name to check
|
230
|
+
# @return [void]
|
231
|
+
protected
|
232
|
+
def check_not_defined!(method_name)
|
233
|
+
already_defined = instance_method(method_name) rescue false
|
234
|
+
raise TestAlreadyDefined, "Test #{method_name} already exists in #{self}" if already_defined
|
235
|
+
end
|
236
|
+
|
237
|
+
# Transform the test +name+ into a snake-case name, prefixed with "test_".
|
238
|
+
#
|
239
|
+
# @param name [#to_s] the test name
|
240
|
+
# @return [Symbol] the transformed test name symbol
|
241
|
+
# @example
|
242
|
+
# test_name(:search_zip) # => :test_search_zip
|
243
|
+
private
|
244
|
+
def test_name(name)
|
245
|
+
undercased_name = sanitize_name(name).gsub(/\s+/, '_')
|
246
|
+
"test_#{undercased_name}".to_sym
|
247
|
+
end
|
248
|
+
|
249
|
+
# Sanitize the +name+ by removing consecutive non-word characters into a
|
250
|
+
# single whitespace.
|
251
|
+
#
|
252
|
+
# @param name [#to_s] the name to sanitize
|
253
|
+
# @return [String] the sanitized value
|
254
|
+
# @example
|
255
|
+
# sanitize_name('The Best Thing [#5]') # => 'The Best Thing 5'
|
256
|
+
# sanitize_name(:ReallySuper___awesome) # => 'ReallySuper Awesome'
|
257
|
+
private
|
258
|
+
def sanitize_name(name)
|
259
|
+
name.to_s.gsub(/\W+/, ' ').strip
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Browsery
|
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 Browsery
|
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
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
module Browsery
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
module Castable
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Attempts to create a new page object from a driver state. Use the
|
10
|
+
# instance method for convenience. Raises `NameError` if the page could
|
11
|
+
# not be found.
|
12
|
+
#
|
13
|
+
# @param driver [Selenium::WebDriver] The instance of the current
|
14
|
+
# WebDriver.
|
15
|
+
# @param name [#to_s] The name of the page object to instantiate.
|
16
|
+
# @return [Base] A subclass of `Base` representing the page object.
|
17
|
+
# @raise InvalidPageState if the page cannot be casted to
|
18
|
+
# @raise NameError if the page object doesn't exist
|
19
|
+
def cast(driver, name)
|
20
|
+
# Transform the name string into a file path and then into a module name
|
21
|
+
klass_name = "browsery/page_objects/#{name}".camelize
|
22
|
+
|
23
|
+
# Attempt to load the class
|
24
|
+
klass = begin
|
25
|
+
klass_name.constantize
|
26
|
+
rescue => exc
|
27
|
+
msg = ""
|
28
|
+
msg << "Cannot find page object '#{name}', "
|
29
|
+
msg << "because could not load class '#{klass_name}' "
|
30
|
+
msg << "with underlying error:\n #{exc.class}: #{exc.message}\n"
|
31
|
+
msg << exc.backtrace.map { |str| " #{str}" }.join("\n")
|
32
|
+
raise NameError, msg
|
33
|
+
end
|
34
|
+
|
35
|
+
# Instantiate the class, passing the driver automatically, and
|
36
|
+
# validates to ensure the driver is in the correct state
|
37
|
+
instance = klass.new(driver)
|
38
|
+
begin
|
39
|
+
instance.validate!
|
40
|
+
rescue Minitest::Assertion => exc
|
41
|
+
raise Browsery::PageObjects::InvalidePageState, "#{klass}: #{exc.message}"
|
42
|
+
end
|
43
|
+
instance
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# Extend the base class in which this module is included in order to
|
49
|
+
# inject class methods.
|
50
|
+
#
|
51
|
+
# @param base [Class]
|
52
|
+
# @return [void]
|
53
|
+
def self.included(base)
|
54
|
+
base.extend(ClassMethods)
|
55
|
+
end
|
56
|
+
|
57
|
+
# The preferred way to create a new page object from the current page's
|
58
|
+
# driver state. Raises a NameError if the page could not be found. If
|
59
|
+
# casting causes a StaleElementReferenceError, the method will retry up
|
60
|
+
# to 2 more times.
|
61
|
+
#
|
62
|
+
# @param name [String] see {Base.cast}
|
63
|
+
# @return [Base] The casted page object.
|
64
|
+
# @raise InvalidPageState if the page cannot be casted to
|
65
|
+
# @raise NameError if the page object doesn't exist
|
66
|
+
def cast(name)
|
67
|
+
tries ||= 3
|
68
|
+
self.class.cast(@driver, name).tap do |new_page|
|
69
|
+
self.freeze
|
70
|
+
Browsery.logger.debug("Casting #{self.class}(##{self.object_id}) into #{new_page.class}(##{new_page.object_id})")
|
71
|
+
end
|
72
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError => sere
|
73
|
+
Browsery.logger.debug("#{self.class}(##{@driver.object_id})->cast(#{name}) raised a potentially-swallowed StaleElementReferenceError")
|
74
|
+
sleep 1
|
75
|
+
retry unless (tries -= 1).zero?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Cast the page to any of the listed `names`, in order of specification.
|
79
|
+
# Returns the first page that accepts the casting, or returns nil, rather
|
80
|
+
# than raising InvalidPageState.
|
81
|
+
#
|
82
|
+
# @param names [Enumerable<String>] see {Base.cast}
|
83
|
+
# @return [Base, nil] the casted page object, if successful; nil otherwise.
|
84
|
+
# @raise NameError if the page object doesn't exist
|
85
|
+
def cast_any(*names)
|
86
|
+
# Try one at a time, swallowing InvalidPageState exceptions
|
87
|
+
names.each do |name|
|
88
|
+
begin
|
89
|
+
return self.cast(name)
|
90
|
+
rescue InvalidPageState
|
91
|
+
# noop
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Return nil otherwise
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,145 @@
|
|
1
|
+
|
2
|
+
module Browsery
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
# Useful helpers to generate fake data.
|
6
|
+
module DataGeneratorHelper
|
7
|
+
|
8
|
+
# All valid area codes in the US
|
9
|
+
NPA = ["201", "202", "203", "205", "206", "207", "208", "209", "210", "212", "213", "214", "215", "216", "217", "218", "219", "224", "225", "227", "228", "229", "231", "234", "239", "240", "248", "251", "252", "253", "254", "256", "260", "262", "267", "269", "270", "276", "281", "283", "301", "302", "303", "304", "305", "307", "308", "309", "310", "312", "313", "314", "315", "316", "317", "318", "319", "320", "321", "323", "330", "331", "334", "336", "337", "339", "347", "351", "352", "360", "361", "386", "401", "402", "404", "405", "406", "407", "408", "409", "410", "412", "413", "414", "415", "417", "419", "423", "424", "425", "434", "435", "440", "443", "445", "464", "469", "470", "475", "478", "479", "480", "484", "501", "502", "503", "504", "505", "507", "508", "509", "510", "512", "513", "515", "516", "517", "518", "520", "530", "540", "541", "551", "557", "559", "561", "562", "563", "564", "567", "570", "571", "573", "574", "580", "585", "586", "601", "602", "603", "605", "606", "607", "608", "609", "610", "612", "614", "615", "616", "617", "618", "619", "620", "623", "626", "630", "631", "636", "641", "646", "650", "651", "660", "661", "662", "667", "678", "682", "701", "702", "703", "704", "706", "707", "708", "712", "713", "714", "715", "716", "717", "718", "719", "720", "724", "727", "731", "732", "734", "737", "740", "754", "757", "760", "763", "765", "770", "772", "773", "774", "775", "781", "785", "786", "801", "802", "803", "804", "805", "806", "808", "810", "812", "813", "814", "815", "816", "817", "818", "828", "830", "831", "832", "835", "843", "845", "847", "848", "850", "856", "857", "858", "859", "860", "862", "863", "864", "865", "870", "872", "878", "901", "903", "904", "906", "907", "908", "909", "910", "912", "913", "914", "915", "916", "917", "918", "919", "920", "925", "928", "931", "936", "937", "940", "941", "947", "949", "952", "954", "956", "959", "970", "971", "972", "973", "975", "978", "979", "980", "984", "985", "989"]
|
10
|
+
|
11
|
+
# Easier to assume for now a list of valid exchanges
|
12
|
+
NXX = NPA
|
13
|
+
|
14
|
+
# Generate a string of random digits.
|
15
|
+
#
|
16
|
+
# @param digits [Fixnum] the number of digits in the string
|
17
|
+
# @return [String] the string of digits
|
18
|
+
def generate_digits(digits = 1)
|
19
|
+
Faker::Number.number(digits)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generate a random email address.
|
23
|
+
#
|
24
|
+
# The specifier portion may be:
|
25
|
+
#
|
26
|
+
# * `nil`, in which case nothing special happens;
|
27
|
+
# * a `String`, in which case the words in the string is shuffled, and
|
28
|
+
# random separators (`.` or `_`) are inserted between them;
|
29
|
+
# * an `Integer`, in which case a random alpha-string will be created
|
30
|
+
# with length of at least that many characters;
|
31
|
+
# * a `Range`, in which case a random alpha-string of length within the
|
32
|
+
# range will be produced.
|
33
|
+
#
|
34
|
+
# @param specifier [nil, String, Integer, Range] a specifier to help
|
35
|
+
# generate the username part of the email address
|
36
|
+
# @return [String]
|
37
|
+
def generate_email(specifier = nil)
|
38
|
+
Faker::Internet.email(name)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Generate a handsome first name.
|
42
|
+
#
|
43
|
+
# @param length [#to_i, nil]
|
44
|
+
# @return [String]
|
45
|
+
def generate_first_name(length = nil)
|
46
|
+
first_name = ''
|
47
|
+
if length.nil?
|
48
|
+
first_name = Faker::Name.first_name
|
49
|
+
else
|
50
|
+
# Ensure a name with requested length is generated
|
51
|
+
name_length = Faker::Name.first_name.length
|
52
|
+
if length > name_length
|
53
|
+
first_name = Faker::Lorem.characters(length)
|
54
|
+
else
|
55
|
+
first_name = Faker::Name.first_name[0..length.to_i]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
# remove all special characters since name fields on our site have this requirement
|
59
|
+
first_name.gsub!(/[^0-9A-Za-z]/, '')
|
60
|
+
first_name
|
61
|
+
end
|
62
|
+
|
63
|
+
# Generate a gosh-darn awesome last name.
|
64
|
+
#
|
65
|
+
# @param length [#to_i, nil]
|
66
|
+
# @return [String]
|
67
|
+
def generate_last_name(length = nil)
|
68
|
+
last_name = ''
|
69
|
+
if length.nil?
|
70
|
+
last_name = Faker::Name.last_name
|
71
|
+
else
|
72
|
+
# Ensure a name with requested length is generated
|
73
|
+
name_length = Faker::Name.last_name.length
|
74
|
+
if length > name_length
|
75
|
+
last_name = Faker::Lorem.characters(length)
|
76
|
+
else
|
77
|
+
last_name = Faker::Name.last_name[0..length.to_i]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# remove all special characters since name fields on our site have this requirement
|
81
|
+
last_name.gsub!(/[^0-9A-Za-z]/, '')
|
82
|
+
last_name
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generate a unique random email ends with @test.com
|
86
|
+
def generate_test_email
|
87
|
+
[ "#{generate_last_name}.#{generate_unique_id}", 'test.com' ].join('@')
|
88
|
+
end
|
89
|
+
|
90
|
+
# Generate a random number between 0 and `max - 1` if `max` is >= 1,
|
91
|
+
# or between 0 and 1 otherwise.
|
92
|
+
def generate_number(max = nil)
|
93
|
+
rand(max)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Generates a U.S. phone number (NANPA-aware).
|
97
|
+
#
|
98
|
+
# @param format [Symbol, nil] the format of the phone, one of: nil,
|
99
|
+
# `:paren`, `:dotted`, or `:dashed`
|
100
|
+
# @return [String] the phone number
|
101
|
+
def generate_phone_number(format = nil)
|
102
|
+
case format
|
103
|
+
when :paren, :parenthesis, :parentheses
|
104
|
+
'(' + NPA.sample + ') ' + NXX.sample + '-' + generate_digits(4)
|
105
|
+
when :dot, :dotted, :dots, :period, :periods
|
106
|
+
[ NPA.sample, NXX.sample, generate_digits(4) ].join('.')
|
107
|
+
when :dash, :dashed, :dashes
|
108
|
+
[ NPA.sample, NXX.sample, generate_digits(4) ].join('-')
|
109
|
+
else
|
110
|
+
NPA.sample + NXX.sample + generate_digits(4)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Generate a random date.
|
115
|
+
#
|
116
|
+
# @param start_date [Integer] minimum date range
|
117
|
+
# @param end_date [Integer] maximum date range
|
118
|
+
# @return [String] the generated date
|
119
|
+
def generate_date(start_date, end_date)
|
120
|
+
random_date = rand start_date..end_date
|
121
|
+
return random_date.to_formatted_s(:month_day_year)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Generate a unique id with a random hex string and time stamp string
|
125
|
+
def generate_unique_id
|
126
|
+
SecureRandom.hex(3) + Time.current.to_i.to_s
|
127
|
+
end
|
128
|
+
|
129
|
+
# Generate a random password of a certain length, or default length 12
|
130
|
+
#
|
131
|
+
# @param length [#to_i, nil]
|
132
|
+
# @return [String]
|
133
|
+
def generate_password(length = nil)
|
134
|
+
if length.nil?
|
135
|
+
SecureRandom.hex(6) # result length = 12
|
136
|
+
else
|
137
|
+
chars = (('a'..'z').to_a + ('0'..'9').to_a) - %w(i o 0 1 l 0)
|
138
|
+
(1..length).collect{|a| chars[rand(chars.length)] }.join
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|