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 +4 -4
- data/lib/applitools/base/dimension.rb +23 -8
- data/lib/applitools/base/point.rb +2 -2
- data/lib/applitools/base/server_connector.rb +6 -7
- data/lib/applitools/base/test_results.rb +3 -3
- data/lib/applitools/eyes.rb +7 -5
- data/lib/applitools/selenium/browser.rb +11 -11
- data/lib/applitools/selenium/driver.rb +40 -51
- data/lib/applitools/selenium/element.rb +2 -2
- data/lib/applitools/selenium/match_window_data.rb +5 -1
- data/lib/applitools/selenium/match_window_task.rb +7 -13
- data/lib/applitools/selenium/viewport_size.rb +108 -86
- data/lib/applitools/utils/image_delta_compressor.rb +3 -3
- data/lib/applitools/utils/image_utils.rb +35 -1
- data/lib/applitools/version.rb +1 -1
- data/test/watir_test_script.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc57b143e0d3877d92cf53f89e53f58ea521db39
|
4
|
+
data.tar.gz: 450452fc879ddc89f77e7089a0d8623b1f8bc81d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d77c9ab86b00cb68b722358807c620517fbcfecb62fc102967ea6faafd9723704b19c2212c4f005e27bb965d7217059a05254d68f7cc7daef21676ac761515ab
|
7
|
+
data.tar.gz: 33e5a22b2d4bff707f18e19cda2b4949b4aee9c154c90148887cb2b8918ec1659e6d5b8c9da9db9832ef7dd24183a45870659151a7c6a2c100b57c1964d69389
|
@@ -1,12 +1,5 @@
|
|
1
1
|
module Applitools::Base
|
2
|
-
|
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
|
14
|
+
DEFAULT_TIMEOUT = 300
|
15
15
|
|
16
|
-
API_SESSIONS_RUNNING =
|
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
|
79
|
-
MAX_LONG_REQUEST_DELAY = 10
|
80
|
-
LONG_REQUEST_DELAY_MULTIPLICATIVE_INCREASE_FACTOR = 1.5
|
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
|
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 =
|
10
|
-
@missing =
|
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
|
-
|
18
|
+
alias is_passed passed?
|
19
19
|
|
20
20
|
def to_s
|
21
21
|
is_new_str = ''
|
data/lib/applitools/eyes.rb
CHANGED
@@ -20,9 +20,9 @@ class Applitools::Eyes
|
|
20
20
|
exact: 'Exact'
|
21
21
|
}.freeze
|
22
22
|
|
23
|
-
DEFAULT_MATCH_TIMEOUT = 2.0
|
23
|
+
DEFAULT_MATCH_TIMEOUT = 2.0 # Seconds
|
24
24
|
# noinspection RubyConstantNamingConvention
|
25
|
-
DEFAULT_WAIT_BEFORE_SCREENSHOTS = 0.1
|
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
|
-
|
78
|
-
|
79
|
-
|
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 =
|
3
|
+
JS_GET_USER_AGENT = <<-JS.freeze
|
4
4
|
return navigator.userAgent;
|
5
5
|
JS
|
6
6
|
|
7
|
-
JS_GET_DEVICE_PIXEL_RATIO =
|
7
|
+
JS_GET_DEVICE_PIXEL_RATIO = <<-JS.freeze
|
8
8
|
return window.devicePixelRatio;
|
9
9
|
JS
|
10
10
|
|
11
|
-
JS_GET_PAGE_METRICS =
|
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 =
|
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 =
|
32
|
+
JS_SCROLL_TO = <<-JS.freeze
|
33
33
|
window.scrollTo(%{left}, %{top});
|
34
34
|
JS
|
35
35
|
|
36
|
-
JS_GET_CURRENT_TRANSFORM =
|
36
|
+
JS_GET_CURRENT_TRANSFORM = <<-JS.freeze
|
37
37
|
return document.body.style.transform;
|
38
38
|
JS
|
39
39
|
|
40
|
-
JS_SET_TRANSFORM =
|
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 =
|
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
|
57
|
-
MIN_SCREENSHOT_PART_HEIGHT = 10
|
58
|
-
MAX_SCROLLBAR_SIZE = 50
|
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
|
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
|
81
|
+
def overflow=(overflow)
|
85
82
|
@browser.set_overflow(overflow)
|
86
83
|
end
|
87
84
|
|
88
|
-
|
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: +
|
95
|
-
def
|
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
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
192
|
-
if
|
193
|
-
|
194
|
-
|
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
|
-
|
204
|
-
|
191
|
+
Applitools::Utils::ImageUtils.quadrant_rotate!(image, num_quadrants)
|
192
|
+
end
|
205
193
|
|
206
|
-
|
207
|
-
|
194
|
+
def normalize_width(driver, image)
|
195
|
+
return if driver.mobile_device?
|
208
196
|
|
209
|
-
|
210
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
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 =
|
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 =
|
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,
|
190
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
49
|
-
@
|
33
|
+
def size
|
34
|
+
@dimension
|
50
35
|
end
|
51
36
|
|
52
|
-
def
|
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
|
-
|
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
|
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
|
-
|
64
|
+
alias viewport_size extract_viewport_size
|
65
|
+
alias extract_viewport_from_browser extract_viewport_size
|
86
66
|
|
87
67
|
def set
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
97
|
-
|
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
|
-
|
102
|
-
|
103
|
-
verify_size(:viewport_size)
|
90
|
+
def resize_browser(other)
|
91
|
+
@driver.manage.window.size = other
|
104
92
|
end
|
105
93
|
|
106
|
-
def
|
107
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
100
|
+
def to_hash
|
101
|
+
@dimension.to_hash
|
102
|
+
end
|
112
103
|
|
113
|
-
|
114
|
-
end
|
104
|
+
private
|
115
105
|
|
116
|
-
|
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
|
-
|
119
|
-
|
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
|
-
|
123
|
-
|
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
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
134
|
-
|
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
|
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
|
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
|
-
|
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
|
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
|
data/lib/applitools/version.rb
CHANGED
data/test/watir_test_script.rb
CHANGED
@@ -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.
|
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-
|
11
|
+
date: 2016-07-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: selenium-webdriver
|