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.
- 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
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
module MiniAutobot
|
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 = "mini_autobot/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 MiniAutobot::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
|
+
MiniAutobot.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
|
+
MiniAutobot.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 MiniAutobot
|
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
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module MiniAutobot
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
# Page object-related helper methods.
|
6
|
+
module EndecaHelper
|
7
|
+
|
8
|
+
# Helper method to instantiate a new page object. This method should only
|
9
|
+
# be used when first loading; subsequent page objects are automatically
|
10
|
+
# instantiated by calling #cast on the page object.
|
11
|
+
#
|
12
|
+
# @param name [String, Symbol]
|
13
|
+
# @return [PageObject::Base]
|
14
|
+
def endeca(name)
|
15
|
+
# Get the fully-qualified class name
|
16
|
+
klass_name = "mini_autobot/database/endeca".camelize
|
17
|
+
klass = begin
|
18
|
+
klass_name.constantize
|
19
|
+
rescue => exc
|
20
|
+
msg = ""
|
21
|
+
msg << "Cannot find page object '#{name}', "
|
22
|
+
msg << "because could not load class '#{klass_name}' "
|
23
|
+
msg << "with underlying error:\n #{exc.class}: #{exc.message}\n"
|
24
|
+
msg << exc.backtrace.map { |str| " #{str}" }.join("\n")
|
25
|
+
raise NameError, msg
|
26
|
+
|
27
|
+
driver = MiniAutobot::Connector.get_default
|
28
|
+
instance = klass.new(driver)
|
29
|
+
|
30
|
+
Drawbridge.setup do |config|
|
31
|
+
config.bridge_url = driver.env[:endeca][:url]
|
32
|
+
config.bridge_path = driver.env[:endeca][:bridge]
|
33
|
+
# e.g. ENDECA_DEBUG=true rackup
|
34
|
+
config.endeca_debug = ENV.fetch('ENDECA_DEBUG') { false }
|
35
|
+
# optional, default is 5
|
36
|
+
config.timeout = 5
|
37
|
+
# optional, default is to change ' into ' before JSON is parsed
|
38
|
+
config.skip_single_quote_encoding = true
|
39
|
+
end
|
40
|
+
|
41
|
+
return instance
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module MiniAutobot
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
# Module that injects a convenience method to access the logger.
|
6
|
+
module Loggable
|
7
|
+
|
8
|
+
# Convenience instance method to access the default logger.
|
9
|
+
def logger
|
10
|
+
MiniAutobot.logger
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module MiniAutobot
|
2
|
+
module Utils
|
3
|
+
module OverlayAndWidgetHelper
|
4
|
+
# Create widgets of type `name` from `items`, where `name` is the widget
|
5
|
+
# class name, and `items` is a single or an array of WebDriver elements.
|
6
|
+
#
|
7
|
+
# @param name [#to_s] the name of the widget, under `mini_autobots/page_objects/widgets`
|
8
|
+
# to load.
|
9
|
+
# @param items [Enumerable<Selenium::WebDriver::Element>] WebDriver elements.
|
10
|
+
# @return [Enumerable<MiniAutobot::PageObjects::Widgets::Base>]
|
11
|
+
# @raise NameError
|
12
|
+
def get_widgets!(name, items)
|
13
|
+
items = Array(items)
|
14
|
+
return [] if items.empty?
|
15
|
+
|
16
|
+
# Load the widget class
|
17
|
+
klass_name = "mini_autobot/page_objects/widgets/#{name}".camelize
|
18
|
+
klass = begin
|
19
|
+
klass_name.constantize
|
20
|
+
rescue => exc
|
21
|
+
msg = ""
|
22
|
+
msg << "Cannot find widget '#{name}', "
|
23
|
+
msg << "because could not load class '#{klass_name}' "
|
24
|
+
msg << "with underlying error:\n #{exc.class}: #{exc.message}\n"
|
25
|
+
msg << exc.backtrace.map { |str| " #{str}" }.join("\n")
|
26
|
+
raise NameError, msg
|
27
|
+
end
|
28
|
+
|
29
|
+
page = self.page_object
|
30
|
+
|
31
|
+
if items.respond_to?(:map)
|
32
|
+
items.map { |item| klass.new(page, item) }
|
33
|
+
else
|
34
|
+
[klass.new(page, items)]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Create overlay of type `name`, where `name` is the overlay
|
39
|
+
# class name
|
40
|
+
#
|
41
|
+
# @param name [#to_s] the name of the overlay, under `mini_autobots/page_objects/widgets`
|
42
|
+
# to load.
|
43
|
+
# @param items [Enumerable<Selenium::WebDriver::Element>] WebDriver elements.
|
44
|
+
# @return [Enumerable<MiniAutobot::PageObjects::Overlay::Base>]
|
45
|
+
# @raise NameError
|
46
|
+
def get_overlay!(name)
|
47
|
+
# Load the Overlay class
|
48
|
+
klass_name = "mini_autobot/page_objects/overlay/#{name}".camelize
|
49
|
+
klass = begin
|
50
|
+
klass_name.constantize
|
51
|
+
rescue => exc
|
52
|
+
msg = ""
|
53
|
+
msg << "Cannot find overlay '#{name}', "
|
54
|
+
msg << "because could not load class '#{klass_name}' "
|
55
|
+
msg << "with underlying error:\n #{exc.class}: #{exc.message}\n"
|
56
|
+
msg << exc.backtrace.map { |str| " #{str}" }.join("\n")
|
57
|
+
raise NameError, msg
|
58
|
+
end
|
59
|
+
page = self.page_object
|
60
|
+
instance = klass.new(page)
|
61
|
+
# Overlay is triggered to show when there's certain interaction on the page
|
62
|
+
# So validate! is necessary for loading some elements on some overlays
|
63
|
+
begin
|
64
|
+
instance.validate!
|
65
|
+
rescue Minitest::Assertion => exc
|
66
|
+
raise MiniAutobot::PageObjects::InvalidePageState, "#{klass}: #{exc.message}"
|
67
|
+
end
|
68
|
+
instance
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def page_object
|
73
|
+
raise NotImplementedError, "classes including OverlayAndWidgetHelper must override :page_object"
|
74
|
+
end
|
75
|
+
|
76
|
+
end #OverlayAndWidgetHelper
|
77
|
+
end #Utils
|
78
|
+
end #MiniAutobot
|
@@ -0,0 +1,209 @@
|
|
1
|
+
|
2
|
+
module MiniAutobot
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
# Page object-related helper methods.
|
6
|
+
module PageObjectHelper
|
7
|
+
|
8
|
+
# Helper method to instantiate a new page object. This method should only
|
9
|
+
# be used when first loading; subsequent page objects are automatically
|
10
|
+
# instantiated by calling #cast on the page object.
|
11
|
+
#
|
12
|
+
# Pass optional parameter Driver, which can be initialized in test and will override the global driver here.
|
13
|
+
#
|
14
|
+
# @param name [String, Driver]
|
15
|
+
# @return [PageObject::Base]
|
16
|
+
def page(name, override_driver=nil)
|
17
|
+
# Get the fully-qualified class name
|
18
|
+
klass_name = "mini_autobot/page_objects/#{name}".camelize
|
19
|
+
klass = begin
|
20
|
+
klass_name.constantize
|
21
|
+
rescue => exc
|
22
|
+
msg = ""
|
23
|
+
msg << "Cannot find page object '#{name}', "
|
24
|
+
msg << "because could not load class '#{klass_name}' "
|
25
|
+
msg << "with underlying error:\n #{exc.class}: #{exc.message}\n"
|
26
|
+
msg << exc.backtrace.map { |str| " #{str}" }.join("\n")
|
27
|
+
raise NameError, msg
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get a default connector
|
31
|
+
@driver = MiniAutobot::Connector.get_default if override_driver.nil?
|
32
|
+
@driver = override_driver if !override_driver.nil?
|
33
|
+
instance = klass.new(@driver)
|
34
|
+
|
35
|
+
# Before visiting the page, do any pre-processing necessary, if any,
|
36
|
+
# but only visit the page if the pre-processing succeeds
|
37
|
+
if block_given?
|
38
|
+
retval = yield instance
|
39
|
+
instance.go! if retval
|
40
|
+
else
|
41
|
+
instance.go! if override_driver.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
# similar like casting a page, necessary to validate some element on a page
|
45
|
+
begin
|
46
|
+
instance.validate!
|
47
|
+
rescue Minitest::Assertion => exc
|
48
|
+
raise MiniAutobot::PageObjects::InvalidePageState, "#{klass}: #{exc.message}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the instance as-is
|
52
|
+
instance
|
53
|
+
end
|
54
|
+
|
55
|
+
# Local teardown for page objects. Any page objects that are loaded will
|
56
|
+
# be finalized upon teardown.
|
57
|
+
#
|
58
|
+
# @return [void]
|
59
|
+
def teardown
|
60
|
+
if !passed? && !skipped? && !@driver.nil?
|
61
|
+
take_screenshot
|
62
|
+
print_sauce_link if connector_is_saucelabs?
|
63
|
+
end
|
64
|
+
begin
|
65
|
+
set_sauce_session_name if connector_is_saucelabs? && !@driver.nil?
|
66
|
+
self.logger.debug "Finished setting saucelabs session name for #{name()}"
|
67
|
+
rescue
|
68
|
+
self.logger.debug "Failed setting saucelabs session name for #{name()}"
|
69
|
+
end
|
70
|
+
|
71
|
+
MiniAutobot::Connector.finalize!
|
72
|
+
super
|
73
|
+
end
|
74
|
+
|
75
|
+
def take_screenshot
|
76
|
+
@driver.save_screenshot("logs/#{name}.png")
|
77
|
+
end
|
78
|
+
|
79
|
+
# Print out a link of a saucelabs's job when a test is not passed
|
80
|
+
# Rescue to skip this step for tests like cube tracking
|
81
|
+
def print_sauce_link
|
82
|
+
begin
|
83
|
+
puts "Find test on saucelabs: https://saucelabs.com/tests/#{@driver.session_id}"
|
84
|
+
rescue
|
85
|
+
puts 'can not retrieve driver session id, no link to saucelabs'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# update session name on saucelabs
|
90
|
+
def set_sauce_session_name
|
91
|
+
# identify the user who runs the tests and grab user's access_key
|
92
|
+
# where are we parsing info from run command to in the code?
|
93
|
+
connector = MiniAutobot.settings.connector # eg. saucelabs:phu:win7_ie11
|
94
|
+
overrides = connector.to_s.split(/:/)
|
95
|
+
new_tags = overrides[2]+"_by_"+overrides[1]
|
96
|
+
file_name = overrides.shift
|
97
|
+
path = MiniAutobot.root.join('config/mini_autobot', 'connectors')
|
98
|
+
filepath = path.join("#{file_name}.yml")
|
99
|
+
raise ArgumentError, "Cannot load profile #{file_name.inspect} because #{filepath.inspect} does not exist" unless filepath.exist?
|
100
|
+
|
101
|
+
cfg = YAML.load(File.read(filepath))
|
102
|
+
cfg = Connector.resolve(cfg, overrides)
|
103
|
+
cfg.freeze
|
104
|
+
username = cfg["hub"]["user"]
|
105
|
+
access_key = cfg["hub"]["pass"]
|
106
|
+
|
107
|
+
require 'json'
|
108
|
+
session_id = @driver.session_id
|
109
|
+
http_auth = "https://#{username}:#{access_key}@saucelabs.com/rest/v1/#{username}/jobs/#{session_id}"
|
110
|
+
# to_json need to: require "active_support/core_ext", but will mess up the whole framework, require 'json' in this method solved it
|
111
|
+
body = {"name" => name(), "tags" => [new_tags]}.to_json
|
112
|
+
# gem 'rest-client'
|
113
|
+
RestClient.put(http_auth, body, {:content_type => "application/json"})
|
114
|
+
end
|
115
|
+
|
116
|
+
def connector_is_saucelabs?
|
117
|
+
return true if MiniAutobot.settings.connector.include?('saucelabs')
|
118
|
+
return false
|
119
|
+
end
|
120
|
+
|
121
|
+
# Generic page object helper method to clear and send keys to a web element found by driver
|
122
|
+
# @param [Element, String]
|
123
|
+
def put_value(web_element, value)
|
124
|
+
web_element.clear
|
125
|
+
web_element.send_keys(value)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Helper method for retrieving value from yml file
|
129
|
+
# todo should be moved to FileHelper.rb once we created this file in utils
|
130
|
+
# @param [String, String]
|
131
|
+
# keys, eg. "timeouts:implicit_wait"
|
132
|
+
def read_yml(file_name, keys)
|
133
|
+
data = Hash.new
|
134
|
+
begin
|
135
|
+
data = YAML.load_file "#{file_name}"
|
136
|
+
rescue
|
137
|
+
raise Exception, "File #{file_name} doesn't exist" unless File.exist?(file_name)
|
138
|
+
rescue
|
139
|
+
raise YAMLErrors, "Failed to load #{file_name}"
|
140
|
+
end
|
141
|
+
keys_array = keys.split(/:/)
|
142
|
+
value = data
|
143
|
+
keys_array.each do |key|
|
144
|
+
value = value[key]
|
145
|
+
end
|
146
|
+
return value
|
147
|
+
end
|
148
|
+
|
149
|
+
# Check if a web element exists on page or not, without wait
|
150
|
+
def is_element_present?(how, what, driver = nil)
|
151
|
+
element_appeared?(how, what, driver)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Check if a web element exists and displayed on page or not, without wait
|
155
|
+
def is_element_present_and_displayed?(how, what, driver = nil)
|
156
|
+
element_appeared?(how, what, driver, check_display = true)
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
# @param eg. (:css, 'button.cancel') or (*BUTTON_SUBMIT_SEARCH)
|
162
|
+
# @param also has an optional parameter-driver, which can be @element when calling this method in a widget object
|
163
|
+
# @return [boolean]
|
164
|
+
def element_appeared?(how, what, driver = nil, check_display = false)
|
165
|
+
original_timeout = read_yml("config/mini_autobot/connectors/saucelabs.yml", "timeouts:implicit_wait")
|
166
|
+
@driver.manage.timeouts.implicit_wait = 0
|
167
|
+
result = false
|
168
|
+
parent_element = @driver if driver == nil
|
169
|
+
parent_element = driver if driver != nil
|
170
|
+
elements = parent_element.find_elements(how, what)
|
171
|
+
if check_display
|
172
|
+
begin
|
173
|
+
result = true if elements.size() > 0 && elements[0].displayed?
|
174
|
+
rescue
|
175
|
+
result = false
|
176
|
+
end
|
177
|
+
else
|
178
|
+
result = true if elements.size() > 0
|
179
|
+
end
|
180
|
+
@driver.manage.timeouts.implicit_wait = original_timeout
|
181
|
+
return result
|
182
|
+
end
|
183
|
+
|
184
|
+
def wait_for_element_to_display(how, what, friendly_name = "element")
|
185
|
+
wait(timeout: 15, message: "Timeout waiting for #{friendly_name} to display")
|
186
|
+
.until {is_element_present_and_displayed?(how, what)}
|
187
|
+
end
|
188
|
+
|
189
|
+
def wait_for_element_to_be_present(how, what, friendly_name = "element")
|
190
|
+
wait(timeout: 15, message: "Timeout waiting for #{friendly_name} to be present")
|
191
|
+
.until {is_element_present?(how, what)}
|
192
|
+
end
|
193
|
+
|
194
|
+
# Useful when you want to wait for the status of an element attribute to change
|
195
|
+
# Example: the class attribute of <body> changes to include 'logged-in' when a user signs in to rent.com
|
196
|
+
# Example usage: wait_for_attribute_status_change(:css, 'body', 'class', 'logged-in', 'sign in')
|
197
|
+
def wait_for_attribute_to_have_value(how, what, attribute, value, friendly_name = "attribute")
|
198
|
+
wait(timeout: 15, message: "Timeout waiting for #{friendly_name} status to update")
|
199
|
+
.until { driver.find_element(how, what).attribute(attribute).include?(value) rescue retry }
|
200
|
+
end
|
201
|
+
|
202
|
+
def current_page(calling_page)
|
203
|
+
calling_page.class.to_s.split('::').last.downcase
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|