eyes_calabash 3.14.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/applitools/calabash/calabash_element.rb +62 -0
- data/lib/applitools/calabash/calabash_screenshot_provider.rb +81 -0
- data/lib/applitools/calabash/environment_detector.rb +23 -0
- data/lib/applitools/calabash/eyes.rb +182 -0
- data/lib/applitools/calabash/eyes_calabash_android_screenshot.rb +56 -0
- data/lib/applitools/calabash/eyes_calabash_ios_screenshot.rb +28 -0
- data/lib/applitools/calabash/eyes_calabash_screenshot.rb +78 -0
- data/lib/applitools/calabash/eyes_hooks.rb +51 -0
- data/lib/applitools/calabash/eyes_settings.rb +43 -0
- data/lib/applitools/calabash/full_page_capture_algorithm.rb +24 -0
- data/lib/applitools/calabash/full_page_capture_algorithm/android_scroll_view.rb +85 -0
- data/lib/applitools/calabash/full_page_capture_algorithm/base.rb +49 -0
- data/lib/applitools/calabash/full_page_capture_algorithm/ios_ui_table_view.rb +148 -0
- data/lib/applitools/calabash/os_versions.rb +23 -0
- data/lib/applitools/calabash/rspec_matchers.rb +22 -0
- data/lib/applitools/calabash/steps/android_eyes_session.rb +35 -0
- data/lib/applitools/calabash/steps/android_matchers.rb +34 -0
- data/lib/applitools/calabash/steps/eyes_session.rb +40 -0
- data/lib/applitools/calabash/steps/eyes_settings.rb +57 -0
- data/lib/applitools/calabash/steps/ios_eyes_session.rb +13 -0
- data/lib/applitools/calabash/steps/ios_matchers.rb +15 -0
- data/lib/applitools/calabash/steps/matchers.rb +69 -0
- data/lib/applitools/calabash/target.rb +67 -0
- data/lib/applitools/calabash/utils.rb +72 -0
- data/lib/applitools/calabash_steps.rb +14 -0
- data/lib/applitools/version.rb +3 -0
- data/lib/eyes_calabash.rb +23 -0
- metadata +128 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
if respond_to?(:Before)
|
2
|
+
Before('@eyes') do |scenario|
|
3
|
+
get_scenario_tags(scenario)
|
4
|
+
before_feature(scenario) if scenario.feature.children.first == scenario.source.last
|
5
|
+
end
|
6
|
+
|
7
|
+
Before('@eyes') do |scenario|
|
8
|
+
Applitools::Calabash::EyesSettings.instance.eyes.add_context(self)
|
9
|
+
step %(eyes test name is "#{@eyes_current_tags[:test_name] || scenario.name}")
|
10
|
+
step %(open eyes)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
if respond_to?(:After)
|
15
|
+
After('@eyes') do |scenario|
|
16
|
+
after_feature(scenario) if scenario.feature.children.last == scenario.source.last
|
17
|
+
end
|
18
|
+
|
19
|
+
After('@eyes') do |_scenario|
|
20
|
+
eyes = Applitools::Calabash::EyesSettings.instance.eyes
|
21
|
+
Applitools::Calabash::EyesSettings.instance.eyes.remove_context if eyes && eyes.open?
|
22
|
+
step %(terminate eyes session)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def before_feature(scenario)
|
27
|
+
@before_hook_scenario = scenario
|
28
|
+
eyes_settings = Applitools::Calabash::EyesSettings.instance
|
29
|
+
|
30
|
+
step %(eyes API key "#{@eyes_current_tags[:api_key] || ENV['APPLITOOLS_API_KEY']}") unless
|
31
|
+
eyes_settings.applitools_api_key
|
32
|
+
|
33
|
+
step %(eyes application name is "#{@eyes_current_tags[:app_name] || scenario.feature.name}")
|
34
|
+
|
35
|
+
step %(set it up) if eyes_settings.needs_setting_up
|
36
|
+
|
37
|
+
step %(create eyes)
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_feature(_scenario)
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def get_scenario_tags(scenario)
|
45
|
+
@eyes_current_tags = {}
|
46
|
+
eyes_tag_name_regexp = /@eyes_(?<tag_name>[a-z,A-Z, \_]+) \"(?<value>.*)\"/
|
47
|
+
scenario.tags.each do |t|
|
48
|
+
match_data = t.name.match eyes_tag_name_regexp
|
49
|
+
@eyes_current_tags[match_data[:tag_name].to_sym] = match_data[:value] if match_data
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require 'securerandom'
|
3
|
+
module Applitools
|
4
|
+
module Calabash
|
5
|
+
class EyesSettings
|
6
|
+
attr_accessor :eyes, :applitools_api_key, :app_name, :test_name, :viewport_size
|
7
|
+
attr_accessor :tmp_dir, :screenshot_dir, :log_dir, :log_file
|
8
|
+
attr_accessor :needs_setting_up
|
9
|
+
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@tmp_dir = 'tmp'
|
14
|
+
@screenshot_dir = 'screenshots'
|
15
|
+
@log_dir = 'logs'
|
16
|
+
@log_file = 'applitools.log'
|
17
|
+
@needs_setting_up = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def options_for_open
|
21
|
+
result = { app_name: app_name, test_name: test_name }
|
22
|
+
return result unless viewport_size
|
23
|
+
result.merge!(viewport_size: viewport_size)
|
24
|
+
end
|
25
|
+
|
26
|
+
def screenshot_prefix
|
27
|
+
File.join(Dir.getwd, tmp_dir, screenshot_dir, '')
|
28
|
+
end
|
29
|
+
|
30
|
+
def log_prefix
|
31
|
+
File.join(Dir.getwd, log_dir)
|
32
|
+
end
|
33
|
+
|
34
|
+
def screenshot_names
|
35
|
+
@names ||= Enumerator.new do |y|
|
36
|
+
loop do
|
37
|
+
y << { prefix: screenshot_prefix, name: "#{SecureRandom.uuid}.png" }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
Applitools::Calabash.require_dir 'calabash/full_page_capture_algorithm'
|
2
|
+
|
3
|
+
module Applitools
|
4
|
+
module Calabash
|
5
|
+
module FullPageCaptureAlgorithm
|
6
|
+
ANDROID_ALGORITHMS = {
|
7
|
+
'android.widget.ScrollView' => Applitools::Calabash::FullPageCaptureAlgorithm::AndroidScrollView
|
8
|
+
}.freeze
|
9
|
+
IOS_ALGORITHMS = {
|
10
|
+
'UITableView' => Applitools::Calabash::FullPageCaptureAlgorithm::IosUITableView
|
11
|
+
}.freeze
|
12
|
+
class << self
|
13
|
+
def get_algorithm_class(env, klass)
|
14
|
+
case env
|
15
|
+
when :ios
|
16
|
+
IOS_ALGORITHMS[klass]
|
17
|
+
when :android
|
18
|
+
ANDROID_ALGORITHMS[klass]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
module Applitools
|
3
|
+
module Calabash
|
4
|
+
module FullPageCaptureAlgorithm
|
5
|
+
class AndroidScrollView < Base
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
@entire_content = nil
|
9
|
+
@stitched_image = nil
|
10
|
+
@original_position = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_stitched_region(scroll_to_top = true)
|
14
|
+
create_entire_image
|
15
|
+
store_original_position
|
16
|
+
scroll_top if scroll_to_top
|
17
|
+
|
18
|
+
scroll_it! do |scrollable_element|
|
19
|
+
put_it_on_canvas!(
|
20
|
+
screenshot_provider.capture_screenshot.sub_screenshot(
|
21
|
+
eyes_window,
|
22
|
+
Applitools::Calabash::EyesCalabashScreenshot::DRIVER,
|
23
|
+
false,
|
24
|
+
false
|
25
|
+
).image.image,
|
26
|
+
element.location.offset_negative(scrollable_element.location)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
restore_original_position
|
31
|
+
|
32
|
+
Applitools::Calabash::EyesCalabashAndroidScreenshot.new(
|
33
|
+
Applitools::Screenshot.from_image(stitched_image),
|
34
|
+
density: screenshot_provider.density
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def scrollable_element
|
41
|
+
child_query = "#{element.element_query} child * index:0"
|
42
|
+
Applitools::Calabash::Utils.get_android_element(context, child_query, 0)
|
43
|
+
end
|
44
|
+
|
45
|
+
def scroll_top
|
46
|
+
logger.info 'Scrolling up...'
|
47
|
+
context.query(element.element_query, scrollTo: [0, 0])
|
48
|
+
logger.info 'Done!'
|
49
|
+
end
|
50
|
+
|
51
|
+
def store_original_position
|
52
|
+
current_scrollable_element = scrollable_element
|
53
|
+
@original_position = Applitools::Location.new(current_scrollable_element.left, current_scrollable_element.top)
|
54
|
+
end
|
55
|
+
|
56
|
+
def restore_original_position
|
57
|
+
return unless @original_position
|
58
|
+
offset = @original_position.offset_negative(scrollable_element.location)
|
59
|
+
context.query(element.element_query, scrollBy: [-offset.x, -offset.y])
|
60
|
+
@original_position = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def scroll_it!
|
64
|
+
scroll_vertical = true
|
65
|
+
loop do
|
66
|
+
scrollable = scrollable_element if scroll_vertical
|
67
|
+
vertical_offset = element.location.offset_negative(scrollable.location).top
|
68
|
+
scroll_vertical = false if vertical_offset + 1 >= scrollable.height - element.height
|
69
|
+
yield(scrollable) if block_given?
|
70
|
+
context.query(element.element_query, scrollBy: [0, element.height]) if scroll_vertical
|
71
|
+
return unless scroll_vertical
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def entire_size
|
76
|
+
entire_content.size
|
77
|
+
end
|
78
|
+
|
79
|
+
def eyes_window
|
80
|
+
@eyes_window ||= Applitools::Region.from_location_size(element.location, element.size)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Applitools
|
2
|
+
module Calabash
|
3
|
+
module FullPageCaptureAlgorithm
|
4
|
+
class Base
|
5
|
+
extend Applitools::Helpers
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
|
8
|
+
|
9
|
+
attr_reader :context, :element, :screenshot_provider, :stitched_image, :debug_screenshot_provider
|
10
|
+
|
11
|
+
DEFAULT_SLEEP_INTERVAL = 1
|
12
|
+
|
13
|
+
def initialize(screenshot_provider, element, options = {})
|
14
|
+
Applitools::ArgumentGuard.is_a?(element, 'element', Applitools::Calabash::CalabashElement)
|
15
|
+
@screenshot_provider = screenshot_provider
|
16
|
+
@element = element
|
17
|
+
@context = screenshot_provider.context
|
18
|
+
return unless options[:debug_screenshot_provider]
|
19
|
+
Applitools::ArgumentGuard.is_a?(
|
20
|
+
options[:debug_screenshot_provider],
|
21
|
+
'options[:debug_screenshot_provider]',
|
22
|
+
Applitools::DebugScreenshotProvider
|
23
|
+
)
|
24
|
+
@debug_screenshot_provider = options[:debug_screenshot_provider]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def create_entire_image
|
30
|
+
current_entire_size = entire_size
|
31
|
+
@stitched_image = ::ChunkyPNG::Image.new(current_entire_size.width, current_entire_size.height)
|
32
|
+
end
|
33
|
+
|
34
|
+
def entire_content
|
35
|
+
@entire_content ||= scrollable_element
|
36
|
+
end
|
37
|
+
|
38
|
+
abstract_method(:entire_size, true)
|
39
|
+
abstract_method(:scrollable_element, true)
|
40
|
+
abstract_method(:eyes_window, true)
|
41
|
+
|
42
|
+
def put_it_on_canvas!(image, offset)
|
43
|
+
debug_screenshot_provider.save(image, "#{offset.x} x #{offset.y}") if debug_screenshot_provider
|
44
|
+
stitched_image.replace!(image, offset.x, offset.y)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module Applitools
|
4
|
+
module Calabash
|
5
|
+
module FullPageCaptureAlgorithm
|
6
|
+
class IosUITableView < Base
|
7
|
+
NSSIZE = /^.*: {(?<width>\d+), (?<height>\d+)}$/
|
8
|
+
NEXT_REQUEST_DELAY = 0.5
|
9
|
+
CROP_SHADOW_BOUNDS = 5
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super
|
13
|
+
@entire_content = nil
|
14
|
+
@stitched_image = nil
|
15
|
+
@original_position = nil
|
16
|
+
@entire_size = nil
|
17
|
+
@eyes_window = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_stitched_region(scroll_to_top = true)
|
21
|
+
create_entire_image
|
22
|
+
store_original_position
|
23
|
+
scroll_left_top if scroll_to_top
|
24
|
+
|
25
|
+
scroll_it! do |position, cut_vertical, cut_horizontal|
|
26
|
+
current_region = cutted_eyes_window(cut_vertical ? :top : :none)
|
27
|
+
current_region = cutted_eyes_window(cut_horizontal ? :left : :none, current_region)
|
28
|
+
|
29
|
+
current_position = updated_position(cut_vertical ? :top : :none, position.dup)
|
30
|
+
current_position = updated_position(cut_horizontal ? :left : :none, current_position)
|
31
|
+
|
32
|
+
put_it_on_canvas!(
|
33
|
+
screenshot_provider.capture_screenshot.sub_screenshot(
|
34
|
+
current_region,
|
35
|
+
Applitools::Calabash::EyesCalabashScreenshot::DRIVER,
|
36
|
+
false,
|
37
|
+
false
|
38
|
+
).image.image,
|
39
|
+
current_position.scale_it!(screenshot_provider.density)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
Applitools::Calabash::EyesCalabashIosScreenshot.new(
|
44
|
+
Applitools::Screenshot.from_image(stitched_image),
|
45
|
+
scale_factor: screenshot_provider.density
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def cutted_eyes_window(side, region = eyes_window)
|
50
|
+
case side
|
51
|
+
when :top
|
52
|
+
Applitools::Region.new(
|
53
|
+
region.left,
|
54
|
+
region.top + CROP_SHADOW_BOUNDS,
|
55
|
+
region.width,
|
56
|
+
region.height - CROP_SHADOW_BOUNDS
|
57
|
+
)
|
58
|
+
when :left
|
59
|
+
Applitools::Region.new(
|
60
|
+
region.left + CROP_SHADOW_BOUNDS,
|
61
|
+
region.top,
|
62
|
+
region.width - CROP_SHADOW_BOUNDS,
|
63
|
+
region.height
|
64
|
+
)
|
65
|
+
else
|
66
|
+
region.dup
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def updated_position(side, position)
|
71
|
+
case side
|
72
|
+
when :top
|
73
|
+
position.offset Applitools::Location.new(0, CROP_SHADOW_BOUNDS)
|
74
|
+
when :left
|
75
|
+
position.offset Applitools::Location.new(CROP_SHADOW_BOUNDS, 0)
|
76
|
+
else
|
77
|
+
position
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def scrollable_element
|
82
|
+
Applitools::Calabash::Utils.get_ios_element(context, element.element_query, 0)
|
83
|
+
end
|
84
|
+
|
85
|
+
def entire_size
|
86
|
+
@entire_size ||= query_entire_size
|
87
|
+
end
|
88
|
+
|
89
|
+
def query_entire_size
|
90
|
+
result = NSSIZE.match(Applitools::Calabash::Utils.request_element(context, element, :contentSize).first)
|
91
|
+
Applitools::RectangleSize.new(
|
92
|
+
result[:width].to_i, result[:height].to_i
|
93
|
+
).scale_it!(screenshot_provider.density)
|
94
|
+
end
|
95
|
+
|
96
|
+
def query_current_position
|
97
|
+
result = Applitools::Calabash::Utils.request_element(context, element, :contentOffset).first
|
98
|
+
Applitools::Location.new(
|
99
|
+
result['X'].to_i,
|
100
|
+
result['Y'].to_i
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def store_original_position
|
105
|
+
current_scrollable_element = Applitools::Calabash::Utils.request_element(context, element, :contentSize).first
|
106
|
+
@original_position = Applitools::Location.new(
|
107
|
+
current_scrollable_element['X'],
|
108
|
+
current_scrollable_element['Y']
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def restore_original_position; end
|
113
|
+
|
114
|
+
def scroll_it!
|
115
|
+
scroll_in_one_dimension(:down) do |pos, is_first_vertical|
|
116
|
+
scroll_in_one_dimension(:right, pos) do |position, is_first_horizontal|
|
117
|
+
yield(position, !is_first_vertical, !is_first_horizontal) if block_given?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def scroll_in_one_dimension(direction, position = nil)
|
123
|
+
previous_position = position || query_current_position
|
124
|
+
is_first = true
|
125
|
+
loop do
|
126
|
+
yield(previous_position, is_first) if block_given?
|
127
|
+
is_first = false if is_first
|
128
|
+
context.scroll(element.element_query, direction)
|
129
|
+
sleep NEXT_REQUEST_DELAY
|
130
|
+
new_position = query_current_position
|
131
|
+
return if previous_position == new_position
|
132
|
+
previous_position = new_position
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def scroll_left_top
|
137
|
+
scroll_in_one_dimension(:up) do |position, _is_first, _direction|
|
138
|
+
scroll_in_one_dimension(:left, position)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def eyes_window
|
143
|
+
@eyes_window ||= Applitools::Region.from_location_size(element.location, element.size)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Applitools
|
2
|
+
module Calabash
|
3
|
+
module OsVersions
|
4
|
+
module Android
|
5
|
+
extend self
|
6
|
+
API_TO_NAME = {
|
7
|
+
4 => 'Android 1',
|
8
|
+
10 => 'Android 2',
|
9
|
+
13 => 'Android 3',
|
10
|
+
20 => 'Android 4',
|
11
|
+
22 => 'Android 5',
|
12
|
+
23 => 'Android 6',
|
13
|
+
25 => 'Android 7',
|
14
|
+
27 => 'Android 8'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def os_version(api_level)
|
18
|
+
API_TO_NAME[API_TO_NAME.keys.select { |v| api_level.to_i >= v }.last] || 'Android'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
|
3
|
+
RSpec::Matchers.define :match_baseline do |expected|
|
4
|
+
match do |actual|
|
5
|
+
result = Applitools::Calabash::EyesSettings.instance.eyes.check(expected, actual)
|
6
|
+
result.as_expected?
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message do |_actual|
|
10
|
+
"expected #{expected} to match a baseline"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec::Matchers.define :be_success do
|
15
|
+
match do |actual|
|
16
|
+
actual.as_expected? == true
|
17
|
+
end
|
18
|
+
|
19
|
+
failure_message do |actual|
|
20
|
+
"Expected eyes session to be successful, but mismatches occur. \n#{actual}"
|
21
|
+
end
|
22
|
+
end
|