eyes_selenium 2.31.0 → 2.32.0

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