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