eyes_selenium 3.14.3 → 3.14.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.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/lib/applitools/selenium/border_aware_element_content_location_provider.rb +2 -0
  3. data/lib/applitools/selenium/browser.rb +66 -56
  4. data/lib/applitools/selenium/context_based_scale_provider.rb +2 -0
  5. data/lib/applitools/selenium/css_transform/css_transform.rb +21 -0
  6. data/lib/applitools/selenium/css_translate_element_position_provider.rb +48 -0
  7. data/lib/applitools/selenium/css_translate_position_provider.rb +29 -10
  8. data/lib/applitools/selenium/dom_capture/dom_capture.rb +113 -0
  9. data/lib/applitools/selenium/dom_capture/dom_capture_script.rb +103 -0
  10. data/lib/applitools/selenium/driver.rb +9 -2
  11. data/lib/applitools/selenium/element.rb +2 -0
  12. data/lib/applitools/selenium/element_position_provider.rb +3 -1
  13. data/lib/applitools/selenium/entire_element_screenshot.rb +20 -0
  14. data/lib/applitools/selenium/eyes.rb +203 -396
  15. data/lib/applitools/selenium/eyes_screenshot.rb +60 -0
  16. data/lib/applitools/selenium/eyes_target_locator.rb +12 -24
  17. data/lib/applitools/selenium/fixed_cut_provider.rb +48 -0
  18. data/lib/applitools/selenium/frame.rb +11 -1
  19. data/lib/applitools/selenium/frame_chain.rb +9 -1
  20. data/lib/applitools/selenium/full_page_capture_algorithm.rb +85 -50
  21. data/lib/applitools/selenium/fullpage_screenshot.rb +37 -0
  22. data/lib/applitools/selenium/keyboard.rb +2 -0
  23. data/lib/applitools/selenium/mouse.rb +2 -0
  24. data/lib/applitools/selenium/move_to_region_visibility_strategy.rb +2 -0
  25. data/lib/applitools/selenium/nop_region_visibility_strategy.rb +2 -0
  26. data/lib/applitools/selenium/region_provider.rb +91 -0
  27. data/lib/applitools/selenium/sauce/driver.rb +2 -0
  28. data/lib/applitools/selenium/scroll_position_provider.rb +13 -2
  29. data/lib/applitools/selenium/takes_screenshot_image_provider.rb +5 -3
  30. data/lib/applitools/selenium/target.rb +32 -6
  31. data/lib/applitools/selenium/viewport_screenshot.rb +46 -0
  32. data/lib/applitools/selenium/viewport_size.rb +2 -0
  33. data/lib/applitools/version.rb +3 -1
  34. data/lib/eyes_selenium.rb +4 -9
  35. metadata +16 -27
  36. data/lib/applitools/capybara.rb +0 -8
  37. data/lib/applitools/poltergeist/applitools_compatible.rb +0 -32
  38. data/lib/applitools/poltergeist/driver.rb +0 -11
  39. data/lib/applitools/selenium/capybara/capybara_settings.rb +0 -23
  40. data/lib/applitools/selenium/capybara/driver.rb +0 -37
  41. data/lib/applitools/selenium/eyes_full_page_screenshot.rb +0 -47
  42. data/lib/applitools/selenium/eyes_web_driver_screenshot.rb +0 -341
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Applitools
4
+ module Selenium
5
+ class EyesScreenshot < Applitools::EyesScreenshot
6
+ attr_accessor :driver, :region_provider
7
+ def_delegators '@driver', :frame_chain
8
+
9
+ def initialize(image, options = {})
10
+ super image
11
+ self.region_provider = options[:region_provider] if options[:region_provider]
12
+ Applitools::ArgumentGuard.is_a?(
13
+ region_provider,
14
+ 'options[:region_provider]',
15
+ Applitools::Selenium::RegionProvider
16
+ )
17
+ self.driver = region_provider.driver
18
+ end
19
+
20
+ def convert_location(location, _from, _to)
21
+ location.offset_negative(top_left_location)
22
+ end
23
+
24
+ # Returns the location in the screenshot.
25
+ #
26
+ # @param [Applitools::Location] location The location.
27
+ # @param [Applitools::EyesScreenshot::COORDINATE_TYPES] coordinate_type The type of the coordinate.
28
+ # @return [Applitools::Location] The location instance in the screenshot.
29
+ def location_in_screenshot(location, coordinate_type)
30
+ raise Applitools::Selenium::UnsupportedCoordinateType, coordinate_type unless
31
+ coordinate_type == Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative]
32
+
33
+ location = convert_location(
34
+ location, coordinate_type, Applitools::EyesScreenshot::COORDINATE_TYPES[:screenshot_as_is]
35
+ )
36
+
37
+ unless image_region.contains?(location.x, location.y)
38
+ raise Applitools::OutOfBoundsException,
39
+ "Location #{location} (#{coordinate_type}) is not visible in screenshot!"
40
+ end
41
+ location
42
+ end
43
+
44
+ def intersected_region(region, original_coordinate_types, result_coordinate_types)
45
+ raise Applitools::Selenium::UnsupportedCoordinateType, original_coordinate_types unless
46
+ original_coordinate_types == Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative]
47
+ raise Applitools::Selenium::UnsupportedCoordinateType, result_coordinate_types unless
48
+ result_coordinate_types == Applitools::EyesScreenshot::COORDINATE_TYPES[:screenshot_as_is]
49
+
50
+ return Applitools::Region::EMPTY if region.empty?
51
+
52
+ intersected_region = convert_region_location(
53
+ region, original_coordinate_types, Applitools::EyesScreenshot::COORDINATE_TYPES[:screenshot_as_is]
54
+ )
55
+
56
+ intersected_region.intersect(image_region)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Applitools::Selenium
2
4
  class EyesTargetLocator < SimpleDelegator
3
5
  extend Forwardable
@@ -38,19 +40,11 @@ module Applitools::Selenium
38
40
  end
39
41
  when Applitools::Selenium::Element
40
42
  frame_by_frame_element(value)
43
+ when String
44
+ frame_by_name_or_id(value)
41
45
  else
42
- raise Applitools::EyesError 'Unknown frame selector to switch!'
46
+ raise Applitools::EyesError, 'Unknown frame selector to switch!'
43
47
  end
44
- # require 'pry'
45
- # binding.pry
46
- # raise Applitools::EyesIllegalArgument.new 'You must pass :index or :name_or_id or :frame_element option' unless
47
- # options[:index] || options[:name_or_id] || options[:frame_element]
48
- # if (needed_keys = (options.keys & [:index, :name_or_id, :frame_element])).length == 1
49
- # send "frame_by_#{needed_keys.first}", options[needed_keys.first]
50
- # else
51
- # raise Applitools::EyesIllegalArgument.new 'You\'ve passed some extra keys!' /
52
- # 'Only :index, :name_or_id or :frame_elenent are allowed.'
53
- # end
54
48
  end
55
49
 
56
50
  # Switches to parent frame.
@@ -105,7 +99,7 @@ module Applitools::Selenium
105
99
  def window(name_or_handle)
106
100
  logger.info 'EyesTargetLocator.window()'
107
101
  logger.info 'Making preparaions...'
108
- on_will_switch.will_switch_to_window name_or_handle
102
+ # on_will_switch.will_switch_to_window name_or_handle
109
103
  logger.info 'Done! Switching to window..'
110
104
  __getobj__.window name_or_handle
111
105
  logger.info 'Done!'
@@ -170,20 +164,14 @@ module Applitools::Selenium
170
164
 
171
165
  def frame_by_name_or_id(name_or_id)
172
166
  logger.info "EyesTargetLocator.frame(#{name_or_id})"
173
- # Finding the target element so we can report it.
174
- # We use find elements(plural) to avoid exception when the element
175
- # is not found.
176
- logger.info 'Getting frames by name...'
177
- frames = driver.find_elements :name, name_or_id
178
- if frames.empty?
179
- logger.info 'No frames found! Trying by id...'
180
- frames = driver.find_elements :id, name_or_id
181
- raise Applitools::EyesNoSuchFrame.new "No frame with name or id #{name_or_id} exists!" if frames.empty?
182
- end
167
+
168
+ logger.info 'Getting frame by name or id...'
169
+ target_frame = driver.find_element(name_or_id: name_or_id)
170
+
183
171
  logger.info 'Done! Making preparations...'
184
- on_will_switch.will_switch_to_frame(:frame, frames.first)
172
+ on_will_switch.will_switch_to_frame(:frame, target_frame)
185
173
  logger.info 'Done! Switching to frame...'
186
- __getobj__.frame frames.first
174
+ __getobj__.frame target_frame
187
175
 
188
176
  logger.info 'Done!'
189
177
  driver
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Applitools
4
+ module Selenium
5
+ class FixedCutProvider
6
+ class << self
7
+ def viewport(image, viewport_size, region_to_check)
8
+ return nil if image.square == viewport_size.square
9
+ current_scroll_position = region_to_check.scroll_position_provider.current_position
10
+ image_canvas = Applitools::Region.new(0, 0, image.width, image.height)
11
+ viewport_frame = image_canvas.dup.intersect(
12
+ Applitools::Region.from_location_size(
13
+ Applitools::Location.new(current_scroll_position.left, current_scroll_position.top),
14
+ viewport_size
15
+ )
16
+ )
17
+ new(
18
+ viewport_frame.left,
19
+ viewport_frame.top,
20
+ image_canvas.right - viewport_frame.right,
21
+ image_canvas.bottom - viewport_frame.bottom
22
+ )
23
+ end
24
+ end
25
+
26
+ def initialize(left, header, right, footer)
27
+ Applitools::ArgumentGuard.is_a?(left, 'left', Integer)
28
+ Applitools::ArgumentGuard.is_a?(header, 'header', Integer)
29
+ Applitools::ArgumentGuard.is_a?(right, 'right', Integer)
30
+ Applitools::ArgumentGuard.is_a?(footer, 'footer', Integer)
31
+ self.left = left
32
+ self.header = header
33
+ self.right = right
34
+ self.footer = footer
35
+ end
36
+
37
+ def cut(image)
38
+ crop_width = image.width - left - right
39
+ crop_height = image.height - header - footer
40
+ image.crop!(left, header, crop_width, crop_height)
41
+ end
42
+
43
+ private
44
+
45
+ attr_accessor :left, :header, :right, :footer
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Applitools::Selenium
2
4
  # @!visibility private
3
5
  class Frame
@@ -8,7 +10,7 @@ module Applitools::Selenium
8
10
  send("#{param}=", options[param])
9
11
  end
10
12
 
11
- raise 'options[:location] must be instance of Applitools::Base::Point' unless
13
+ raise 'options[:location] must be instance of Applitools::Base::Location' unless
12
14
  location.is_a? Applitools::Location
13
15
 
14
16
  raise 'options[:parent_scroll_position] must be instance of Applitools::Location' unless
@@ -20,5 +22,13 @@ module Applitools::Selenium
20
22
  return if reference.is_a? Applitools::Selenium::Element
21
23
  raise 'options[:reference] must be instance of Applitools::Selenium::Element'
22
24
  end
25
+
26
+ def dup
27
+ super.tap do |r|
28
+ r.location = location.dup
29
+ r.size = size.dup
30
+ r.parent_scroll_position = parent_scroll_position.dup
31
+ end
32
+ end
23
33
  end
24
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Applitools::Selenium
2
4
  # @!visibility private
3
5
  class FrameChain
@@ -15,7 +17,7 @@ module Applitools::Selenium
15
17
 
16
18
  def same_frame_chain?(other)
17
19
  return false unless size == other.size
18
- all? { |my_elem| my_elem.id == other.next.id }
20
+ ids == other.ids
19
21
  end
20
22
 
21
23
  def push(frame)
@@ -71,5 +73,11 @@ module Applitools::Selenium
71
73
  end
72
74
 
73
75
  class NoFramesException < RuntimeError; end
76
+
77
+ protected
78
+
79
+ def ids
80
+ map { |i| i.reference.ref }
81
+ end
74
82
  end
75
83
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Applitools::Selenium
2
4
  # @!visibility private
3
5
  class FullPageCaptureAlgorithm
@@ -8,6 +10,7 @@ module Applitools::Selenium
8
10
 
9
11
  MAX_SCROLL_BAR_SIZE = 50
10
12
  MIN_SCREENSHOT_PART_HEIGHT = 10
13
+ MIN_SCREENSHOT_PART_WIDTH = 10
11
14
 
12
15
  def initialize(options = {})
13
16
  @debug_screenshot_provider = options[:debug_screenshot_provider] ||
@@ -37,34 +40,28 @@ module Applitools::Selenium
37
40
  wait_before_screenshot = options[:wait_before_screenshots]
38
41
  eyes_screenshot_factory = options[:eyes_screenshot_factory]
39
42
  stitching_overlap = options[:stitching_overlap] || MAX_SCROLL_BAR_SIZE
43
+ top_left_position = options[:top_left_position] || Applitools::Location::TOP_LEFT
40
44
 
41
- logger.info "Region to check: #{region_provider.region}"
45
+ logger.info "Region to check: #{region_provider.region(false)}"
42
46
  logger.info "Coordinates type: #{region_provider.coordinate_type}"
43
47
 
44
48
  original_position = origin_provider.state
45
49
  current_position = nil
46
50
  set_position_retries = 3
47
51
  while current_position.nil? ||
48
- (current_position.x.nonzero? || current_position.y.nonzero?) && set_position_retries > 0
49
- origin_provider.position = Applitools::Location.new(0, 0)
52
+ (current_position == top_left_position) && set_position_retries > 0
53
+ origin_provider.position = top_left_position
50
54
  sleep wait_before_screenshot
51
55
  current_position = origin_provider.current_position
52
56
  set_position_retries -= 1
53
57
  end
54
58
 
55
- unless current_position.x.zero? && current_position.y.zero?
59
+ unless current_position == top_left_position
56
60
  origin_provider.restore_state original_position
57
61
  raise Applitools::EyesError.new 'Couldn\'t set position to the top/left corner!'
58
62
  end
59
63
 
60
- begin
61
- entire_size = position_provider.entire_size
62
- logger.info "Entire size of region context: #{entire_size}"
63
- rescue Applitools::EyesDriverOperationException => e
64
- logger.error "Failed to extract entire size of region context: #{e.message}"
65
- logger.error "Using image size instead: #{image.width}x#{image.height}"
66
- entire_size = Applitools::RectangleSize.new image.width, image.height
67
- end
64
+ logger.info "Current position: #{current_position}"
68
65
 
69
66
  logger.info 'Getting top/left image...'
70
67
  image = image_provider.take_screenshot
@@ -76,44 +73,45 @@ module Applitools::Selenium
76
73
  logger.info 'Done! Creating screenshot object...'
77
74
  screenshot = eyes_screenshot_factory.call(image)
78
75
 
79
- if region_provider.coordinate_type
80
- left_top_image = screenshot.sub_screenshot(region_provider.region, region_provider.coordinate_type)
81
- debug_screenshot_provider.save_subscreenshot(left_top_image, region_provider.region)
76
+ begin
77
+ entire_size = position_provider.entire_size(image.width, image.height)
78
+ rescue Applitools::EyesDriverOperationException => e
79
+ logger.error "Failed to extract entire size of region context: #{e.message}"
80
+ logger.error "Using image size instead: #{image.width}x#{image.height}"
81
+ entire_size = Applitools::RectangleSize.new image.width, image.height
82
+ end
83
+
84
+ if region_provider.region(false).size.square > 0
85
+ left_top_image = screenshot.sub_screenshot(region_provider.region(false), region_provider.coordinate_type).image
82
86
  else
83
- left_top_image = screenshot.sub_screenshot(
84
- Applitools::Region.from_location_size(Applitools::Location.new(0, 0), entire_size),
85
- Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative]
86
- )
87
- debug_screenshot_provider.save_subscreenshot(
88
- left_top_image,
89
- Applitools::Region.from_location_size(Applitools::Location.new(0, 0), entire_size)
90
- )
87
+ left_top_image = screenshot.image
91
88
  end
89
+ debug_screenshot_provider.save_subscreenshot(left_top_image, region_provider.region(false))
92
90
 
93
91
  image = left_top_image.image
94
92
 
95
- # Notice that this might still happen even if we used
96
- # "getImagePart", since "entirePageSize" might be that of a frame.
97
-
98
- if image.width >= entire_size.width && image.height >= entire_size.height
99
- origin_provider.restore_state original_position
100
- return image
101
- end
102
-
103
- part_image_size = Applitools::RectangleSize.new image.width,
93
+ part_image_size = Applitools::RectangleSize.new(
94
+ [image.width - stitching_overlap, MIN_SCREENSHOT_PART_WIDTH].max,
104
95
  [image.height - stitching_overlap, MIN_SCREENSHOT_PART_HEIGHT].max
96
+ )
105
97
 
106
98
  logger.info "Total size: #{entire_size}, image_part_size: #{part_image_size}"
107
99
 
108
100
  # Getting the list of sub-regions composing the whole region (we'll
109
101
  # take screenshot for each one).
110
102
  entire_page = Applitools::Region.from_location_size Applitools::Location::TOP_LEFT, entire_size
103
+
104
+ # Notice that this might still happen even if we used
105
+ # "getImagePart", since "entirePageSize" might be that of a frame.
106
+ if image.width >= entire_size.width && image.height >= entire_size.height
107
+ origin_provider.restore_state original_position
108
+ return left_top_image
109
+ end
110
+
111
111
  image_parts = entire_page.sub_regions(part_image_size)
112
112
 
113
113
  logger.info "Creating stitchedImage container. Size: #{entire_size}"
114
114
 
115
- # Notice stitched_image uses the same type of image as the screenshots.
116
- # stitched_image = Applitools::Screenshot.from_region entire_size
117
115
  stitched_image = ::ChunkyPNG::Image.new(entire_size.width, entire_size.height)
118
116
  logger.info 'Done! Adding initial screenshot..'
119
117
  logger.info "Initial part:(0,0) [#{image.width} x #{image.height}]"
@@ -126,13 +124,29 @@ module Applitools::Selenium
126
124
 
127
125
  original_stitched_state = position_provider.state
128
126
 
127
+ stitched_image_region = Applitools::Region.new(
128
+ 0,
129
+ 0,
130
+ stitched_image.width,
131
+ stitched_image.height
132
+ )
133
+
129
134
  logger.info 'Getting the rest of the image parts...'
130
135
 
131
- image_parts.each_with_index do |part_region, i|
136
+ # rubocop:disable Metrics/BlockLength
137
+ image_parts.each_with_index do |part_regions, i|
132
138
  next unless i > 0
139
+
140
+ part_region = part_regions.first
141
+ intersection = part_regions.last
142
+
133
143
  logger.info "Taking screenshot for #{part_region}"
134
144
 
135
- position_provider.position = part_region.location
145
+ position_provider.position = part_region
146
+ .location
147
+ .offset(top_left_position)
148
+ .offset_negative(intersection.location)
149
+
136
150
  sleep wait_before_screenshot
137
151
  current_position = position_provider.current_position
138
152
  logger.info "Set position to #{current_position}"
@@ -145,30 +159,51 @@ module Applitools::Selenium
145
159
  debug_screenshot_provider.save(part_image, 'cutted')
146
160
 
147
161
  logger.info 'Done!'
148
- begin
149
- region_to_check = Applitools::Region.from_location_size(
150
- part_region.location.offset(region_provider.region.location), part_region.size
162
+
163
+ a_screenshot = eyes_screenshot_factory.call(part_image)
164
+
165
+ if region_provider.region(false).size.square > 0
166
+ a_screenshot = a_screenshot.sub_screenshot(
167
+ region_provider.region(false),
168
+ Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative],
169
+ false
151
170
  )
152
- a_screenshot = eyes_screenshot_factory.call(part_image).sub_screenshot(region_to_check,
153
- Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative], false)
154
- debug_screenshot_provider.save_subscreenshot(a_screenshot, region_to_check)
155
- rescue Applitools::OutOfBoundsException => e
156
- logger.error e.message
157
- break
158
171
  end
159
172
 
173
+ position_to_replace = current_position.offset(intersection.location)
174
+
175
+ replacement_region = Applitools::Region.new(
176
+ position_to_replace.x,
177
+ position_to_replace.y,
178
+ a_screenshot.width - intersection.size.width,
179
+ a_screenshot.height - intersection.size.height
180
+ )
181
+
182
+ replacement_size = stitched_image_region.dup.intersect(replacement_region).size
183
+ replacement_region_in_screenshot = Applitools::Region.from_location_size(
184
+ Applitools::Location.from_any_attribute(intersection.location),
185
+ replacement_size
186
+ )
187
+
188
+ image_to_stitch = a_screenshot.sub_screenshot(
189
+ replacement_region_in_screenshot,
190
+ Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative],
191
+ false
192
+ ).image
193
+
160
194
  logger.info 'Stitching part into the image container...'
161
195
 
162
- stitched_image.replace! a_screenshot.image, part_region.x, part_region.y
196
+ stitched_image.replace! image_to_stitch, position_to_replace.x, position_to_replace.y
163
197
  logger.info 'Done!'
164
198
 
165
- last_successful_location = Applitools::Location.for part_region.x, part_region.y
166
- next unless a_screenshot
199
+ last_successful_location = Applitools::Location.for position_to_replace.x, position_to_replace.y
200
+ next unless image_to_stitch.area > 0
167
201
  last_successful_part_size = Applitools::RectangleSize.new(
168
- a_screenshot.image.width,
169
- a_screenshot.image.height
202
+ image_to_stitch.width,
203
+ image_to_stitch.height
170
204
  )
171
205
  end
206
+ # rubocop:enable Metrics/BlockLength
172
207
 
173
208
  logger.info 'Stitching done!'
174
209
 
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'eyes_screenshot'
3
+ module Applitools::Selenium
4
+ class FullpageScreenshot < Applitools::Selenium::EyesScreenshot
5
+ def sub_screenshot(region, _coordinate_type, throw_if_clipped = false, force_nil_if_clipped = false)
6
+ logger.info "get_subscreenshot(#{region}, #{throw_if_clipped})"
7
+ Applitools::ArgumentGuard.not_nil region, 'region'
8
+
9
+ as_is_subscreenshot_region = region.intersect(image_region)
10
+
11
+ if as_is_subscreenshot_region.empty? || (throw_if_clipped && as_is_subscreenshot_region.size != region.size)
12
+ return nil if force_nil_if_clipped
13
+ raise Applitools::OutOfBoundsException.new "Region #{region} is out" \
14
+ ' of screenshot bounds'
15
+ end
16
+
17
+ sub_screenshot_image = Applitools::Screenshot.from_image(
18
+ image.crop(
19
+ as_is_subscreenshot_region.left,
20
+ as_is_subscreenshot_region.top,
21
+ as_is_subscreenshot_region.width,
22
+ as_is_subscreenshot_region.height
23
+ )
24
+ )
25
+
26
+ result = self.class.new(sub_screenshot_image, region_provider: region_provider).tap do |s|
27
+ s.top_left_location = top_left_location.dup.offset(region.location)
28
+ end
29
+ logger.info 'Done!'
30
+ result
31
+ end
32
+
33
+ def top_left_location
34
+ @top_left_location ||= Applitools::Location::TOP_LEFT
35
+ end
36
+ end
37
+ end