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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +191 -0
  7. data/LICENSE +22 -0
  8. data/README.md +632 -0
  9. data/bin/mini_autobot +5 -0
  10. data/lib/mini_autobot.rb +44 -0
  11. data/lib/mini_autobot/connector.rb +288 -0
  12. data/lib/mini_autobot/console.rb +15 -0
  13. data/lib/mini_autobot/emails.rb +5 -0
  14. data/lib/mini_autobot/emails/mailbox.rb +15 -0
  15. data/lib/mini_autobot/endeca/base.rb +6 -0
  16. data/lib/mini_autobot/init.rb +63 -0
  17. data/lib/mini_autobot/logger.rb +12 -0
  18. data/lib/mini_autobot/page_objects.rb +22 -0
  19. data/lib/mini_autobot/page_objects/base.rb +264 -0
  20. data/lib/mini_autobot/page_objects/overlay/base.rb +76 -0
  21. data/lib/mini_autobot/page_objects/widgets/base.rb +47 -0
  22. data/lib/mini_autobot/parallel.rb +197 -0
  23. data/lib/mini_autobot/runner.rb +91 -0
  24. data/lib/mini_autobot/settings.rb +78 -0
  25. data/lib/mini_autobot/test_case.rb +233 -0
  26. data/lib/mini_autobot/test_cases.rb +7 -0
  27. data/lib/mini_autobot/utils.rb +10 -0
  28. data/lib/mini_autobot/utils/assertion_helper.rb +35 -0
  29. data/lib/mini_autobot/utils/castable.rb +103 -0
  30. data/lib/mini_autobot/utils/data_generator_helper.rb +145 -0
  31. data/lib/mini_autobot/utils/endeca_helper.rb +46 -0
  32. data/lib/mini_autobot/utils/loggable.rb +16 -0
  33. data/lib/mini_autobot/utils/overlay_and_widget_helper.rb +78 -0
  34. data/lib/mini_autobot/utils/page_object_helper.rb +209 -0
  35. data/lib/mini_autobot/version.rb +3 -0
  36. data/lib/minitap/minitest5_rent.rb +22 -0
  37. data/lib/minitest/autobot_settings_plugin.rb +77 -0
  38. data/lib/tapout/custom_reporters/fancy_tap_reporter.rb +94 -0
  39. data/lib/yard/tagged_test_case_handler.rb +61 -0
  40. data/mini_autobot.gemspec +38 -0
  41. 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 &#39; 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