eyes_core 3.0.4

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