eyes_core 3.14.0 → 3.14.1

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