eyes_selenium 2.15.0 → 2.16.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.travis.yml +16 -0
  4. data/Gemfile +1 -1
  5. data/README.md +14 -4
  6. data/Rakefile +8 -1
  7. data/eyes_selenium.gemspec +26 -17
  8. data/lib/applitools/base/batch_info.rb +19 -0
  9. data/lib/applitools/base/dimension.rb +21 -0
  10. data/lib/applitools/base/environment.rb +19 -0
  11. data/lib/applitools/base/mouse_trigger.rb +33 -0
  12. data/lib/applitools/base/point.rb +21 -0
  13. data/lib/applitools/base/region.rb +77 -0
  14. data/lib/applitools/base/server_connector.rb +113 -0
  15. data/lib/applitools/base/session.rb +15 -0
  16. data/lib/applitools/base/start_info.rb +34 -0
  17. data/lib/applitools/base/test_results.rb +36 -0
  18. data/lib/applitools/base/text_trigger.rb +22 -0
  19. data/lib/applitools/eyes.rb +393 -0
  20. data/lib/applitools/eyes_logger.rb +40 -0
  21. data/lib/applitools/method_tracer.rb +22 -0
  22. data/lib/applitools/selenium/driver.rb +194 -0
  23. data/lib/applitools/selenium/element.rb +66 -0
  24. data/lib/applitools/selenium/keyboard.rb +27 -0
  25. data/lib/applitools/selenium/match_window_data.rb +24 -0
  26. data/lib/applitools/selenium/match_window_task.rb +190 -0
  27. data/lib/applitools/selenium/mouse.rb +62 -0
  28. data/lib/applitools/selenium/viewport_size.rb +128 -0
  29. data/lib/applitools/utils/image_delta_compressor.rb +150 -0
  30. data/lib/applitools/utils/image_utils.rb +63 -0
  31. data/lib/applitools/utils/utils.rb +52 -0
  32. data/lib/applitools/version.rb +3 -0
  33. data/lib/eyes_selenium.rb +9 -29
  34. data/spec/driver_passthrough_spec.rb +25 -25
  35. data/spec/spec_helper.rb +5 -8
  36. data/test/appium_example_script.rb +57 -0
  37. data/{test_script.rb → test/test_script.rb} +7 -9
  38. data/{watir_test.rb → test/watir_test_script.rb} +6 -4
  39. metadata +120 -48
  40. data/appium_eyes_example.rb +0 -56
  41. data/lib/eyes_selenium/capybara.rb +0 -21
  42. data/lib/eyes_selenium/eyes/agent_connector.rb +0 -76
  43. data/lib/eyes_selenium/eyes/batch_info.rb +0 -19
  44. data/lib/eyes_selenium/eyes/dimension.rb +0 -15
  45. data/lib/eyes_selenium/eyes/driver.rb +0 -266
  46. data/lib/eyes_selenium/eyes/element.rb +0 -78
  47. data/lib/eyes_selenium/eyes/environment.rb +0 -15
  48. data/lib/eyes_selenium/eyes/eyes.rb +0 -396
  49. data/lib/eyes_selenium/eyes/eyes_keyboard.rb +0 -25
  50. data/lib/eyes_selenium/eyes/eyes_mouse.rb +0 -60
  51. data/lib/eyes_selenium/eyes/failure_reports.rb +0 -4
  52. data/lib/eyes_selenium/eyes/match_level.rb +0 -8
  53. data/lib/eyes_selenium/eyes/match_window_data.rb +0 -18
  54. data/lib/eyes_selenium/eyes/match_window_task.rb +0 -182
  55. data/lib/eyes_selenium/eyes/mouse_trigger.rb +0 -23
  56. data/lib/eyes_selenium/eyes/region.rb +0 -72
  57. data/lib/eyes_selenium/eyes/screenshot_taker.rb +0 -18
  58. data/lib/eyes_selenium/eyes/session.rb +0 -14
  59. data/lib/eyes_selenium/eyes/start_info.rb +0 -32
  60. data/lib/eyes_selenium/eyes/test_results.rb +0 -32
  61. data/lib/eyes_selenium/eyes/text_trigger.rb +0 -19
  62. data/lib/eyes_selenium/eyes/viewport_size.rb +0 -105
  63. data/lib/eyes_selenium/eyes_logger.rb +0 -47
  64. data/lib/eyes_selenium/utils.rb +0 -6
  65. data/lib/eyes_selenium/utils/image_delta_compressor.rb +0 -149
  66. data/lib/eyes_selenium/utils/image_utils.rb +0 -76
  67. data/lib/eyes_selenium/version.rb +0 -3
@@ -0,0 +1,40 @@
1
+ require 'logger'
2
+ require 'forwardable'
3
+
4
+ module Applitools::EyesLogger
5
+ class NullLogger < Logger
6
+ def initialize(*args)
7
+ end
8
+
9
+ def add(*args, &block)
10
+ end
11
+ end
12
+
13
+ extend Forwardable
14
+ extend self
15
+
16
+ MANDATORY_METHODS = [:debug, :info, :close].freeze
17
+ OPTIONAL_METHODS = [:warn, :error, :fatal].freeze
18
+
19
+ def_delegators :@@log_handler, *MANDATORY_METHODS
20
+
21
+ @@log_handler = NullLogger.new
22
+
23
+ def log_handler=(log_handler)
24
+ raise Applitools::EyesError.new('log_handler must implement Logger!') unless valid?(log_handler)
25
+
26
+ @@log_handler = log_handler
27
+ end
28
+
29
+ OPTIONAL_METHODS.each do |method|
30
+ define_singleton_method(method) do |msg|
31
+ @@log_handler.respond_to?(method) ? @@log_handler.send(method, msg) : @@log_handler.info(msg)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def valid?(log_handler)
38
+ MANDATORY_METHODS.all? {|method| log_handler.respond_to?(method)}
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ module Applitools::MethodTracer
2
+ def self.included(base)
3
+ instance_methods = base.instance_methods(false) + base.private_instance_methods(false)
4
+ class_methods = base.methods(false)
5
+
6
+ base.class_eval do
7
+ def self.trace_method(base, method_name, instance = true)
8
+ original_method = instance ? instance_method(method_name) : method(method_name)
9
+
10
+ send(instance ? :define_method : :define_singleton_method, method_name) do |*args, &block|
11
+ Applitools::EyesLogger.debug "-> #{base}##{method_name}"
12
+ return_value = (instance ? original_method.bind(self) : original_method).call(*args, &block)
13
+ Applitools::EyesLogger.debug "<- #{base}##{method_name}"
14
+ return_value
15
+ end
16
+ end
17
+
18
+ instance_methods.each {|method_name| trace_method(base, method_name) }
19
+ class_methods.each {|method_name| trace_method(base, method_name, false) }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,194 @@
1
+ require 'forwardable'
2
+ require 'socket'
3
+ require 'selenium-webdriver'
4
+
5
+ module Applitools::Selenium
6
+ class Driver < SimpleDelegator
7
+ extend Forwardable
8
+
9
+ include Selenium::WebDriver::DriverExtensions::HasInputDevices
10
+
11
+ RIGHT_ANGLE = 90.freeze
12
+ ANDROID = 'ANDROID'.freeze
13
+ IOS = 'IOS'.freeze
14
+ LANDSCAPE = 'LANDSCAPE'.freeze
15
+
16
+ IE = 'ie'.freeze
17
+ FIREFOX = 'firefox'.freeze
18
+
19
+ FINDERS = {
20
+ class: 'class name',
21
+ class_name: 'class name',
22
+ css: 'css selector',
23
+ id: 'id',
24
+ link: 'link text',
25
+ link_text: 'link text',
26
+ name: 'name',
27
+ partial_link_text: 'partial link text',
28
+ tag_name: 'tag name',
29
+ xpath: 'xpath'
30
+ }.freeze
31
+
32
+ JS_GET_USER_AGENT = 'return navigator.userAgent;'.freeze
33
+
34
+ def_delegators :@eyes, :user_inputs, :clear_user_inputs
35
+
36
+ # If driver is not provided, Applitools::Selenium::Driver will raise an EyesError exception.
37
+ def initialize(eyes, options)
38
+ super(options[:driver])
39
+
40
+ @is_mobile_device = options.fetch(:is_mobile_device, false)
41
+ @eyes = eyes
42
+
43
+ raise 'Uncapable of taking screenshots!' unless capabilities.takes_screenshot?
44
+ end
45
+
46
+ # Rotates the image as necessary. The rotation is either manually forced by passing a value in
47
+ # the +rotation+ parameter, or automatically inferred if the +rotation+ parameter is +nil+.
48
+ #
49
+ # +driver+:: +Applitools::Selenium::Driver+ The driver which produced the screenshot.
50
+ # +image+:: +ChunkyPNG::Canvas+ The image to normalize.
51
+ # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the image: positive values = clockwise rotation,
52
+ # negative values = counter-clockwise, 0 = force no rotation, +nil+ = rotate automatically when needed.
53
+ def self.normalize_rotation(driver, image, rotation)
54
+ return if rotation == 0
55
+
56
+ num_quadrants = 0
57
+ if !rotation.nil?
58
+ if rotation % RIGHT_ANGLE != 0
59
+ raise Applitools::EyesError.new("Currently only quadrant rotations are supported. Current rotation: "\
60
+ "#{rotation}")
61
+ end
62
+ num_quadrants = (rotation / RIGHT_ANGLE).to_i
63
+ elsif rotation.nil? && driver.mobile_device? && driver.landscape_orientation? && image.height > image.width
64
+ # For Android, we need to rotate images to the right, and for iOS to the left.
65
+ num_quadrants = driver.android? ? 1 : -1
66
+ end
67
+
68
+ Applitools::Utils::ImageUtils.quadrant_rotate!(image, num_quadrants)
69
+ end
70
+
71
+ # Returns:
72
+ # +String+ The platform name or +nil+ if it is undefined.
73
+ def platform_name
74
+ capabilities['platformName']
75
+ end
76
+
77
+ # Returns:
78
+ # +String+ The platform version or +nil+ if it is undefined.
79
+ def platform_version
80
+ version = capabilities['platformVersion']
81
+ version.nil? ? nil : version.to_s
82
+ end
83
+
84
+ # Returns:
85
+ # +true+ if the driver is an Android driver.
86
+ def android?
87
+ platform_name.to_s.upcase == ANDROID
88
+ end
89
+
90
+ # Returns:
91
+ # +true+ if the driver is an iOS driver.
92
+ def ios?
93
+ platform_name.to_s.upcase == IOS
94
+ end
95
+
96
+ # Returns:
97
+ # +true+ if the driver orientation is landscape.
98
+ def landscape_orientation?
99
+ begin
100
+ driver.orientation.to_s.upcase == LANDSCAPE
101
+ rescue NameError
102
+ Applitools::EyesLogger.debug 'driver has no "orientation" attribute. Assuming: portrait.'
103
+ end
104
+ end
105
+
106
+ # Returns:
107
+ # +true+ if the platform running the test is a mobile platform. +false+ otherwise.
108
+ def mobile_device?
109
+ # We CAN'T check if the device is an +Appium::Driver+ since it is not a RemoteWebDriver. Because of that we use a
110
+ # flag we got as an option in the constructor.
111
+ @is_mobile_device
112
+ end
113
+
114
+ # Return a PNG screenshot in the given format as a string
115
+ #
116
+ # +output_type+:: +Symbol+ The format of the screenshot. Accepted values are +:base64+ and +:png+.
117
+ # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the image: positive values = clockwise rotation,
118
+ # negative values = counter-clockwise, 0 = force no rotation, +nil+ = rotate automatically when needed.
119
+ #
120
+ # Returns: +String+ A screenshot in the requested format.
121
+ def screenshot_as(output_type, rotation = nil)
122
+ screenshot = Applitools::Utils::ImageUtils.png_image_from_base64(driver.screenshot_as(:base64))
123
+ Applitools::Selenium::Driver.normalize_rotation(self, screenshot, rotation)
124
+
125
+ case output_type
126
+ when :base64
127
+ screenshot = Applitools::Utils::ImageUtils.base64_from_png_image(screenshot)
128
+ when :png
129
+ screenshot = Applitools::Utils::ImageUtils.bytes_from_png_image(screenshot)
130
+ else
131
+ raise Applitools::EyesError.new("Unsupported screenshot output type: #{output_type.to_s}")
132
+ end
133
+
134
+ screenshot.force_encoding('BINARY')
135
+ end
136
+
137
+ def mouse
138
+ Applitools::Selenium::Mouse.new(self, driver.mouse)
139
+ end
140
+
141
+ def keyboard
142
+ Applitools::Selenium::Keyboard.new(self, driver.keyboard)
143
+ end
144
+
145
+ def find_element(*args)
146
+ how, what = extract_args(args)
147
+
148
+ # Make sure that "how" is a valid locator.
149
+ raise ArgumentError, "cannot find element by: #{how.inspect}" unless FINDERS[how.to_sym]
150
+
151
+ Applitools::Selenium::Element.new(self, driver.find_element(how, what))
152
+ end
153
+
154
+ def find_elements(*args)
155
+ how, what = extract_args(args)
156
+
157
+ raise ArgumentError, "cannot find element by: #{how.inspect}" unless FINDERS[how.to_sym]
158
+
159
+ driver.find_elements(how, what).map { |el| Applitools::Selenium::Element.new(self, el) }
160
+ end
161
+
162
+ def user_agent
163
+ @user_agent ||= execute_script JS_GET_USER_AGENT
164
+ rescue => e
165
+ Applitools::EyesLogger.error "Failed to obtain user-agent string (#{e.message})"
166
+
167
+ nil
168
+ end
169
+
170
+ private
171
+
172
+ def driver
173
+ @driver ||= __getobj__
174
+ end
175
+
176
+ def extract_args(args)
177
+ case args.size
178
+ when 2
179
+ args
180
+ when 1
181
+ arg = args.first
182
+
183
+ raise Argu mentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" unless arg.respond_to?(:shift)
184
+
185
+ # This will be a single-entry hash, so use #shift over #first or #[].
186
+ arg.dup.shift.tap do |arr|
187
+ raise ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2
188
+ end
189
+ else
190
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,66 @@
1
+ module Applitools::Selenium
2
+ class Element < SimpleDelegator
3
+ TRACE_PREFIX = 'EyesWebElement'.freeze
4
+
5
+ def initialize(driver, element)
6
+ super(element)
7
+
8
+ @driver = driver
9
+ end
10
+
11
+ def web_element
12
+ @web_element ||= __getobj__
13
+ end
14
+
15
+ def click
16
+ current_control = region
17
+ offset = current_control.middle_offset
18
+ @driver.user_inputs << Applitools::Base::MouseTrigger.new(:click, current_control, offset)
19
+
20
+ web_element.click
21
+ end
22
+
23
+ def inspect
24
+ TRACE_PREFIX + web_element.inspect
25
+ end
26
+
27
+ def ==(other)
28
+ other.kind_of?(web_element.class) && web_element == other
29
+ end
30
+ alias_method :eql?, :==
31
+
32
+ def send_keys(*args)
33
+ current_control = region
34
+ Selenium::WebDriver::Keys.encode(args).each do |key|
35
+ @driver.user_inputs << Applitools::Base::TextTrigger.new(key.to_s, current_control)
36
+ end
37
+
38
+ web_element.send_keys(*args)
39
+ end
40
+ alias_method :send_key, :send_keys
41
+
42
+ def region
43
+ point = location
44
+ left, top, width, height = point.x, point.y, 0, 0
45
+
46
+ begin
47
+ dimension = size
48
+ width, height = dimension.width, dimension.height
49
+ rescue
50
+ # Not supported on all platforms.
51
+ end
52
+
53
+ if left < 0
54
+ width = [0, width + left].max
55
+ left = 0
56
+ end
57
+
58
+ if top < 0
59
+ height = [0, height + top].max
60
+ top = 0
61
+ end
62
+
63
+ return Applitools::Base::Region.new(left, top, width, height)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,27 @@
1
+ module Applitools::Selenium
2
+ class Keyboard
3
+ attr_reader :keyboard, :driver
4
+
5
+ def initialize(driver, keyboard)
6
+ @driver = driver
7
+ @keyboard = keyboard
8
+ end
9
+
10
+ def send_keys(*keys)
11
+ active_element = Applitools::Selenium::Element.new(driver, driver.switch_to.active_element)
12
+ current_control = active_element.region
13
+ Selenium::WebDriver::Keys.encode(keys).each do |key|
14
+ driver.user_inputs << Applitools::Base::TextTrigger.new(key.to_s, current_control)
15
+ end
16
+ keyboard.send_keys(*keys)
17
+ end
18
+
19
+ def press(key)
20
+ keyboard.press(key)
21
+ end
22
+
23
+ def release(key)
24
+ keyboard.release(key)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ module Applitools::Selenium
2
+ class MatchWindowData
3
+ attr_reader :user_inputs, :app_output, :tag, :ignore_mismatch, :screenshot
4
+
5
+ def initialize(app_output, user_inputs = [], tag, ignore_mismatch, screenshot)
6
+ @user_inputs = user_inputs
7
+ @app_output = app_output
8
+ @tag = tag
9
+ @ignore_mismatch = ignore_mismatch
10
+ @screenshot = screenshot
11
+ end
12
+
13
+ # IMPORTANT This method returns a hash WITHOUT the screenshot property. This is on purspose! The screenshot should
14
+ # not be included as part of the json.
15
+ def to_hash
16
+ {
17
+ user_inputs: user_inputs.map(&:to_hash),
18
+ app_output: Hash[app_output.each_pair.to_a],
19
+ tag: @tag,
20
+ ignore_mismatch: @ignore_mismatch
21
+ }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,190 @@
1
+ require 'base64'
2
+
3
+ module Applitools::Selenium
4
+ class MatchWindowTask
5
+ MATCH_INTERVAL = 0.5.freeze
6
+ AppOuptut = Struct.new(:title, :screenshot64)
7
+
8
+ attr_reader :eyes, :session, :driver, :default_retry_timeout, :last_checked_window,
9
+ :last_screenshot_bounds
10
+
11
+ def initialize(eyes, session, driver, default_retry_timeout)
12
+ @eyes = eyes
13
+
14
+ @session = session
15
+ @driver = driver
16
+ @default_retry_timeout = default_retry_timeout
17
+ @last_checked_window = nil # +ChunkyPNG::Canvas+
18
+ @last_screenshot_bounds = Applitools::Base::Region::EMPTY # +Applitools::Base::Region+
19
+ @current_screenshot = nil # +ChunkyPNG::Canvas+
20
+ end
21
+
22
+ def match_window(region, retry_timeout, tag, rotation, run_once_after_wait = false)
23
+ retry_timeout = default_retry_timeout if retry_timeout < 0
24
+
25
+ Applitools::EyesLogger.debug "Retry timeout set to: #{retry_timeout}"
26
+
27
+ start = Time.now
28
+ res = if retry_timeout.zero?
29
+ run(region, tag, rotation)
30
+ elsif run_once_after_wait
31
+ run(region, tag, rotation, retry_timeout)
32
+ else
33
+ run_with_intervals(region, tag, rotation, retry_timeout)
34
+ end
35
+ elapsed_time = Time.now - start
36
+
37
+ Applitools::EyesLogger.debug "match_window(): Completed in #{format('%.2f', elapsed_time)} seconds"
38
+
39
+ @last_checked_window = @current_screenshot
40
+ @last_screenshot_bounds = region.empty? ? Applitools::Base::Region.new(0, 0, last_checked_window.width,
41
+ last_checked_window.height) : region
42
+ driver.clear_user_inputs
43
+
44
+ res
45
+ end
46
+
47
+ def run(region, tag, rotation, wait_before_run=nil)
48
+ Applitools::EyesLogger.debug 'Trying matching once...'
49
+
50
+ if wait_before_run
51
+ Applitools::EyesLogger.debug 'Waiting before run...'
52
+ sleep(wait_before_run)
53
+ Applitools::EyesLogger.debug 'Wwaiting done!'
54
+ end
55
+
56
+ match(region, tag, rotation)
57
+ end
58
+
59
+ def run_with_intervals(region, tag, rotation, retry_timeout)
60
+ # We intentionally take the first screenshot before starting the timer, to allow the page just a tad more time to
61
+ # stabilize.
62
+ Applitools::EyesLogger.debug 'Matching with intervals...'
63
+ data = prep_match_data(region, tag, rotation, true)
64
+ start = Time.now
65
+ as_expected = Applitools::Base::ServerConnector.match_window(session, data)
66
+ Applitools::EyesLogger.debug "First call result: #{as_expected}"
67
+ return true if as_expected
68
+ Applitools::EyesLogger.debug "Not as expected, performing retry (total timeout #{retry_timeout})"
69
+ match_retry = Time.now - start
70
+ while match_retry < retry_timeout
71
+ Applitools::EyesLogger.debug 'Waiting before match...'
72
+ sleep(MATCH_INTERVAL)
73
+ Applitools::EyesLogger.debug 'Done! Matching...'
74
+ return true if match(region, tag, rotation, true)
75
+ match_retry = Time.now - start
76
+ Applitools::EyesLogger.debug "Elapsed time: #{match_retry}"
77
+ end
78
+ # Let's try one more time if we still don't have a match.
79
+ Applitools::EyesLogger.debug 'Last attempt to match...'
80
+ as_expected = match(region, tag, rotation)
81
+ Applitools::EyesLogger.debug "Match result: #{as_expected}"
82
+ as_expected
83
+ end
84
+
85
+ private
86
+
87
+ def get_clipped_region(region, image)
88
+ left, top = [region.left, 0].max, [region.top, 0].max
89
+ max_width = image.width - left
90
+ max_height = image.height - top
91
+ width, height = [region.width, max_width].min, [region.height, max_height].min
92
+ Applitools::Base::Region.new(left, top, width, height)
93
+ end
94
+
95
+ def prep_match_data(region, tag, rotation, ignore_mismatch)
96
+ Applitools::EyesLogger.debug 'Preparing match data...'
97
+ title = eyes.title
98
+ Applitools::EyesLogger.debug 'Getting screenshot...'
99
+ current_screenshot_encoded = driver.screenshot_as(:png, rotation)
100
+ Applitools::EyesLogger.debug 'Done! Creating image object from PNG...'
101
+ @current_screenshot = ChunkyPNG::Image.from_blob(current_screenshot_encoded)
102
+ Applitools::EyesLogger.debug 'Done!'
103
+ # If a region was defined, we refer to the sub-image defined by the region.
104
+ unless region.empty?
105
+ Applitools::EyesLogger.debug 'Calculating clipped region...'
106
+ # If the region is out of bounds, clip it
107
+ clipped_region = get_clipped_region(region, @current_screenshot)
108
+ raise Applitools::EyesError.new("Region is outside the viewport: #{region}") if clipped_region.empty?
109
+ Applitools::EyesLogger.debug 'Done! Cropping region...'
110
+ @current_screenshot.crop!(clipped_region.left, clipped_region.top, clipped_region.width, clipped_region.height)
111
+ Applitools::EyesLogger.debug 'Done! Creating cropped image object...'
112
+ current_screenshot_encoded = @current_screenshot.to_blob.force_encoding('BINARY')
113
+ Applitools::EyesLogger.debug 'Done!'
114
+ end
115
+ Applitools::EyesLogger.debug 'Compressing screenshot...'
116
+ compressed_screenshot = Applitools::Utils::ImageDeltaCompressor.compress_by_raw_blocks(@current_screenshot,
117
+ current_screenshot_encoded, last_checked_window)
118
+ Applitools::EyesLogger.debug 'Done! Creating AppOuptut...'
119
+ app_output = AppOuptut.new(title, nil)
120
+ user_inputs = []
121
+ Applitools::EyesLogger.debug 'Handling user inputs...'
122
+ if !last_checked_window.nil?
123
+ driver.user_inputs.each do |trigger|
124
+ Applitools::EyesLogger.debug 'Handling trigger...'
125
+ if trigger.is_a?(Applitools::Base::MouseTrigger)
126
+ updated_trigger = nil
127
+ trigger_left = trigger.control.left + trigger.location.x
128
+ trigger_top = trigger.control.top + trigger.location.y
129
+ if last_screenshot_bounds.contains?(trigger_left, trigger_top)
130
+ trigger.control.intersect(last_screenshot_bounds)
131
+ if trigger.control.empty?
132
+ trigger_left -= - last_screenshot_bounds.left
133
+ trigger_top = trigger_top - last_screenshot_bounds.top
134
+ updated_trigger = Applitools::Base::MouseTrigger.new(trigger.mouse_action, trigger.control,
135
+ Applitools::Base::Point.new(trigger_left, trigger_top))
136
+ else
137
+ trigger_left = trigger_left - trigger.control.left
138
+ trigger_top = trigger_top - trigger.control.top
139
+ control_left = trigger.control.left - last_screenshot_bounds.left
140
+ control_top = trigger.control.top - last_screenshot_bounds.top
141
+ updated_control = Applitools::Base::Region.new(control_left, control_top, trigger.control.width,
142
+ trigger.control.height)
143
+ updated_trigger = Applitools::Base::MouseTrigger.new(trigger.mouse_action, updated_control,
144
+ Applitools::Base::Point.new(trigger_left, trigger_top))
145
+ end
146
+ Applitools::EyesLogger.debug 'Done with trigger!'
147
+ user_inputs << updated_trigger
148
+ else
149
+ Applitools::EyesLogger.info "Trigger ignored: #{trigger} (out of bounds)"
150
+ end
151
+ elsif trigger.is_a?(Applitools::Base::TextTrigger)
152
+ unless trigger.control.empty?
153
+ trigger.control.intersect(last_screenshot_bounds)
154
+ unless trigger.control.empty?
155
+ control_left = trigger.control.left - last_screenshot_bounds.left
156
+ control_top = trigger.control.top - last_screenshot_bounds.top
157
+ updated_control = Applitools::Base::Region.new(control_left, control_top, trigger.control.width,
158
+ trigger.control.height)
159
+ updated_trigger = Applitools::Base::TextTrigger.new(trigger.text, updated_control)
160
+ Applitools::EyesLogger.debug 'Done with trigger!'
161
+ user_inputs << updated_trigger
162
+ else
163
+ Applitools::EyesLogger.info "Trigger ignored: #{trigger} (control out of bounds)"
164
+ end
165
+ else
166
+ Applitools::EyesLogger.info "Trigger ignored: #{trigger} (out of bounds)"
167
+ end
168
+ else
169
+ Applitools::EyesLogger.info "Trigger ignored: #{trigger} (Unrecognized trigger)"
170
+ end
171
+ end
172
+ else
173
+ Applitools::EyesLogger.info 'Triggers ignored: no previous window checked'
174
+ end
175
+ Applitools::EyesLogger.debug 'Creating MatchWindowData object..'
176
+ match_window_data_obj = Applitools::Selenium::MatchWindowData.new(app_output, user_inputs, tag, ignore_mismatch,
177
+ compressed_screenshot)
178
+ Applitools::EyesLogger.debug 'Done creating MatchWindowData object!'
179
+ match_window_data_obj
180
+ end
181
+
182
+ def match(region, tag, rotation, ignore_mismatch=false)
183
+ Applitools::EyesLogger.debug 'Match called...'
184
+ data = prep_match_data(region, tag, rotation, ignore_mismatch)
185
+ match_result = Applitools::Base::ServerConnector.match_window(session, data)
186
+ Applitools::EyesLogger.debug 'Match done!'
187
+ match_result
188
+ end
189
+ end
190
+ end