eyes_core 3.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/ext/eyes_core/extconf.rb +3 -0
- data/ext/eyes_core/eyes_core.c +80 -0
- data/ext/eyes_core/eyes_core.h +24 -0
- data/lib/applitools/capybara.rb +8 -0
- data/lib/applitools/chunky_png/resampling.rb +148 -0
- data/lib/applitools/chunky_png_patch.rb +8 -0
- data/lib/applitools/connectivity/proxy.rb +3 -0
- data/lib/applitools/connectivity/server_connector.rb +118 -0
- data/lib/applitools/core/app_environment.rb +29 -0
- data/lib/applitools/core/app_output.rb +17 -0
- data/lib/applitools/core/app_output_with_screenshot.rb +22 -0
- data/lib/applitools/core/argument_guard.rb +35 -0
- data/lib/applitools/core/batch_info.rb +18 -0
- data/lib/applitools/core/eyes_base.rb +463 -0
- data/lib/applitools/core/eyes_screenshot.rb +35 -0
- data/lib/applitools/core/fixed_cut_provider.rb +61 -0
- data/lib/applitools/core/fixed_scale_provider.rb +14 -0
- data/lib/applitools/core/helpers.rb +18 -0
- data/lib/applitools/core/location.rb +84 -0
- data/lib/applitools/core/match_result.rb +16 -0
- data/lib/applitools/core/match_results.rb +9 -0
- data/lib/applitools/core/match_window_data.rb +34 -0
- data/lib/applitools/core/match_window_task.rb +86 -0
- data/lib/applitools/core/mouse_trigger.rb +39 -0
- data/lib/applitools/core/rectangle_size.rb +46 -0
- data/lib/applitools/core/region.rb +180 -0
- data/lib/applitools/core/screenshot.rb +49 -0
- data/lib/applitools/core/session.rb +15 -0
- data/lib/applitools/core/session_start_info.rb +33 -0
- data/lib/applitools/core/test_results.rb +55 -0
- data/lib/applitools/core/text_trigger.rb +24 -0
- data/lib/applitools/core/trigger.rb +8 -0
- data/lib/applitools/extensions.rb +18 -0
- data/lib/applitools/eyes_logger.rb +45 -0
- data/lib/applitools/images/eyes.rb +204 -0
- data/lib/applitools/images/eyes_images_screenshot.rb +102 -0
- data/lib/applitools/method_tracer.rb +23 -0
- data/lib/applitools/sauce.rb +2 -0
- data/lib/applitools/utils/eyes_selenium_utils.rb +348 -0
- data/lib/applitools/utils/image_delta_compressor.rb +146 -0
- data/lib/applitools/utils/image_utils.rb +146 -0
- data/lib/applitools/utils/utils.rb +68 -0
- data/lib/applitools/version.rb +3 -0
- data/lib/eyes_core.rb +70 -0
- metadata +273 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module Applitools
|
2
|
+
class EyesScreenshot
|
3
|
+
extend Forwardable
|
4
|
+
extend Applitools::Helpers
|
5
|
+
|
6
|
+
def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
|
7
|
+
attr_accessor :image
|
8
|
+
|
9
|
+
COORDINATE_TYPES = {
|
10
|
+
screenshot_as_is: 'SCREENSHOT_AS_IS',
|
11
|
+
context_relative: 'CONTEXT_RELATIVE'
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def initialize(screenshot)
|
15
|
+
Applitools::ArgumentGuard.is_a? screenshot, 'screenshot', Applitools::Screenshot
|
16
|
+
self.image = screenshot
|
17
|
+
end
|
18
|
+
|
19
|
+
abstract_method :sub_screenshot, false
|
20
|
+
abstract_method :convert_location, false
|
21
|
+
abstract_method :location_in_screenshot, false
|
22
|
+
abstract_method :intersected_region, false
|
23
|
+
|
24
|
+
def convert_region_location(region, from, to)
|
25
|
+
Applitools::ArgumentGuard.not_nil region, 'region'
|
26
|
+
Applitools::ArgumentGuard.is_a? region, 'region', Applitools::Region
|
27
|
+
return Region.new(0, 0, 0, 0) if region.empty?
|
28
|
+
Applitools::ArgumentGuard.not_nil from, 'from'
|
29
|
+
Applitools::ArgumentGuard.not_nil to, 'to'
|
30
|
+
|
31
|
+
updated_location = convert_location(region.location, from, to)
|
32
|
+
Region.new updated_location.x, updated_location.y, region.width, region.height
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Applitools
|
2
|
+
# Provides 'cut' method which is used to cut screen shots
|
3
|
+
class FixedCutProvider
|
4
|
+
# Creates a FixedCutProvider instance
|
5
|
+
# @param [Applitools::Region] crop_region Outside space of the region will be cropped
|
6
|
+
# @param [Integer] header A top field to crop
|
7
|
+
# @param [Integer] left A left field to crop
|
8
|
+
# @param [Integer] right A right field to crop
|
9
|
+
# @param [Integer] footer A bottom field to crop
|
10
|
+
# @example Creates cut provider by a Region
|
11
|
+
# Applitools::FixedCutProvider.new Applitools::Region.new(20,20, 300, 300)
|
12
|
+
# @example Creates cut provider by a set of fields
|
13
|
+
# Applitools::FixedCutProvider.new 20, 20, 300, 300
|
14
|
+
def initialize(*args)
|
15
|
+
self.region = nil
|
16
|
+
self.left = 0
|
17
|
+
self.header = 0
|
18
|
+
self.right = 0
|
19
|
+
self.footer = 0
|
20
|
+
case args.length
|
21
|
+
when 1
|
22
|
+
initialize_by_rectangle(args[0])
|
23
|
+
when 4
|
24
|
+
initialize_by_fields(*args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def cut(image)
|
29
|
+
Applitools::Utils::ImageUtils.cut! image, crop_region(image)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_accessor :header, :footer, :left, :right, :region
|
35
|
+
|
36
|
+
def initialize_by_rectangle(region)
|
37
|
+
unless region.is_a? Applitools::Region
|
38
|
+
raise Applitools::EyesIllegalArgument.new 'Applitools::Region expected as argument ' /
|
39
|
+
" (#{region.class} is passed)."
|
40
|
+
end
|
41
|
+
self.region = region
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize_by_fields(header, footer, left, right)
|
45
|
+
self.header = header
|
46
|
+
self.footer = footer
|
47
|
+
self.left = left
|
48
|
+
self.right = right
|
49
|
+
end
|
50
|
+
|
51
|
+
def crop_region(image)
|
52
|
+
current_height = image.height
|
53
|
+
current_width = image.width
|
54
|
+
if region.nil?
|
55
|
+
Applitools::Region.new(left, header, current_width - left - right, current_height - header - footer)
|
56
|
+
else
|
57
|
+
region.dup
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Applitools
|
2
|
+
# @!visibility private
|
3
|
+
class FixedScaleProvider
|
4
|
+
attr_reader :scale_ratio, :scale_method
|
5
|
+
def initialize(scale_ratio, method = :speed)
|
6
|
+
@scale_ratio = scale_ratio
|
7
|
+
@scale_method = method
|
8
|
+
end
|
9
|
+
|
10
|
+
def scale_image(image)
|
11
|
+
Applitools::Utils::ImageUtils.scale!(image, scale_method, scale_ratio)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Applitools
|
2
|
+
module Helpers
|
3
|
+
def abstract_attr_accessor(*names)
|
4
|
+
names.each do |method_name|
|
5
|
+
instance_variable_set "@#{method_name}", nil
|
6
|
+
abstract_method method_name, true
|
7
|
+
abstract_method "#{method_name}=", true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def abstract_method(method_name, is_private = true)
|
12
|
+
define_method method_name do |*_args|
|
13
|
+
raise Applitools::AbstractMethodCalled.new method_name, self
|
14
|
+
end
|
15
|
+
private method_name if is_private
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'applitools/core/region'
|
2
|
+
module Applitools
|
3
|
+
class Location
|
4
|
+
class << self
|
5
|
+
def from_any_attribute(*args)
|
6
|
+
if args.size == 2
|
7
|
+
new args[0], args[1]
|
8
|
+
elsif args.size == 1
|
9
|
+
value = args.shift
|
10
|
+
from_hash(value) if value.is_a? Hash
|
11
|
+
from_array(value) if value.is_a? Array
|
12
|
+
from_string(value) if value.is_a? String
|
13
|
+
from_struct(value) if value.respond_to?(:x) & value.respond_to?(:y)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias for from_any_attribute
|
18
|
+
|
19
|
+
def from_hash(value)
|
20
|
+
new value[:x], value[:y]
|
21
|
+
end
|
22
|
+
|
23
|
+
def from_array(value)
|
24
|
+
new value.shift, value.shift
|
25
|
+
end
|
26
|
+
|
27
|
+
def from_string(value)
|
28
|
+
x, y = value.split(/x/)
|
29
|
+
new x, y
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_struct(value)
|
33
|
+
new value.x, value.y
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :x, :y
|
38
|
+
|
39
|
+
alias left x
|
40
|
+
alias top y
|
41
|
+
|
42
|
+
def initialize(x, y)
|
43
|
+
@x = x
|
44
|
+
@y = y
|
45
|
+
end
|
46
|
+
|
47
|
+
TOP_LEFT = Location.new(0, 0)
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"#{x} x #{y}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def ==(other)
|
54
|
+
return super.==(other) unless other.is_a?(Location)
|
55
|
+
@x == other.x && @y == other.y
|
56
|
+
end
|
57
|
+
|
58
|
+
alias eql? ==
|
59
|
+
|
60
|
+
def hash
|
61
|
+
@x.hash & @y.hash
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_hash(options = {})
|
65
|
+
options[:region] ? { left: left, top: top } : { x: x, y: y }
|
66
|
+
end
|
67
|
+
|
68
|
+
def values
|
69
|
+
[x, y]
|
70
|
+
end
|
71
|
+
|
72
|
+
def offset(other)
|
73
|
+
@x += other.x
|
74
|
+
@y += other.y
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def offset_negative(other)
|
79
|
+
@x -= other.x
|
80
|
+
@y -= other.y
|
81
|
+
self
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Applitools
|
2
|
+
class MatchResult
|
3
|
+
attr_reader :response_hash
|
4
|
+
attr_accessor :screenshot
|
5
|
+
|
6
|
+
def initialize(response_hash)
|
7
|
+
Applitools::ArgumentGuard.hash response_hash, 'response hash', ['asExpected']
|
8
|
+
@response_hash = response_hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def as_expected?
|
12
|
+
return response_hash['asExpected'] if [TrueClass, FalseClass].include? response_hash['asExpected'].class
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Applitools
|
2
|
+
class MatchWindowData
|
3
|
+
attr_accessor :app_output, :user_inputs, :tag, :options, :ignore_mismatch
|
4
|
+
|
5
|
+
def initialize(user_inputs, app_output, tag, ignore_mismatch, options = {})
|
6
|
+
self.user_inputs = user_inputs
|
7
|
+
self.app_output = app_output
|
8
|
+
self.tag = tag
|
9
|
+
self.ignore_mismatch = ignore_mismatch
|
10
|
+
self.options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def screenshot
|
14
|
+
app_output.screenshot.image.to_blob
|
15
|
+
end
|
16
|
+
|
17
|
+
alias appOutput app_output
|
18
|
+
alias userInputs user_inputs
|
19
|
+
alias ignoreMismatch ignore_mismatch
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
ary = [:userInputs, :appOutput, :tag, :ignoreMismatch].map do |field|
|
23
|
+
result = send(field)
|
24
|
+
result = result.to_hash if result.respond_to? :to_hash
|
25
|
+
[field, result] if [String, Symbol, Hash, Array, FalseClass, TrueClass].include? result.class
|
26
|
+
end
|
27
|
+
Hash[ary]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
to_hash
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'base64'
|
2
|
+
module Applitools
|
3
|
+
class MatchWindowTask
|
4
|
+
MATCH_INTERVAL = 0.5
|
5
|
+
AppOuptut = Struct.new(:title, :screenshot64)
|
6
|
+
|
7
|
+
attr_reader :logger, :running_session, :default_retry_timeout, :app_output_provider
|
8
|
+
|
9
|
+
def initialize(logger, running_session, retry_timeout, app_output_provider)
|
10
|
+
@logger = logger
|
11
|
+
@running_session = running_session
|
12
|
+
# @driver = driver
|
13
|
+
@default_retry_timeout = retry_timeout
|
14
|
+
@app_output_provider = app_output_provider
|
15
|
+
|
16
|
+
ArgumentGuard.not_nil logger, 'logger'
|
17
|
+
ArgumentGuard.not_nil running_session, 'running_session'
|
18
|
+
ArgumentGuard.not_nil app_output_provider, 'app_output_provider'
|
19
|
+
ArgumentGuard.greater_than_or_equal_to_zero retry_timeout, 'retry_timeout'
|
20
|
+
|
21
|
+
return if app_output_provider.respond_to? :app_output
|
22
|
+
raise Applitools::EyesIllegalArgument.new 'MatchWindowTask.new(): app_output_provider doesn\'t' /
|
23
|
+
' respond to :app_output'
|
24
|
+
end
|
25
|
+
|
26
|
+
def match_window(options = {})
|
27
|
+
user_inputs = options[:user_inputs]
|
28
|
+
last_screenshot = options[:last_screenshot]
|
29
|
+
region_provider = options[:region_provider]
|
30
|
+
tag = options[:tag]
|
31
|
+
should_match_window_run_once_on_timeout = options[:should_match_window_run_once_on_timeout]
|
32
|
+
ignore_mismatch = options[:ignore_mismatch]
|
33
|
+
retry_timeout = options[:retry_timeout]
|
34
|
+
|
35
|
+
retry_timeout = default_retry_timeout if retry_timeout < 0
|
36
|
+
|
37
|
+
logger.info "retry_timeout = #{retry_timeout}"
|
38
|
+
elapsed_time_start = Time.now
|
39
|
+
|
40
|
+
if retry_timeout.zero? || should_match_window_run_once_on_timeout
|
41
|
+
sleep retry_timeout if should_match_window_run_once_on_timeout
|
42
|
+
app_output = app_output_provider.app_output region_provider, last_screenshot
|
43
|
+
match_result = perform_match user_inputs: user_inputs, app_output: app_output, tag: tag,
|
44
|
+
ignore_mismatch: ignore_mismatch
|
45
|
+
else
|
46
|
+
app_output = app_output_provider.app_output region_provider, last_screenshot
|
47
|
+
start = Time.now
|
48
|
+
match_result = perform_match user_inputs: user_inputs, app_output: app_output, tag: tag, ignore_mismatch: true
|
49
|
+
retry_time = Time.now - start
|
50
|
+
|
51
|
+
while retry_time < retry_timeout && !match_result.as_expected?
|
52
|
+
sleep MATCH_INTERVAL
|
53
|
+
app_output = app_output_provider.app_output region_provider, last_screenshot
|
54
|
+
match_result = perform_match user_inputs: user_inputs, app_output: app_output, tag: tag, ignore_mismatch: true
|
55
|
+
retry_time = Time.now - start
|
56
|
+
end
|
57
|
+
|
58
|
+
unless match_result.as_expected?
|
59
|
+
app_output = app_output_provider.app_output region_provider, last_screenshot
|
60
|
+
match_result = perform_match user_inputs: user_inputs, app_output: app_output, tag: tag,
|
61
|
+
ignore_mismatch: ignore_mismatch
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
elapsed_time = (Time.now - elapsed_time_start) / 1000
|
66
|
+
|
67
|
+
logger.info "Completed in #{format('%.2f', elapsed_time)} seconds"
|
68
|
+
|
69
|
+
match_result.screenshot = app_output.screenshot
|
70
|
+
match_result
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def perform_match(options = {})
|
76
|
+
user_inputs = options[:user_inputs]
|
77
|
+
app_output = options[:app_output]
|
78
|
+
tag = options[:tag]
|
79
|
+
ignore_mismatch = options[:ignore_mismatch]
|
80
|
+
data = Applitools::MatchWindowData.new user_inputs, app_output, tag, ignore_mismatch,
|
81
|
+
tag: tag, user_inputs: user_inputs, ignore_mismatch: ignore_mismatch, ignore_match: false,
|
82
|
+
force_mistmatch: false, force_match: false
|
83
|
+
Applitools::Connectivity::ServerConnector.match_window running_session, data
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'applitools/core/trigger'
|
2
|
+
module Applitools
|
3
|
+
class MouseTrigger < Trigger
|
4
|
+
TRIGGER_TYPE = :Mouse
|
5
|
+
|
6
|
+
##
|
7
|
+
# A hash contains available mouse actions: +:click+, +:right_click+, +:double_click+, +:move+, +:down+, +:up+
|
8
|
+
|
9
|
+
MOUSE_ACTION = {
|
10
|
+
click: 'Click',
|
11
|
+
right_click: 'RightClick',
|
12
|
+
double_click: 'DoubleClick',
|
13
|
+
move: 'Move',
|
14
|
+
down: 'Down',
|
15
|
+
up: 'Up'
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
attr_reader :mouse_action, :control, :location
|
19
|
+
|
20
|
+
def initialize(mouse_action, control, location)
|
21
|
+
@mouse_action = mouse_action
|
22
|
+
@control = control
|
23
|
+
@location = location
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
{
|
28
|
+
triggerType: trigger_type,
|
29
|
+
mouseAction: MOUSE_ACTION[mouse_action],
|
30
|
+
control: control.to_hash,
|
31
|
+
location: location.to_hash
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
"#{mouse_action} [#{control}] #{location.x}, #{location.y}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Applitools
|
2
|
+
RectangleSize = Struct.new(:width, :height) do
|
3
|
+
class << self
|
4
|
+
def from_any_argument(value)
|
5
|
+
return from_string(value) if value.is_a? String
|
6
|
+
return from_hash(value) if value.is_a? Hash
|
7
|
+
return from_struct(value) if value.respond_to?(:width) & value.respond_to?(:height)
|
8
|
+
return value if value.is_a? self
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :for, :from_any_argument
|
13
|
+
|
14
|
+
def from_string(value)
|
15
|
+
width, height = value.split(/x/)
|
16
|
+
new width, height
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_hash(value)
|
20
|
+
new value[:width], value[:height]
|
21
|
+
end
|
22
|
+
|
23
|
+
def from_struct(value)
|
24
|
+
new value.width, value.height
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
"#{width}x#{height}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def -(other)
|
33
|
+
self.width = width - other.width
|
34
|
+
self.height = height - other.height
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def +(other)
|
39
|
+
self.width = width + other.width
|
40
|
+
self.height = height + other.height
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :to_hash, :to_h
|
45
|
+
end
|
46
|
+
end
|