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
@@ -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
|