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