eyes_selenium 2.31.0 → 2.32.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 57e3ec595f0c7d68c32885999d4fe662de97f90b
4
- data.tar.gz: b2c665c860fc5356572ab4150b955bb237b12d0a
3
+ metadata.gz: dc57b143e0d3877d92cf53f89e53f58ea521db39
4
+ data.tar.gz: 450452fc879ddc89f77e7089a0d8623b1f8bc81d
5
5
  SHA512:
6
- metadata.gz: a5b40fb1f539daca644a7bda37991908b6563ab1c888a2b63b7efc279b06eaab106e603efdcac46c63faaf58e75a11f2b751de7de6bede377127560562156b1e
7
- data.tar.gz: bf67f4c46ad8ad8d04612d4a99b2cfb01662085d9302ecfffd57742ff583939f0758961f66be5f4ab2addb099b08abfd7738da59db0a36f5bb44790cff801449
6
+ metadata.gz: d77c9ab86b00cb68b722358807c620517fbcfecb62fc102967ea6faafd9723704b19c2212c4f005e27bb965d7217059a05254d68f7cc7daef21676ac761515ab
7
+ data.tar.gz: 33e5a22b2d4bff707f18e19cda2b4949b4aee9c154c90148887cb2b8918ec1659e6d5b8c9da9db9832ef7dd24183a45870659151a7c6a2c100b57c1964d69389
@@ -1,12 +1,5 @@
1
1
  module Applitools::Base
2
- class Dimension
3
- attr_accessor :width, :height
4
-
5
- def initialize(width, height)
6
- @width = width
7
- @height = height
8
- end
9
-
2
+ Dimension = Struct.new(:width, :height) do
10
3
  def to_hash
11
4
  {
12
5
  width: width,
@@ -17,5 +10,27 @@ module Applitools::Base
17
10
  def values
18
11
  [width, height]
19
12
  end
13
+
14
+ def -(other)
15
+ self.width -= other.width
16
+ self.height -= other.height
17
+ self
18
+ end
19
+
20
+ def +(other)
21
+ self.width += other.width
22
+ self.height += other.height
23
+ self
24
+ end
25
+
26
+ def to_s
27
+ values.to_s
28
+ end
29
+
30
+ class << self
31
+ def for(other)
32
+ new(other.width, other.height)
33
+ end
34
+ end
20
35
  end
21
36
  end
@@ -17,12 +17,12 @@ module Applitools::Base
17
17
  @x == other.x && @y == other.y
18
18
  end
19
19
 
20
+ alias eql? ==
21
+
20
22
  def hash
21
23
  @x.hash & @y.hash
22
24
  end
23
25
 
24
- alias_method :eql?, :==
25
-
26
26
  def to_hash(options = {})
27
27
  options[:region] ? { left: left, top: top } : { x: x, y: y }
28
28
  end
@@ -11,9 +11,9 @@ module Applitools::Base::ServerConnector
11
11
  DEFAULT_SERVER_URL = 'https://eyessdk.applitools.com'.freeze
12
12
 
13
13
  SSL_CERT = File.join(File.dirname(File.expand_path(__FILE__)), '../../../certs/cacert.pem').to_s.freeze
14
- DEFAULT_TIMEOUT = 300.freeze
14
+ DEFAULT_TIMEOUT = 300
15
15
 
16
- API_SESSIONS_RUNNING = '/api/sessions/running/'.freeze
16
+ API_SESSIONS_RUNNING = '/api/sessions/running/'.freeze
17
17
 
18
18
  HTTP_STATUS_CODES = {
19
19
  created: 201,
@@ -40,7 +40,6 @@ module Applitools::Base::ServerConnector
40
40
  # Notice that this does not include the screenshot.
41
41
  json_data = Oj.dump(Applitools::Utils.camelcase_hash_keys(data.to_hash)).force_encoding('BINARY')
42
42
  body = [json_data.length].pack('L>') + json_data + data.screenshot
43
-
44
43
  Applitools::EyesLogger.debug 'Sending match data...'
45
44
 
46
45
  res = post(URI.join(endpoint_url, session.id.to_s), content_type: 'application/octet-stream', body: body)
@@ -75,9 +74,9 @@ module Applitools::Base::ServerConnector
75
74
  'Content-Type' => 'application/json'
76
75
  }.freeze
77
76
 
78
- LONG_REQUEST_DELAY = 2.freeze # seconds
79
- MAX_LONG_REQUEST_DELAY = 10.freeze # seconds
80
- LONG_REQUEST_DELAY_MULTIPLICATIVE_INCREASE_FACTOR = 1.5.freeze
77
+ LONG_REQUEST_DELAY = 2 # seconds
78
+ MAX_LONG_REQUEST_DELAY = 10 # seconds
79
+ LONG_REQUEST_DELAY_MULTIPLICATIVE_INCREASE_FACTOR = 1.5
81
80
 
82
81
  [:get, :post, :delete].each do |method|
83
82
  define_method method do |url, options = {}|
@@ -91,7 +90,7 @@ module Applitools::Base::ServerConnector
91
90
 
92
91
  def request(url, method, options = {})
93
92
  Faraday::Connection.new(url, ssl: { ca_file: SSL_CERT }, proxy: @proxy || nil).send(method) do |req|
94
- req.options.timeout = DEFAULT_TIMEOUT
93
+ req.options.timeout = DEFAULT_TIMEOUT
95
94
  req.headers = DEFAULT_HEADERS.merge(options[:headers] || {})
96
95
  req.headers['Content-Type'] = options[:content_type] if options.key?(:content_type)
97
96
  req.params = { apiKey: api_key }.merge(options[:query] || {})
@@ -6,8 +6,8 @@ module Applitools::Base
6
6
  def initialize(results = {})
7
7
  @steps = results.fetch('steps', 0)
8
8
  @matches = results.fetch('matches', 0)
9
- @mismatches = results.fetch('mismatches', 0)
10
- @missing = results.fetch('missing', 0)
9
+ @mismatches = results.fetch('mismatches', 0)
10
+ @missing = results.fetch('missing', 0)
11
11
  @is_new = nil
12
12
  @url = nil
13
13
  end
@@ -15,7 +15,7 @@ module Applitools::Base
15
15
  def passed?
16
16
  !is_new && !(mismatches > 0) && !(missing > 0)
17
17
  end
18
- alias_method :is_passed, :passed?
18
+ alias is_passed passed?
19
19
 
20
20
  def to_s
21
21
  is_new_str = ''
@@ -20,9 +20,9 @@ class Applitools::Eyes
20
20
  exact: 'Exact'
21
21
  }.freeze
22
22
 
23
- DEFAULT_MATCH_TIMEOUT = 2.0.freeze # Seconds
23
+ DEFAULT_MATCH_TIMEOUT = 2.0 # Seconds
24
24
  # noinspection RubyConstantNamingConvention
25
- DEFAULT_WAIT_BEFORE_SCREENSHOTS = 0.1.freeze # Seconds
25
+ DEFAULT_WAIT_BEFORE_SCREENSHOTS = 0.1 # Seconds
26
26
  BASE_AGENT_ID = ('eyes.selenium.ruby/' + Applitools::VERSION).freeze
27
27
 
28
28
  ANDROID = 'Android'.freeze
@@ -74,9 +74,9 @@ class Applitools::Eyes
74
74
  # the default value.
75
75
  attr_reader :app_name, :test_name, :is_open, :viewport_size, :driver
76
76
  attr_accessor :match_timeout, :batch, :host_os, :host_app, :branch_name, :parent_branch_name, :user_inputs,
77
- :save_new_tests, :save_failed_tests, :is_disabled, :server_url, :agent_id, :failure_reports,
78
- :match_level, :baseline_name, :rotation, :force_fullpage_screenshot, :hide_scrollbars,
79
- :use_css_transition, :scale_ratio, :wait_before_screenshots
77
+ :save_new_tests, :save_failed_tests, :is_disabled, :server_url, :agent_id, :failure_reports,
78
+ :match_level, :baseline_name, :rotation, :force_fullpage_screenshot, :hide_scrollbars,
79
+ :use_css_transition, :scale_ratio, :wait_before_screenshots
80
80
 
81
81
  def_delegators 'Applitools::EyesLogger', :log_handler, :log_handler=
82
82
  def_delegators 'Applitools::Base::ServerConnector', :api_key, :api_key=, :server_url, :server_url=, :set_proxy
@@ -143,6 +143,8 @@ class Applitools::Eyes
143
143
  else
144
144
  unless driver.is_a?(Applitools::Selenium::Driver)
145
145
  Applitools::EyesLogger.warn("Unrecognized driver type: (#{driver.class.name})!")
146
+ is_mobile_device = driver.respond_to?(:capabilities) && driver.capabilities['platformName']
147
+ @driver = Applitools::Selenium::Driver.new(self, driver: driver, is_mobile_device: is_mobile_device)
146
148
  end
147
149
  end
148
150
 
@@ -1,14 +1,14 @@
1
1
  module Applitools::Selenium
2
2
  class Browser
3
- JS_GET_USER_AGENT = (<<-JS).freeze
3
+ JS_GET_USER_AGENT = <<-JS.freeze
4
4
  return navigator.userAgent;
5
5
  JS
6
6
 
7
- JS_GET_DEVICE_PIXEL_RATIO = (<<-JS).freeze
7
+ JS_GET_DEVICE_PIXEL_RATIO = <<-JS.freeze
8
8
  return window.devicePixelRatio;
9
9
  JS
10
10
 
11
- JS_GET_PAGE_METRICS = (<<-JS).freeze
11
+ JS_GET_PAGE_METRICS = <<-JS.freeze
12
12
  return {
13
13
  scrollWidth: document.documentElement.scrollWidth,
14
14
  bodyScrollWidth: document.body.scrollWidth,
@@ -19,7 +19,7 @@ module Applitools::Selenium
19
19
  };
20
20
  JS
21
21
 
22
- JS_GET_CURRENT_SCROLL_POSITION = (<<-JS).freeze
22
+ JS_GET_CURRENT_SCROLL_POSITION = <<-JS.freeze
23
23
  return (function() {
24
24
  var doc = document.documentElement;
25
25
  var x = (window.scrollX || window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
@@ -29,15 +29,15 @@ module Applitools::Selenium
29
29
  }());
30
30
  JS
31
31
 
32
- JS_SCROLL_TO = (<<-JS).freeze
32
+ JS_SCROLL_TO = <<-JS.freeze
33
33
  window.scrollTo(%{left}, %{top});
34
34
  JS
35
35
 
36
- JS_GET_CURRENT_TRANSFORM = (<<-JS).freeze
36
+ JS_GET_CURRENT_TRANSFORM = <<-JS.freeze
37
37
  return document.body.style.transform;
38
38
  JS
39
39
 
40
- JS_SET_TRANSFORM = (<<-JS).freeze
40
+ JS_SET_TRANSFORM = <<-JS.freeze
41
41
  return (function() {
42
42
  var originalTransform = document.body.style.transform;
43
43
  document.body.style.transform = '%{transform}';
@@ -45,7 +45,7 @@ module Applitools::Selenium
45
45
  }());
46
46
  JS
47
47
 
48
- JS_SET_OVERFLOW = (<<-JS).freeze
48
+ JS_SET_OVERFLOW = <<-JS.freeze
49
49
  return (function() {
50
50
  var origOF = document.documentElement.style.overflow;
51
51
  document.documentElement.style.overflow = '%{overflow}';
@@ -53,9 +53,9 @@ module Applitools::Selenium
53
53
  }());
54
54
  JS
55
55
 
56
- EPSILON_WIDTH = 12.freeze
57
- MIN_SCREENSHOT_PART_HEIGHT = 10.freeze
58
- MAX_SCROLLBAR_SIZE = 50.freeze
56
+ EPSILON_WIDTH = 12
57
+ MIN_SCREENSHOT_PART_HEIGHT = 10
58
+ MAX_SCROLLBAR_SIZE = 50
59
59
  OVERFLOW_HIDDEN = 'hidden'.freeze
60
60
 
61
61
  def initialize(driver, eyes)
@@ -8,7 +8,7 @@ module Applitools::Selenium
8
8
 
9
9
  include Selenium::WebDriver::DriverExtensions::HasInputDevices
10
10
 
11
- RIGHT_ANGLE = 90.freeze
11
+ RIGHT_ANGLE = 90
12
12
  IOS = 'IOS'.freeze
13
13
  ANDROID = 'ANDROID'.freeze
14
14
  LANDSCAPE = 'LANDSCAPE'.freeze
@@ -40,10 +40,7 @@ module Applitools::Selenium
40
40
  @wait_before_screenshots = 0
41
41
  @eyes = eyes
42
42
  @browser = Applitools::Selenium::Browser.new(self, @eyes)
43
-
44
- unless capabilities.takes_screenshot?
45
- Applitools::EyesLogger.warn '"takes_screenshot" capability not found.'
46
- end
43
+ Applitools::EyesLogger.warn '"takes_screenshot" capability not found.' unless driver.respond_to? :screenshot_as
47
44
  end
48
45
 
49
46
  # Returns:
@@ -81,32 +78,22 @@ module Applitools::Selenium
81
78
  end
82
79
 
83
80
  ## Set the overflow value for document element and return the original overflow value.
84
- def set_overflow(overflow)
81
+ def overflow=(overflow)
85
82
  @browser.set_overflow(overflow)
86
83
  end
87
84
 
88
- # Return a PNG screenshot in the given format as a string
85
+ alias set_overflow overflow=
86
+
87
+ # Return a normalized screenshot.
89
88
  #
90
- # +output_type+:: +Symbol+ The format of the screenshot. Accepted values are +:base64+ and +:png+.
91
89
  # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the image: positive values = clockwise rotation,
92
90
  # negative values = counter-clockwise, 0 = force no rotation, +nil+ = rotate automatically when needed.
93
91
  #
94
- # Returns: +String+ A screenshot in the requested format.
95
- def screenshot_as(output_type, rotation = nil)
92
+ # Returns: +ChunkPng::Image+ A screenshot object, normalized by scale and rotation.
93
+ def get_screenshot(rotation = nil)
96
94
  image = mobile_device? || !@eyes.force_fullpage_screenshot ? visible_screenshot : @browser.fullpage_screenshot
97
-
98
95
  Applitools::Selenium::Driver.normalize_image(self, image, rotation)
99
-
100
- case output_type
101
- when :base64
102
- image = Applitools::Utils::ImageUtils.base64_from_png_image(image)
103
- when :png
104
- image = Applitools::Utils::ImageUtils.bytes_from_png_image(image)
105
- else
106
- raise Applitools::EyesError.new("Unsupported screenshot output type: #{output_type}")
107
- end
108
-
109
- image.force_encoding('BINARY')
96
+ image
110
97
  end
111
98
 
112
99
  def visible_screenshot
@@ -173,41 +160,43 @@ module Applitools::Selenium
173
160
  end
174
161
  end
175
162
 
176
- def self.normalize_image(driver, image, rotation)
177
- normalize_rotation(driver, image, rotation)
178
- normalize_width(driver, image)
179
- end
180
-
181
- # Rotates the image as necessary. The rotation is either manually forced by passing a value in
182
- # the +rotation+ parameter, or automatically inferred if the +rotation+ parameter is +nil+.
183
- #
184
- # +driver+:: +Applitools::Selenium::Driver+ The driver which produced the screenshot.
185
- # +image+:: +ChunkyPNG::Canvas+ The image to normalize.
186
- # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the image: positive values = clockwise rotation,
187
- # negative values = counter-clockwise, 0 = force no rotation, +nil+ = rotate automatically when needed.
188
- def self.normalize_rotation(driver, image, rotation)
189
- return if rotation == 0
163
+ class << self
164
+ def normalize_image(driver, image, rotation)
165
+ normalize_rotation(driver, image, rotation)
166
+ normalize_width(driver, image)
167
+ end
190
168
 
191
- num_quadrants = 0
192
- if !rotation.nil?
193
- if rotation % RIGHT_ANGLE != 0
194
- raise Applitools::EyesError.new('Currently only quadrant rotations are supported. Current rotation: '\
169
+ # Rotates the image as necessary. The rotation is either manually forced by passing a value in
170
+ # the +rotation+ parameter, or automatically inferred if the +rotation+ parameter is +nil+.
171
+ #
172
+ # +driver+:: +Applitools::Selenium::Driver+ The driver which produced the screenshot.
173
+ # +image+:: +ChunkyPNG::Canvas+ The image to normalize.
174
+ # +rotation+:: +Integer+|+nil+ The degrees by which to rotate the image: positive values = clockwise rotation,
175
+ # negative values = counter-clockwise, 0 = force no rotation, +nil+ = rotate automatically when needed.
176
+ def normalize_rotation(driver, image, rotation)
177
+ return if rotation == 0
178
+
179
+ num_quadrants = 0
180
+ if !rotation.nil?
181
+ if rotation % RIGHT_ANGLE != 0
182
+ raise Applitools::EyesError.new('Currently only quadrant rotations are supported. Current rotation: '\
195
183
  "#{rotation}")
184
+ end
185
+ num_quadrants = (rotation / RIGHT_ANGLE).to_i
186
+ elsif rotation.nil? && driver.mobile_device? && driver.landscape_orientation? && image.height > image.width
187
+ # For Android, we need to rotate images to the right, and for iOS to the left.
188
+ num_quadrants = driver.android? ? 1 : -1
196
189
  end
197
- num_quadrants = (rotation / RIGHT_ANGLE).to_i
198
- elsif rotation.nil? && driver.mobile_device? && driver.landscape_orientation? && image.height > image.width
199
- # For Android, we need to rotate images to the right, and for iOS to the left.
200
- num_quadrants = driver.android? ? 1 : -1
201
- end
202
190
 
203
- Applitools::Utils::ImageUtils.quadrant_rotate!(image, num_quadrants)
204
- end
191
+ Applitools::Utils::ImageUtils.quadrant_rotate!(image, num_quadrants)
192
+ end
205
193
 
206
- def self.normalize_width(driver, image)
207
- return if driver.mobile_device?
194
+ def normalize_width(driver, image)
195
+ return if driver.mobile_device?
208
196
 
209
- normalization_factor = driver.browser.image_normalization_factor(image)
210
- Applitools::Utils::ImageUtils.scale!(image, normalization_factor) unless normalization_factor == 1
197
+ normalization_factor = driver.browser.image_normalization_factor(image)
198
+ Applitools::Utils::ImageUtils.scale!(image, normalization_factor) unless normalization_factor == 1
199
+ end
211
200
  end
212
201
  end
213
202
  end
@@ -27,7 +27,7 @@ module Applitools::Selenium
27
27
  def ==(other)
28
28
  other.is_a?(web_element.class) && web_element == other
29
29
  end
30
- alias_method :eql?, :==
30
+ alias eql? ==
31
31
 
32
32
  def send_keys(*args)
33
33
  current_control = region
@@ -37,7 +37,7 @@ module Applitools::Selenium
37
37
 
38
38
  web_element.send_keys(*args)
39
39
  end
40
- alias_method :send_key, :send_keys
40
+ alias send_key send_keys
41
41
 
42
42
  def region
43
43
  point = location
@@ -2,7 +2,7 @@ module Applitools::Selenium
2
2
  class MatchWindowData
3
3
  attr_reader :user_inputs, :app_output, :tag, :ignore_mismatch, :screenshot
4
4
 
5
- def initialize(app_output, user_inputs = [], tag, ignore_mismatch, screenshot)
5
+ def initialize(app_output, tag, ignore_mismatch, screenshot, user_inputs = [])
6
6
  @user_inputs = user_inputs
7
7
  @app_output = app_output
8
8
  @tag = tag
@@ -20,5 +20,9 @@ module Applitools::Selenium
20
20
  ignore_mismatch: @ignore_mismatch
21
21
  }
22
22
  end
23
+
24
+ def screenshot
25
+ @screenshot.to_blob.force_encoding('BINARY')
26
+ end
23
27
  end
24
28
  end
@@ -2,7 +2,7 @@ require 'base64'
2
2
 
3
3
  module Applitools::Selenium
4
4
  class MatchWindowTask
5
- MATCH_INTERVAL = 0.5.freeze
5
+ MATCH_INTERVAL = 0.5
6
6
  AppOuptut = Struct.new(:title, :screenshot64)
7
7
 
8
8
  attr_reader :eyes, :session, :driver, :default_retry_timeout, :last_checked_window,
@@ -45,7 +45,7 @@ module Applitools::Selenium
45
45
  region
46
46
  end
47
47
  driver.clear_user_inputs
48
-
48
+ GC.start
49
49
  res
50
50
  end
51
51
 
@@ -65,9 +65,8 @@ module Applitools::Selenium
65
65
  # We intentionally take the first screenshot before starting the timer, to allow the page just a tad more time to
66
66
  # stabilize.
67
67
  Applitools::EyesLogger.debug 'Matching with intervals...'
68
- data = prep_match_data(region, tag, rotation, true)
69
68
  start = Time.now
70
- as_expected = Applitools::Base::ServerConnector.match_window(session, data)
69
+ as_expected = match(region, tag, rotation, true)
71
70
  Applitools::EyesLogger.debug "First call result: #{as_expected}"
72
71
  return true if as_expected
73
72
  Applitools::EyesLogger.debug "Not as expected, performing retry (total timeout #{retry_timeout})"
@@ -103,9 +102,8 @@ module Applitools::Selenium
103
102
  Applitools::EyesLogger.debug 'Preparing match data...'
104
103
  title = eyes.title
105
104
  Applitools::EyesLogger.debug 'Getting screenshot...'
106
- current_screenshot_encoded = driver.screenshot_as(:png, rotation)
107
105
  Applitools::EyesLogger.debug 'Done! Creating image object from PNG...'
108
- @current_screenshot = ChunkyPNG::Image.from_blob(current_screenshot_encoded)
106
+ @current_screenshot = Applitools::Utils::ImageUtils::Screenshot.new(driver.get_screenshot(rotation))
109
107
  Applitools::EyesLogger.debug 'Done!'
110
108
  # If a region was defined, we refer to the sub-image defined by the region.
111
109
  unless region.empty?
@@ -116,18 +114,14 @@ module Applitools::Selenium
116
114
  Applitools::EyesLogger.debug 'Done! Cropping region...'
117
115
  @current_screenshot.crop!(clipped_region.left, clipped_region.top, clipped_region.width, clipped_region.height)
118
116
  Applitools::EyesLogger.debug 'Done! Creating cropped image object...'
119
- current_screenshot_encoded = @current_screenshot.to_blob.force_encoding('BINARY')
120
117
  Applitools::EyesLogger.debug 'Done!'
121
118
  end
122
119
 
123
- # FIXME re-Enable screenshot compression after handling memory leaks.
120
+ # FIXME: re-Enable screenshot compression after handling memory leaks.
124
121
  # Applitools::EyesLogger.debug 'Compressing screenshot...'
125
122
  # compressed_screenshot = Applitools::Utils::ImageDeltaCompressor.compress_by_raw_blocks(@current_screenshot,
126
123
  # current_screenshot_encoded, last_checked_window)
127
124
 
128
- # FIXME Remove the following line after compression is re-enabled.
129
- compressed_screenshot = current_screenshot_encoded
130
-
131
125
  Applitools::EyesLogger.debug 'Done! Creating AppOuptut...'
132
126
  app_output = AppOuptut.new(title, nil)
133
127
  user_inputs = []
@@ -186,8 +180,8 @@ module Applitools::Selenium
186
180
  Applitools::EyesLogger.info 'Triggers ignored: no previous window checked'
187
181
  end
188
182
  Applitools::EyesLogger.debug 'Creating MatchWindowData object..'
189
- match_window_data_obj = Applitools::Selenium::MatchWindowData.new(app_output, user_inputs, tag, ignore_mismatch,
190
- compressed_screenshot)
183
+ match_window_data_obj = Applitools::Selenium::MatchWindowData.new(app_output, tag, ignore_mismatch,
184
+ @current_screenshot, user_inputs)
191
185
  Applitools::EyesLogger.debug 'Done creating MatchWindowData object!'
192
186
  match_window_data_obj
193
187
  end
@@ -1,78 +1,57 @@
1
1
  module Applitools::Selenium
2
2
  class ViewportSize
3
- JS_GET_VIEWPORT_HEIGHT = (<<-JS).freeze
4
- return (function() {
5
- var height = undefined;
6
- if (window.innerHeight) {
7
- height = window.innerHeight;
8
- }
9
- else if (document.documentElement && document.documentElement.clientHeight) {
10
- height = document.documentElement.clientHeight;
11
- } else {
12
- var b = document.getElementsByTagName("body")[0];
13
- if (b.clientHeight) {
14
- height = b.clientHeight;
15
- }
16
- }
17
-
18
- return height;
19
- }());
3
+ JS_GET_VIEWPORT_SIZE = <<-JS.freeze
4
+ return (function() {
5
+ var height = undefined;
6
+ var width = undefined;
7
+ if (window.innerHeight) {height = window.innerHeight;}
8
+ else if (document.documentElement && document.documentElement.clientHeight)
9
+ {height = document.documentElement.clientHeight;}
10
+ else { var b = document.getElementsByTagName('body')[0];
11
+ if (b.clientHeight) {height = b.clientHeight;}
12
+ };
13
+
14
+ if (window.innerWidth) {width = window.innerWidth;}
15
+ else if (document.documentElement && document.documentElement.clientWidth)
16
+ {width = document.documentElement.clientWidth;}
17
+ else { var b = document.getElementsByTagName('body')[0];
18
+ if (b.clientWidth) {width = b.clientWidth;}
19
+ };
20
+ return [width, height];
21
+ }());
20
22
  JS
21
23
 
22
- JS_GET_VIEWPORT_WIDTH = (<<-JS).freeze
23
- return (function() {
24
- var width = undefined;
25
- if (window.innerWidth) {
26
- width = window.innerWidth
27
- } else if (document.documentElement && document.documentElement.clientWidth) {
28
- width = document.documentElement.clientWidth;
29
- } else {
30
- var b = document.getElementsByTagName("body")[0];
31
- if (b.clientWidth) {
32
- width = b.clientWidth;
33
- }
34
- }
35
-
36
- return width;
37
- }());
38
- JS
39
-
40
- VERIFY_SLEEP_PERIOD = 1.freeze
41
- VERIFY_RETRIES = 3.freeze
24
+ VERIFY_SLEEP_PERIOD = 1
25
+ VERIFY_RETRIES = 3
26
+ BROWSER_SIZE_CALCULATION_RETRIES = 2
42
27
 
43
28
  def initialize(driver, dimension = nil)
44
29
  @driver = driver
45
- @dimension = dimension
30
+ @dimension = setup_dimension(dimension)
46
31
  end
47
32
 
48
- def extract_viewport_width
49
- @driver.execute_script(JS_GET_VIEWPORT_WIDTH)
33
+ def size
34
+ @dimension
50
35
  end
51
36
 
52
- def extract_viewport_height
53
- @driver.execute_script(JS_GET_VIEWPORT_HEIGHT)
54
- end
55
-
56
- def extract_viewport_from_browser!
37
+ def extract_viewport_size!
57
38
  @dimension = extract_viewport_from_browser
58
39
  end
59
40
 
60
- def extract_viewport_from_browser
41
+ alias extract_viewport_from_browser! extract_viewport_size!
42
+
43
+ def extract_viewport_size
61
44
  width = nil
62
45
  height = nil
63
46
  begin
64
- width = extract_viewport_width
65
- height = extract_viewport_height
47
+ width, height = @driver.execute_script(JS_GET_VIEWPORT_SIZE)
66
48
  rescue => e
67
49
  Applitools::EyesLogger.error "Failed extracting viewport size using JavaScript: (#{e.message})"
68
50
  end
69
-
70
51
  if width.nil? || height.nil?
71
52
  Applitools::EyesLogger.info 'Using window size as viewport size.'
72
53
 
73
- width, height = *browser_size.values
74
- width = width.ceil
75
- height = height.ceil
54
+ width, height = *browser_size.values.map(&:ceil)
76
55
 
77
56
  if @driver.landscape_orientation? && height > width
78
57
  width, height = height, width
@@ -82,56 +61,99 @@ module Applitools::Selenium
82
61
  Applitools::Base::Dimension.new(width, height)
83
62
  end
84
63
 
85
- alias_method :viewport_size, :extract_viewport_from_browser
64
+ alias viewport_size extract_viewport_size
65
+ alias extract_viewport_from_browser extract_viewport_size
86
66
 
87
67
  def set
88
- if @dimension.is_a?(Hash) && @dimension.key?(:width) && @dimension.key?(:height)
89
- # If @dimension is hash of width/height, we convert it to a struct with width/height properties.
90
- @dimension = Applitools::Base::Dimension.new(@dimension[:width], @dimension[:height])
91
- elsif !@dimension.respond_to?(:width) || !@dimension.respond_to?(:height)
92
- raise ArgumentError, "expected #{@dimension.inspect}:#{@dimension.class} to respond to #width and #height, or "\
93
- 'be a hash with these keys.'
68
+ Applitools::EyesLogger.debug "Set viewport size #{@dimension}"
69
+ # Before resizing the window, set its position to the upper left corner (otherwise, there might not be enough
70
+ # "space" below/next to it and the operation won't be successful).
71
+ browser_to_upper_left_corner
72
+
73
+ browser_size_calculation_count = 0
74
+ while browser_size_calculation_count < BROWSER_SIZE_CALCULATION_RETRIES
75
+ raise Applitools::TestFailedError.new 'Failed to set browser size!' \
76
+ " (current size: #{browser_size})" unless resize_attempt
77
+ browser_size_calculation_count += 1
78
+ if viewport_size == size
79
+ Applitools::EyesLogger.debug "Actual viewport size #{viewport_size}"
80
+ return
81
+ end
94
82
  end
83
+ raise Applitools::TestFailedError.new 'Failed to set viewport size'
84
+ end
95
85
 
96
- resize_browser(@dimension)
97
- verify_size(:browser_size)
98
-
99
- current_viewport_size = extract_viewport_from_browser
86
+ def browser_size
87
+ Applitools::Base::Dimension.for @driver.manage.window.size
88
+ end
100
89
 
101
- resize_browser(Applitools::Base::Dimension.new((2 * browser_size.width) - current_viewport_size.width,
102
- (2 * browser_size.height) - current_viewport_size.height))
103
- verify_size(:viewport_size)
90
+ def resize_browser(other)
91
+ @driver.manage.window.size = other
104
92
  end
105
93
 
106
- def verify_size(to_verify)
107
- current_size = nil
94
+ def browser_to_upper_left_corner
95
+ @driver.manage.window.position = Selenium::WebDriver::Point.new(0, 0)
96
+ rescue Selenium::WebDriver::Error::UnsupportedOperationError => e
97
+ Applitools::EyesLogger.warn "Unsupported operation error: (#{e.message})"
98
+ end
108
99
 
109
- VERIFY_RETRIES.times do
110
- sleep(VERIFY_SLEEP_PERIOD)
111
- current_size = send(to_verify)
100
+ def to_hash
101
+ @dimension.to_hash
102
+ end
112
103
 
113
- return if current_size.values == @dimension.values
114
- end
104
+ private
115
105
 
116
- err_msg = "Failed setting #{to_verify} to #{@dimension.values} (current size: #{current_size.values})"
106
+ def setup_dimension(dimension)
107
+ return dimension if dimension.is_a? ::Selenium::WebDriver::Dimension
108
+ return Applitools::Base::Dimension.for(dimension) if dimension.respond_to?(:width) &
109
+ dimension.respond_to?(:height)
110
+ return Applitools::Base::Dimension.new(
111
+ dimension[:width],
112
+ dimension[:height]
113
+ ) if dimension.is_a?(Hash) && (dimension.keys & [:width, :height]).size == 2
117
114
 
118
- Applitools::EyesLogger.error(err_msg)
119
- raise Applitools::TestFailedError.new(err_msg)
115
+ raise ArgumentError,
116
+ "expected #{@dimension.inspect}:#{@dimension.class} to respond to #width and #height," \
117
+ ' or be a hash with these keys.'
120
118
  end
121
119
 
122
- def browser_size
123
- @driver.manage.window.size
120
+ # Calculates a necessary browser size to get a requested viewport size,
121
+ # tries to resize browser, yields a block (which should check if an attempt was successful) before each iteration.
122
+ # If the block returns true, stop trying and returns true (resize was successful)
123
+ # Otherwise, returns false after VERIFY_RETRIES iterations
124
+
125
+ def resize_attempt
126
+ actual_viewport_size = extract_viewport_size
127
+ Applitools::EyesLogger.debug "Actual viewport size #{actual_viewport_size}"
128
+ required_browser_size = ViewportSize.required_browser_size actual_browser_size: browser_size,
129
+ actual_viewport_size: actual_viewport_size, required_viewport_size: size
130
+
131
+ retries_left = VERIFY_RETRIES
132
+
133
+ until retries_left == 0
134
+ return true if browser_size == required_browser_size
135
+ Applitools::EyesLogger.debug "Trying to set browser size to #{required_browser_size}"
136
+ resize_browser required_browser_size
137
+ sleep VERIFY_SLEEP_PERIOD
138
+ Applitools::EyesLogger.debug "Required browser size #{required_browser_size}, " \
139
+ "Current browser size #{browser_size}"
140
+ retries_left -= 1
141
+ end
142
+ false
124
143
  end
125
144
 
126
- def resize_browser(other)
127
- # Before resizing the window, set its position to the upper left corner (otherwise, there might not be enough
128
- # "space" below/next to it and the operation won't be successful).
129
- @driver.manage.window.position = Selenium::WebDriver::Point.new(0, 0)
130
- @driver.manage.window.size = other
131
- end
145
+ class << self
146
+ def required_browser_size(options)
147
+ unless options[:actual_browser_size].is_a?(Applitools::Base::Dimension) &&
148
+ options[:actual_viewport_size].is_a?(Applitools::Base::Dimension) &&
149
+ options[:required_viewport_size].is_a?(Applitools::Base::Dimension)
132
150
 
133
- def to_hash
134
- @dimension.to_hash
151
+ raise ArgumentError,
152
+ "expected #{options.inspect}:#{options.class} to be a hash with keys" \
153
+ ' :actual_browser_size, :actual_viewport_size, :required_viewport_size'
154
+ end
155
+ options[:actual_browser_size] - options[:actual_viewport_size] + options[:required_viewport_size]
156
+ end
135
157
  end
136
158
  end
137
159
  end
@@ -4,7 +4,7 @@ module Applitools::Utils
4
4
  module ImageDeltaCompressor
5
5
  extend self
6
6
 
7
- BLOCK_SIZE = 10.freeze
7
+ BLOCK_SIZE = 10
8
8
 
9
9
  # Compresses the target image based on the source image.
10
10
  #
@@ -84,7 +84,7 @@ module Applitools::Utils
84
84
  private
85
85
 
86
86
  PREAMBLE = 'applitools'.freeze
87
- FORMAT_RAW_BLOCKS = 3.freeze
87
+ FORMAT_RAW_BLOCKS = 3
88
88
 
89
89
  Dimension = Struct.new(:width, :height)
90
90
  CompareAndCopyBlockChannelDataResult = Struct.new(:identical, :channel_bytes)
@@ -115,7 +115,7 @@ module Applitools::Utils
115
115
  # Returns +CompareAndCopyBlockChannelDataResult+ object containing a flag saying whether the blocks are identical
116
116
  # and a copy of the target block's bytes.
117
117
  def compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, pixel_length, block_size,
118
- block_column, block_row, channel)
118
+ block_column, block_row, channel)
119
119
  identical = true
120
120
 
121
121
  actual_block_size = get_actual_block_size(image_size, block_size, block_column, block_row)
@@ -1,8 +1,9 @@
1
1
  require 'oily_png'
2
2
  require 'base64'
3
+ require 'tempfile'
3
4
 
4
5
  module Applitools::Utils
5
- QUADRANTS_COUNT = 4.freeze
6
+ QUADRANTS_COUNT = 4
6
7
 
7
8
  module ImageUtils
8
9
  extend self
@@ -91,6 +92,39 @@ module Applitools::Utils
91
92
  end
92
93
  end
93
94
 
95
+ class Screenshot < Delegator
96
+ attr_accessor :width, :height, :file
97
+
98
+ def initialize(image)
99
+ @file = Tempfile.new('applitools')
100
+ image.save file
101
+ @width = image.width
102
+ @height = image.height
103
+ end
104
+
105
+ def __getobj__
106
+ restore
107
+ end
108
+
109
+ def __setobj__(obj)
110
+ obj.save file
111
+ end
112
+
113
+ def method_missing(method, *args, &block)
114
+ if method =~ /^.+!$/
115
+ __setobj__ super
116
+ else
117
+ super
118
+ end
119
+ end
120
+
121
+ private
122
+
123
+ def restore
124
+ ChunkyPNG::Image.from_file(file)
125
+ end
126
+ end
127
+
94
128
  include Applitools::MethodTracer
95
129
  end
96
130
  end
@@ -1,3 +1,3 @@
1
1
  module Applitools
2
- VERSION = '2.31.0'.freeze
2
+ VERSION = '2.32.0'.freeze
3
3
  end
@@ -3,6 +3,9 @@ require_relative '../lib/eyes_selenium'
3
3
  require 'logger'
4
4
  require 'watir-webdriver'
5
5
 
6
+ require 'openssl'
7
+ OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
8
+
6
9
  eyes = Applitools::Eyes.new
7
10
  eyes.api_key = ENV['APPLITOOLS_API_KEY']
8
11
  eyes.log_handler = Logger.new(STDOUT)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eyes_selenium
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.31.0
4
+ version: 2.32.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Applitools Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-19 00:00:00.000000000 Z
11
+ date: 2016-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: selenium-webdriver