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 +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
|