eyes_core 3.14.0 → 3.14.1

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/applitools/calabash/calabash_element.rb +62 -0
  3. data/lib/applitools/calabash/calabash_screenshot_provider.rb +81 -0
  4. data/lib/applitools/calabash/environment_detector.rb +23 -0
  5. data/lib/applitools/calabash/eyes.rb +182 -0
  6. data/lib/applitools/calabash/eyes_calabash_android_screenshot.rb +56 -0
  7. data/lib/applitools/calabash/eyes_calabash_ios_screenshot.rb +28 -0
  8. data/lib/applitools/calabash/eyes_calabash_screenshot.rb +78 -0
  9. data/lib/applitools/calabash/eyes_hooks.rb +51 -0
  10. data/lib/applitools/calabash/eyes_settings.rb +43 -0
  11. data/lib/applitools/calabash/full_page_capture_algorithm.rb +24 -0
  12. data/lib/applitools/calabash/full_page_capture_algorithm/android_scroll_view.rb +85 -0
  13. data/lib/applitools/calabash/full_page_capture_algorithm/base.rb +49 -0
  14. data/lib/applitools/calabash/full_page_capture_algorithm/ios_ui_table_view.rb +148 -0
  15. data/lib/applitools/calabash/os_versions.rb +23 -0
  16. data/lib/applitools/calabash/rspec_matchers.rb +22 -0
  17. data/lib/applitools/calabash/steps/android_eyes_session.rb +35 -0
  18. data/lib/applitools/calabash/steps/android_matchers.rb +34 -0
  19. data/lib/applitools/calabash/steps/eyes_session.rb +40 -0
  20. data/lib/applitools/calabash/steps/eyes_settings.rb +57 -0
  21. data/lib/applitools/calabash/steps/ios_eyes_session.rb +13 -0
  22. data/lib/applitools/calabash/steps/ios_matchers.rb +15 -0
  23. data/lib/applitools/calabash/steps/matchers.rb +69 -0
  24. data/lib/applitools/calabash/target.rb +67 -0
  25. data/lib/applitools/calabash/utils.rb +72 -0
  26. data/lib/applitools/calabash_steps.rb +14 -0
  27. data/lib/applitools/chunky_png_patch.rb +1 -0
  28. data/lib/applitools/connectivity/server_connector.rb +5 -4
  29. data/lib/applitools/core/abstract_region.rb +16 -0
  30. data/lib/applitools/core/class_name.rb +7 -0
  31. data/lib/applitools/core/eyes_base.rb +2 -0
  32. data/lib/applitools/core/floating_region.rb +17 -4
  33. data/lib/applitools/core/fluent_interface.rb +8 -0
  34. data/lib/applitools/core/location.rb +7 -0
  35. data/lib/applitools/core/match_window_data.rb +1 -1
  36. data/lib/applitools/core/rectangle_size.rb +8 -2
  37. data/lib/applitools/core/region.rb +9 -1
  38. data/lib/applitools/rspec/target_matcher.rb +23 -0
  39. data/lib/applitools/utils/eyes_selenium_utils.rb +0 -2
  40. data/lib/applitools/version.rb +1 -1
  41. metadata +30 -2
@@ -0,0 +1,78 @@
1
+ module Applitools
2
+ module Calabash
3
+ class EyesCalabashScreenshot < Applitools::EyesScreenshot
4
+ SCREENSHOT_AS_IS = Applitools::EyesScreenshot::COORDINATE_TYPES[:screenshot_as_is].freeze
5
+ CONTEXT_RELATIVE = Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative].freeze
6
+ DRIVER = 'DRIVER'.freeze
7
+
8
+ attr_reader :scale_factor
9
+
10
+ def initialize(image, options = {})
11
+ super image
12
+ @scale_factor = options[:scale_factor] || 1
13
+ # return if (location = options[:location]).nil?
14
+ # Applitools::ArgumentGuard.is_a? location, 'options[:location]', Applitools::Location
15
+ # @bounds = Applitools::Region.new location.x, location.y, image.width, image.height
16
+ end
17
+
18
+ def sub_screenshot(region, coordinates_type, throw_if_clipped = false, force_nil_if_clipped = false)
19
+ Applitools::ArgumentGuard.not_nil region, 'region'
20
+ Applitools::ArgumentGuard.not_nil coordinates_type, 'coordinates_type'
21
+
22
+ sub_screen_region = intersected_region region, coordinates_type, SCREENSHOT_AS_IS
23
+
24
+ if sub_screen_region.empty? || (throw_if_clipped && !region.size_equals?(sub_screen_region))
25
+ return nil if force_nil_if_clipped
26
+ raise Applitools::OutOfBoundsException, "Region #{sub_screen_region} (#{coordinates_type}) is out of " \
27
+ " screenshot bounds #{bounds}"
28
+ end
29
+
30
+ sub_screenshot_image = Applitools::Screenshot.from_any_image(
31
+ image.crop(
32
+ sub_screen_region.left, sub_screen_region.top, sub_screen_region.width, sub_screen_region.height
33
+ ).to_datastream.to_blob
34
+ )
35
+
36
+ self.class.new sub_screenshot_image, scale_factor: scale_factor
37
+ end
38
+
39
+ def location_in_screenshot(_location, _coordinates_type)
40
+ raise(
41
+ Applitools::EyesError,
42
+ 'Call to :convert_location is prohibited for Applitools::Calabash::EyesCalabashScreenshot'
43
+ )
44
+ end
45
+
46
+ def intersected_region(region, from, to = CONTEXT_RELATIVE)
47
+ Applitools::ArgumentGuard.not_nil region, 'region'
48
+ Applitools::ArgumentGuard.not_nil from, 'coordinates Type (from)'
49
+ return Applitools::Region.new(0, 0, 0, 0) if region.empty?
50
+ intersected_region = convert_region_location region, from, to
51
+ intersected_region.intersect bounds
52
+ intersected_region
53
+ end
54
+
55
+ def convert_location(_location, _from, _to)
56
+ raise(
57
+ Applitools::EyesError,
58
+ 'Call to :convert_location is prohibited for Applitools::Calabash::EyesCalabashScreenshot'
59
+ )
60
+ end
61
+
62
+ abstract_method :convert_region_location, false
63
+
64
+ def scale_it!
65
+ width = (image.width.to_f / scale_factor).to_i
66
+ height = (image.height.to_f / scale_factor).to_i
67
+ image.resample_bicubic!(width, height)
68
+ self
69
+ end
70
+
71
+ private
72
+
73
+ def bounds
74
+ @bounds ||= Applitools::Region.new(0, 0, image.width, image.height)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -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