eyes_core 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/ext/eyes_core/extconf.rb +3 -0
  3. data/ext/eyes_core/eyes_core.c +80 -0
  4. data/ext/eyes_core/eyes_core.h +24 -0
  5. data/lib/applitools/capybara.rb +8 -0
  6. data/lib/applitools/chunky_png/resampling.rb +148 -0
  7. data/lib/applitools/chunky_png_patch.rb +8 -0
  8. data/lib/applitools/connectivity/proxy.rb +3 -0
  9. data/lib/applitools/connectivity/server_connector.rb +118 -0
  10. data/lib/applitools/core/app_environment.rb +29 -0
  11. data/lib/applitools/core/app_output.rb +17 -0
  12. data/lib/applitools/core/app_output_with_screenshot.rb +22 -0
  13. data/lib/applitools/core/argument_guard.rb +35 -0
  14. data/lib/applitools/core/batch_info.rb +18 -0
  15. data/lib/applitools/core/eyes_base.rb +463 -0
  16. data/lib/applitools/core/eyes_screenshot.rb +35 -0
  17. data/lib/applitools/core/fixed_cut_provider.rb +61 -0
  18. data/lib/applitools/core/fixed_scale_provider.rb +14 -0
  19. data/lib/applitools/core/helpers.rb +18 -0
  20. data/lib/applitools/core/location.rb +84 -0
  21. data/lib/applitools/core/match_result.rb +16 -0
  22. data/lib/applitools/core/match_results.rb +9 -0
  23. data/lib/applitools/core/match_window_data.rb +34 -0
  24. data/lib/applitools/core/match_window_task.rb +86 -0
  25. data/lib/applitools/core/mouse_trigger.rb +39 -0
  26. data/lib/applitools/core/rectangle_size.rb +46 -0
  27. data/lib/applitools/core/region.rb +180 -0
  28. data/lib/applitools/core/screenshot.rb +49 -0
  29. data/lib/applitools/core/session.rb +15 -0
  30. data/lib/applitools/core/session_start_info.rb +33 -0
  31. data/lib/applitools/core/test_results.rb +55 -0
  32. data/lib/applitools/core/text_trigger.rb +24 -0
  33. data/lib/applitools/core/trigger.rb +8 -0
  34. data/lib/applitools/extensions.rb +18 -0
  35. data/lib/applitools/eyes_logger.rb +45 -0
  36. data/lib/applitools/images/eyes.rb +204 -0
  37. data/lib/applitools/images/eyes_images_screenshot.rb +102 -0
  38. data/lib/applitools/method_tracer.rb +23 -0
  39. data/lib/applitools/sauce.rb +2 -0
  40. data/lib/applitools/utils/eyes_selenium_utils.rb +348 -0
  41. data/lib/applitools/utils/image_delta_compressor.rb +146 -0
  42. data/lib/applitools/utils/image_utils.rb +146 -0
  43. data/lib/applitools/utils/utils.rb +68 -0
  44. data/lib/applitools/version.rb +3 -0
  45. data/lib/eyes_core.rb +70 -0
  46. metadata +273 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b1df68d352f994ee3111a658286083efd568c76
4
+ data.tar.gz: 4b7734495c6f07a2b63ee190f18cac3a064a23be
5
+ SHA512:
6
+ metadata.gz: 3e568a25ce2eeeddb8ca7fcae76dbe037d963b30e738ce595b6a04a2681ec81aa34fc4fdbbceeddefb66c6ad429c0991fd416a63f1cc78d569c8fcae260a2738
7
+ data.tar.gz: 07adaf7c6c520f1d762d7ab42a1a3b18f14e4324e32c69fddb346ec6804dc5cde9d1e0a776c9a953b7a0ebbf4f09d2cc87152d7bc6a21e1b94c3186c527af469
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+ $CFLAGS << ' -Wall'
3
+ create_makefile('eyes_core/eyes_core')
@@ -0,0 +1,80 @@
1
+ #include "eyes_core.h"
2
+
3
+ void Init_eyes_core() {
4
+ VALUE Applitools = rb_define_module("Applitools");
5
+ VALUE Resampling = rb_define_module_under(Applitools, "ResamplingFast");
6
+ rb_define_method(Resampling, "interpolate_cubic", c_interpolate_cubic, 1);
7
+ rb_define_method(Resampling, "merge_pixels", c_merge_pixels, 1);
8
+ };
9
+
10
+
11
+ VALUE c_interpolate_cubic(VALUE self, VALUE data) {
12
+ double t = NUM2DBL(rb_ary_entry(data, 1));
13
+ BYTE new_r, new_g, new_b, new_a;
14
+ VALUE p0, p1, p2, p3;
15
+
16
+ p0 = NUM2UINT(rb_ary_entry(data, 2));
17
+ p1 = NUM2UINT(rb_ary_entry(data, 3));
18
+ p2 = NUM2UINT(rb_ary_entry(data, 4));
19
+ p3 = NUM2UINT(rb_ary_entry(data, 5));
20
+
21
+ new_r = interpolate_char(t, R_BYTE(p0), R_BYTE(p1), R_BYTE(p2), R_BYTE(p3));
22
+ new_g = interpolate_char(t, G_BYTE(p0), G_BYTE(p1), G_BYTE(p2), G_BYTE(p3));
23
+ new_b = interpolate_char(t, B_BYTE(p0), B_BYTE(p1), B_BYTE(p2), B_BYTE(p3));
24
+ new_a = interpolate_char(t, A_BYTE(p0), A_BYTE(p1), A_BYTE(p2), A_BYTE(p3));
25
+
26
+ return UINT2NUM(BUILD_PIXEL(new_r, new_g, new_b, new_a));
27
+ };
28
+
29
+ BYTE interpolate_char(double t, BYTE c0, BYTE c1, BYTE c2, BYTE c3) {
30
+ double a, b, c, d, res;
31
+ a = - 0.5 * c0 + 1.5 * c1 - 1.5 * c2 + 0.5 * c3;
32
+ b = c0 - 2.5 * c1 + 2 * c2 - 0.5 * c3;
33
+ c = 0.5 * c2 - 0.5 * c0;
34
+ d = c1;
35
+ res = a * t * t * t + b * t * t + c * t + d + 0.5;
36
+ if(res < 0) {
37
+ res = 0;
38
+ } else if(res > 255) {
39
+ res = 255;
40
+ };
41
+ return (BYTE)(res);
42
+ };
43
+
44
+ VALUE c_merge_pixels(VALUE self, VALUE pixels) {
45
+ unsigned int i, size, real_colors, acum_r, acum_g, acum_b, acum_a;
46
+ BYTE new_r, new_g, new_b, new_a;
47
+ PIXEL pix;
48
+
49
+ acum_r = 0;
50
+ acum_g = 0;
51
+ acum_b = 0;
52
+ acum_a = 0;
53
+
54
+ new_r = 0;
55
+ new_g = 0;
56
+ new_b = 0;
57
+ new_a = 0;
58
+
59
+ size = NUM2UINT(rb_funcall(pixels, rb_intern("size"), 0, Qnil)) - 1;
60
+ real_colors = 0;
61
+
62
+ for(i=1; i < size; i++) {
63
+ pix = NUM2UINT(rb_ary_entry(pixels, i));
64
+ if(A_BYTE(pix) != 0) {
65
+ acum_r += R_BYTE(pix);
66
+ acum_g += G_BYTE(pix);
67
+ acum_b += B_BYTE(pix);
68
+ acum_a += A_BYTE(pix);
69
+ real_colors += 1;
70
+ }
71
+ }
72
+
73
+ if(real_colors > 0) {
74
+ new_r = (BYTE)(acum_r/real_colors + 0.5);
75
+ new_g = (BYTE)(acum_g/real_colors + 0.5);
76
+ new_b = (BYTE)(acum_b/real_colors + 0.5);
77
+ }
78
+ new_a = (BYTE)(acum_a/(size - 1) + 0.5);
79
+ return UINT2NUM(BUILD_PIXEL(new_r, new_g, new_b, new_a));
80
+ }
@@ -0,0 +1,24 @@
1
+ #ifndef APPLITOOLS_RESAMPLING_EXT
2
+ #define APPLITOOLS_RESAMPLING_EXT
3
+ #include "ruby.h"
4
+
5
+ typedef uint32_t PIXEL; // Pixels use 32 bits unsigned integers
6
+ typedef unsigned char BYTE; // Bytes use 8 bits unsigned integers
7
+
8
+ #define R_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0xff000000) >> 24))
9
+ #define G_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0x00ff0000) >> 16))
10
+ #define B_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0x0000ff00) >> 8))
11
+ #define A_BYTE(pixel) ((BYTE) (((pixel) & (PIXEL) 0x000000ff)))
12
+
13
+ #define BUILD_PIXEL(r, g, b, a) (((PIXEL) (r) << 24) + ((PIXEL) (g) << 16) + ((PIXEL) (b) << 8) + (PIXEL) (a))
14
+ #define INT8_MULTIPLY(a, b) (((((a) * (b) + 0x80) >> 8) + ((a) * (b) + 0x80)) >> 8)
15
+
16
+
17
+
18
+ void Init_resampling();
19
+
20
+ VALUE c_interpolate_cubic(VALUE,VALUE);
21
+ VALUE c_merge_pixels(VALUE, VALUE);
22
+ BYTE interpolate_char(double, BYTE, BYTE, BYTE, BYTE);
23
+
24
+ #endif
@@ -0,0 +1,8 @@
1
+ require 'capybara'
2
+
3
+ Applitools.require_dir 'selenium/capybara'
4
+
5
+ module Applitools
6
+ extend Applitools::Selenium::Capybara::CapybaraSettings
7
+ register_capybara_driver
8
+ end
@@ -0,0 +1,148 @@
1
+ module Applitools::ChunkyPNG
2
+ module Resampling
3
+ def resample_bicubic!(dst_width, dst_height)
4
+ w_m = [1, width / dst_width].max
5
+ h_m = [1, height / dst_height].max
6
+
7
+ dst_width2 = dst_width * w_m
8
+ dst_height2 = dst_height * h_m
9
+
10
+ points = bicubic_x_points(dst_width2)
11
+ pixels = Array.new(points.size)
12
+
13
+ points.each do |interpolation_data|
14
+ pixels[interpolation_data[0]] = interpolate_cubic(interpolation_data)
15
+ end
16
+ replace_canvas!(dst_width2, height, pixels)
17
+
18
+ points = bicubic_y_points(dst_height2)
19
+ pixels = Array.new(points.size)
20
+
21
+ points.each do |interpolation_data|
22
+ pixels[interpolation_data[0]] = interpolate_cubic(interpolation_data)
23
+ end
24
+ replace_canvas!(dst_width2, dst_height2, pixels)
25
+
26
+ return self unless w_m * h_m > 1
27
+
28
+ points = scale_points(dst_width, dst_height, w_m, h_m)
29
+ pixels = Array.new(points.size)
30
+
31
+ points.each do |merge_data|
32
+ pixels[merge_data[0]] = merge_pixels(merge_data)
33
+ end
34
+
35
+ replace_canvas!(dst_width, dst_height, pixels)
36
+ end
37
+
38
+ def resample_bicubic(new_width, new_height)
39
+ dup.resample_bicubic!(new_width, new_height)
40
+ end
41
+
42
+ def bicubic_x_points(dst_width)
43
+ bicubic_points(width, dst_width, false)
44
+ end
45
+
46
+ def bicubic_y_points(dst_height)
47
+ bicubic_points(height, dst_height, true)
48
+ end
49
+
50
+ def bicubic_points(src_dimension, dst_dimension, direction, y_start_position = 0)
51
+ step = (src_dimension - 1).to_f / dst_dimension
52
+ y_bounds = direction ? width : height
53
+ raise ArgumentError.new 'Start position value is invalid!' unless y_start_position < y_bounds
54
+ pixels_size = (y_bounds - y_start_position) * dst_dimension
55
+
56
+ steps = Array.new(dst_dimension)
57
+ residues = Array.new(dst_dimension)
58
+
59
+ (0..dst_dimension - 1).each do |i|
60
+ steps[i] = (i * step).to_i
61
+ residues[i] = i * step - steps[i]
62
+ end
63
+ Enumerator.new(pixels_size) do |enum|
64
+ (y_start_position..y_bounds - 1).each do |y|
65
+ line = (direction ? column(y) : row(y))
66
+
67
+ line_with_bounds = [imaginable_point(line[0], line[1])] + line + [
68
+ imaginable_point(line[src_dimension - 2], line[src_dimension - 3]),
69
+ imaginable_point(line[src_dimension - 1], line[src_dimension - 2])
70
+ ]
71
+
72
+ index_y = dst_dimension * y
73
+ (0..dst_dimension - 1).each do |x|
74
+ index = direction ? y_bounds * x + y : index_y + x
75
+ enum << ([index, residues[x]] + line_with_bounds.last(src_dimension + 3 - steps[x]).first(4))
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def scale_points(dst_width, dst_height, w_m, h_m)
82
+ Enumerator.new(dst_width * dst_height) do |enum|
83
+ (0..dst_height - 1).each do |i|
84
+ (0..dst_width - 1).each do |j|
85
+ pixels_to_merge = []
86
+ (0..h_m - 1).each do |y|
87
+ y_pos = i * h_m + y
88
+ (0..w_m - 1).each do |x|
89
+ x_pos = j * w_m + x
90
+ pixels_to_merge << get_pixel(x_pos, y_pos)
91
+ end
92
+ end
93
+ index = i * dst_width + j
94
+ enum << ([index] + pixels_to_merge)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def merge_pixels(merge_data)
101
+ pixels = merge_data[1..merge_data.size]
102
+ merged_data = pixels.each_with_object(r: 0, g: 0, b: 0, a: 0, real_colors: 0) do |pixel, result|
103
+ unless ChunkyPNG::Color.fully_transparent?(pixel)
104
+ result[:real_colors] += 1
105
+ [:r, :g, :b].each do |ch|
106
+ result[ch] += ChunkyPNG::Color.send(ch, pixel)
107
+ end
108
+ end
109
+ result[:a] += ChunkyPNG::Color.a(pixel)
110
+ result
111
+ end
112
+
113
+ r = merged_data[:real_colors] > 0 ? merged_data[:r] / merged_data[:real_colors] : 0
114
+ g = merged_data[:real_colors] > 0 ? merged_data[:g] / merged_data[:real_colors] : 0
115
+ b = merged_data[:real_colors] > 0 ? merged_data[:b] / merged_data[:real_colors] : 0
116
+ a = merged_data[:a] / pixels.size
117
+
118
+ ChunkyPNG::Color.rgba(r, g, b, a)
119
+ end
120
+
121
+ def interpolate_cubic(data)
122
+ result = {}
123
+ t = data[1]
124
+ [:r, :g, :b, :a].each do |chan|
125
+ c0 = ChunkyPNG::Color.send(chan, data[2])
126
+ c1 = ChunkyPNG::Color.send(chan, data[3])
127
+ c2 = ChunkyPNG::Color.send(chan, data[4])
128
+ c3 = ChunkyPNG::Color.send(chan, data[5])
129
+
130
+ a = -0.5 * c0 + 1.5 * c1 - 1.5 * c2 + 0.5 * c3
131
+ b = c0 - 2.5 * c1 + 2 * c2 - 0.5 * c3
132
+ c = 0.5 * c2 - 0.5 * c0
133
+ d = c1
134
+
135
+ result[chan] = [0, [255, (a * t**3 + b * t**2 + c * t + d).to_i].min].max
136
+ end
137
+ ChunkyPNG::Color.rgba(result[:r], result[:g], result[:b], result[:a])
138
+ end
139
+
140
+ def imaginable_point(point1, point2)
141
+ r = [0, [255, ChunkyPNG::Color.r(point1) << 1].min - ChunkyPNG::Color.r(point2)].max
142
+ g = [0, [255, ChunkyPNG::Color.g(point1) << 1].min - ChunkyPNG::Color.g(point2)].max
143
+ b = [0, [255, ChunkyPNG::Color.b(point1) << 1].min - ChunkyPNG::Color.b(point2)].max
144
+ a = [0, [255, ChunkyPNG::Color.a(point1) << 1].min - ChunkyPNG::Color.a(point2)].max
145
+ ChunkyPNG::Color.rgba(r, g, b, a)
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,8 @@
1
+ require 'oily_png'
2
+ require_relative 'chunky_png/resampling'
3
+ require 'eyes_core/eyes_core'
4
+
5
+ ChunkyPNG::Canvas.class_eval do
6
+ include Applitools::ChunkyPNG::Resampling
7
+ include Applitools::ResamplingFast
8
+ end
@@ -0,0 +1,3 @@
1
+ module Applitools::Connectivity
2
+ Proxy = Struct.new(:uri, :user, :password)
3
+ end
@@ -0,0 +1,118 @@
1
+ require 'faraday'
2
+ require 'oj'
3
+ Oj.default_options = { :mode => :compat }
4
+
5
+ require 'uri'
6
+
7
+ module Applitools::Connectivity
8
+ module ServerConnector
9
+ extend self
10
+
11
+ DEFAULT_SERVER_URL = 'https://eyessdk.applitools.com'.freeze
12
+
13
+ SSL_CERT = File.join(File.dirname(File.expand_path(__FILE__)), '../../../certs/cacert.pem').to_s.freeze
14
+ DEFAULT_TIMEOUT = 300
15
+
16
+ API_SESSIONS_RUNNING = '/api/sessions/running/'.freeze
17
+
18
+ HTTP_STATUS_CODES = {
19
+ created: 201,
20
+ accepted: 202
21
+ }.freeze
22
+
23
+ attr_accessor :server_url, :api_key
24
+ attr_reader :endpoint_url
25
+ attr_accessor :proxy
26
+
27
+ def server_url=(url)
28
+ @server_url = url.nil? ? DEFAULT_SERVER_URL : url
29
+ unless @server_url.is_a? String
30
+ raise Applitools::EyesIllegalArgument.new 'You should pass server url as a String!' \
31
+ " (#{@server_url.class} is passed)"
32
+ end
33
+ @endpoint_url = URI.join(@server_url, API_SESSIONS_RUNNING).to_s
34
+ end
35
+
36
+ def set_proxy(uri, user = nil, password = nil)
37
+ self.proxy = Proxy.new uri, user, password
38
+ end
39
+
40
+ def match_window(session, data)
41
+ # Notice that this does not include the screenshot.
42
+ json_data = Oj.dump(Applitools::Utils.camelcase_hash_keys(data.to_hash)).force_encoding('BINARY')
43
+ body = [json_data.length].pack('L>') + json_data + data.screenshot
44
+ Applitools::EyesLogger.debug 'Sending match data...'
45
+ res = post(URI.join(endpoint_url, session.id.to_s), content_type: 'application/octet-stream', body: body)
46
+ raise Applitools::EyesError.new("Request failed: #{res.status}") unless res.success?
47
+ Applitools::MatchResult.new Oj.load(res.body)
48
+ end
49
+
50
+ def start_session(session_start_info)
51
+ res = post(endpoint_url, body: Oj.dump(startInfo:
52
+ Applitools::Utils.camelcase_hash_keys(session_start_info.to_hash)))
53
+ raise Applitools::EyesError.new("Request failed: #{res.status}") unless res.success?
54
+
55
+ response = Oj.load(res.body)
56
+ Applitools::Session.new(response['id'], response['url'], res.status == HTTP_STATUS_CODES[:created])
57
+ end
58
+
59
+ def stop_session(session, aborted = nil, save = false)
60
+ res = long_delete(URI.join(endpoint_url, session.id.to_s), query: { aborted: aborted, updateBaseline: save })
61
+ raise Applitools::EyesError.new("Request failed: #{res.status}") unless res.success?
62
+
63
+ response = Oj.load(res.body)
64
+ Applitools::TestResults.new(response)
65
+ end
66
+
67
+ private
68
+
69
+ DEFAULT_HEADERS = {
70
+ 'Accept' => 'application/json',
71
+ 'Content-Type' => 'application/json'
72
+ }.freeze
73
+
74
+ LONG_REQUEST_DELAY = 2 # seconds
75
+ MAX_LONG_REQUEST_DELAY = 10 # seconds
76
+ LONG_REQUEST_DELAY_MULTIPLICATIVE_INCREASE_FACTOR = 1.5
77
+
78
+ [:get, :post, :delete].each do |method|
79
+ define_method method do |url, options = {}|
80
+ request(url, method, options)
81
+ end
82
+
83
+ define_method "long_#{method}" do |url, options = {}|
84
+ long_request(url, method, options)
85
+ end
86
+ end
87
+
88
+ def request(url, method, options = {})
89
+ Faraday::Connection.new(url, ssl: { ca_file: SSL_CERT }, proxy: @proxy || nil).send(method) do |req|
90
+ req.options.timeout = DEFAULT_TIMEOUT
91
+ req.headers = DEFAULT_HEADERS.merge(options[:headers] || {})
92
+ req.headers['Content-Type'] = options[:content_type] if options.key?(:content_type)
93
+ req.params = { apiKey: api_key }.merge(options[:query] || {})
94
+ req.body = options[:body]
95
+ end
96
+ end
97
+
98
+ def long_request(url, method, options = {})
99
+ delay = LONG_REQUEST_DELAY
100
+ (options[:headers] ||= {})['Eyes-Expect'] = '202-accepted'
101
+
102
+ loop do
103
+ # Date should be in RFC 1123 format.
104
+ options[:headers]['Eyes-Date'] = Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S GMT')
105
+
106
+ res = request(url, method, options)
107
+ return res unless res.status == HTTP_STATUS_CODES[:accepted]
108
+
109
+ Applitools::EyesLogger.debug "Still running... retrying in #{delay}s"
110
+ sleep delay
111
+
112
+ delay = [MAX_LONG_REQUEST_DELAY, (delay * LONG_REQUEST_DELAY_MULTIPLICATIVE_INCREASE_FACTOR).round].min
113
+ end
114
+ end
115
+
116
+ include Applitools::MethodTracer
117
+ end
118
+ end
@@ -0,0 +1,29 @@
1
+ module Applitools
2
+ class AppEnvironment
3
+ attr_accessor :os, :hosting_app, :display_size, :inferred_environment
4
+
5
+ def initialize(options = {})
6
+ @os = options[:os]
7
+ @hosting_app = options[:hosting_app]
8
+ @display_size = options[:display_size]
9
+ @inferred = options[:inferred]
10
+ end
11
+
12
+ def to_hash
13
+ {
14
+ os: @os,
15
+ hosting_app: @hosting_app,
16
+ display_size: @display_size.to_hash,
17
+ inferred: @inferred
18
+ }
19
+ end
20
+
21
+ def to_s
22
+ result = ''
23
+ to_hash.each_pair do |k, v|
24
+ result << "#{k}: #{v}; "
25
+ end
26
+ result
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,17 @@
1
+ module Applitools
2
+ class AppOutput
3
+ attr_reader :title, :screenshot64
4
+
5
+ def initialize(title, screenshot64)
6
+ @title = title
7
+ @screenshot64 = screenshot64
8
+ end
9
+
10
+ def to_hash
11
+ {
12
+ title: title,
13
+ screenshot64: ''
14
+ }
15
+ end
16
+ end
17
+ end