mini_autobot 0.0.1

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