eyes_selenium 3.14.10 → 3.15.0.beta

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/applitools/selenium/concerns/browser_types.rb +15 -0
  3. data/lib/applitools/selenium/concerns/browsers_info.rb +16 -0
  4. data/lib/applitools/selenium/concerns/external_css_resources.rb +31 -0
  5. data/lib/applitools/selenium/concerns/render_browser_info_fluent.rb +42 -0
  6. data/lib/applitools/selenium/concerns/render_resources.rb +15 -0
  7. data/lib/applitools/selenium/concerns/rgrid_dom.rb +46 -0
  8. data/lib/applitools/selenium/concerns/stitch_modes.rb +15 -0
  9. data/lib/applitools/selenium/concerns/test_list.rb +23 -0
  10. data/lib/applitools/selenium/eyes.rb +13 -854
  11. data/lib/applitools/selenium/scripts/process_page_and_serialize.rb +519 -0
  12. data/lib/applitools/selenium/selenium_configuration.rb +64 -0
  13. data/lib/applitools/selenium/selenium_eyes.rb +860 -0
  14. data/lib/applitools/selenium/target.rb +2 -2
  15. data/lib/applitools/selenium/visual_grid/eyes_connector.rb +170 -0
  16. data/lib/applitools/selenium/visual_grid/render_browser_info.rb +31 -0
  17. data/lib/applitools/selenium/visual_grid/render_info.rb +9 -0
  18. data/lib/applitools/selenium/visual_grid/render_request.rb +22 -0
  19. data/lib/applitools/selenium/visual_grid/render_requests.rb +11 -0
  20. data/lib/applitools/selenium/visual_grid/render_task.rb +171 -0
  21. data/lib/applitools/selenium/visual_grid/resource_cache.rb +51 -0
  22. data/lib/applitools/selenium/visual_grid/running_test.rb +198 -0
  23. data/lib/applitools/selenium/visual_grid/thread_pool.rb +94 -0
  24. data/lib/applitools/selenium/visual_grid/vg_resource.rb +37 -0
  25. data/lib/applitools/selenium/visual_grid/vg_task.rb +46 -0
  26. data/lib/applitools/selenium/visual_grid/visual_grid_eyes.rb +236 -0
  27. data/lib/applitools/selenium/visual_grid/visual_grid_runner.rb +57 -0
  28. data/lib/applitools/version.rb +1 -1
  29. data/lib/eyes_selenium.rb +3 -0
  30. metadata +46 -7
@@ -0,0 +1,64 @@
1
+ require 'applitools/selenium/concerns/stitch_modes'
2
+ require 'applitools/selenium/concerns/stitch_modes'
3
+ require 'applitools/selenium/concerns/browsers_info'
4
+
5
+ module Applitools
6
+ module Selenium
7
+ class SeleniumConfiguration < Applitools::EyesBaseConfiguration
8
+ DEFAULT_CONFIG = proc do
9
+ {
10
+ force_full_page_screenshot: false,
11
+ wait_before_screenshots: 100,
12
+ stitch_mode: Applitools::Selenium::Concerns::StitchModes::CSS,
13
+ hide_scrollbars: false,
14
+ hide_caret: false,
15
+ browsers_info: Applitools::Selenium::Concerns::BrowsersInfo.new
16
+ }
17
+ end
18
+ class << self
19
+ def default_config
20
+ super.merge DEFAULT_CONFIG.call
21
+ end
22
+ end
23
+
24
+ boolean_field :force_full_page_screenshot
25
+ int_field :wait_before_screenshots
26
+ enum_field :stitch_mode, Applitools::Selenium::Concerns::StitchModes.enum_values
27
+ boolean_field :hide_scrollbars
28
+ boolean_field :hide_caret
29
+ boolean_field :send_dom
30
+
31
+ object_field :browsers_info, Applitools::Selenium::Concerns::BrowsersInfo
32
+
33
+ int_field :concurrent_sessions
34
+
35
+ # private int waitBeforeScreenshots = DEFAULT_WAIT_BEFORE_SCREENSHOTS;
36
+ # private StitchMode stitchMode = StitchMode.SCROLL;
37
+ # private boolean hideScrollbars = true;
38
+ # private boolean hideCaret = true;
39
+ #
40
+ # //Rendering Configuration
41
+ # private int concurrentSessions = 3;
42
+ # private boolean isThrowExceptionOn = false;
43
+ # private Boolean isRenderingConfig = false;
44
+ #
45
+ # public enum BrowserType {CHROME, FIREFOX}
46
+ # private List<RenderBrowserInfo> browsersInfo =
47
+ def add_browser(b = nil)
48
+ browser = b || Applitools::Selenium::RenderBrowserInfo.new
49
+ yield(Applitools::Selenium::Concerns::RenderBrowserInfoFluent.new(browser)) if block_given?
50
+ browsers_info.add browser
51
+ self.viewport_size = browser.viewport_size unless viewport_size
52
+ self
53
+ end
54
+
55
+ def deep_clone
56
+ new_config = self.class.new
57
+ config_keys.each do |k|
58
+ new_config.send("#{k}=", self.send(k).clone)
59
+ end
60
+ new_config
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,860 @@
1
+ # frozen_string_literal: false
2
+
3
+ module Applitools::Selenium
4
+ # The main API gateway for the SDK
5
+ class SeleniumEyes < Applitools::EyesBase
6
+ # @!visibility private
7
+ UNKNOWN_DEVICE_PIXEL_RATIO = 0
8
+
9
+ # The pixel ratio will be used if detection of device pixel ratio is failed
10
+ DEFAULT_DEVICE_PIXEL_RATIO = 1
11
+
12
+ DEFAULT_WAIT_BEFORE_SCREENSHOTS = 0.1 # Seconds
13
+
14
+ USE_DEFAULT_MATCH_TIMEOUT = -1
15
+
16
+ DEFAULT_STITCHING_OVERLAP = 50 # Pixels
17
+
18
+ ENTIRE_ELEMENT_SCREENSHOT = 0
19
+
20
+ FULLPAGE_SCREENSHOT = 1
21
+
22
+ VIEWPORT_SCREENSHOT = 2
23
+
24
+ extend Forwardable
25
+ # @!visibility public
26
+
27
+ class << self
28
+ def eyes_driver(driver, eyes = nil)
29
+ if driver.respond_to? :driver_for_eyes
30
+ driver.driver_for_eyes eyes
31
+ else
32
+ unless driver.is_a?(Applitools::Selenium::Driver)
33
+ Applitools::EyesLogger.warn("Unrecognized driver type: (#{driver.class.name})!")
34
+ is_mobile_device = driver.respond_to?(:capabilities) && driver.capabilities['platformName']
35
+ Applitools::Selenium::Driver.new(eyes, driver: driver, is_mobile_device: is_mobile_device)
36
+ end
37
+ raise Applitools::EyesError.new "Unknown driver #{driver}!"
38
+ end
39
+ end
40
+
41
+ def obtain_screenshot_type(is_element, inside_a_frame, stitch_content, force_fullpage)
42
+ if stitch_content || force_fullpage
43
+ unless inside_a_frame
44
+ return FULLPAGE_SCREENSHOT if force_fullpage && !stitch_content
45
+ return FULLPAGE_SCREENSHOT if stitch_content && !is_element
46
+ end
47
+ return ENTIRE_ELEMENT_SCREENSHOT if inside_a_frame
48
+ return ENTIRE_ELEMENT_SCREENSHOT if stitch_content
49
+ else
50
+ return VIEWPORT_SCREENSHOT unless stitch_content || force_fullpage
51
+ end
52
+ VIEWPORT_SCREENSHOT
53
+ end
54
+
55
+ # Set the viewport size.
56
+ #
57
+ # @param [Applitools::Selenium::Driver] driver The driver instance.
58
+ # @param [Hash] viewport_size The required browser's viewport size.
59
+ def set_viewport_size(driver, viewport_size)
60
+ Applitools::ArgumentGuard.not_nil(driver, 'Driver')
61
+ Applitools::ArgumentGuard.not_nil(viewport_size, 'viewport_size')
62
+ Applitools::ArgumentGuard.is_a?(viewport_size, 'viewport_size', Applitools::RectangleSize)
63
+ begin
64
+ Applitools::Utils::EyesSeleniumUtils.set_viewport_size eyes_driver(driver), viewport_size
65
+ rescue => e
66
+ Applitools::EyesLogger.error e.class
67
+ Applitools::EyesLogger.error e.message
68
+ raise Applitools::EyesError.new 'Failed to set viewport size!'
69
+ end
70
+ end
71
+ end
72
+
73
+ # @!attribute [rw] force_full_page_screenshot
74
+ # Forces a full page screenshot (by scrolling and stitching) if the
75
+ # browser only supports viewport screenshots.
76
+ # @return [boolean] force full page screenshot flag
77
+ # @!attribute [rw] wait_before_screenshots
78
+ # Sets the time to wait just before taking a screenshot (e.g., to allow
79
+ # positioning to stabilize when performing a full page stitching).
80
+ # @return [Float] The time to wait (Seconds). Values
81
+ # smaller or equal to 0, will cause the default value to be used.
82
+ # @!attribute [rw] hide_scrollbars
83
+ # Turns on/off hiding scrollbars before taking a screenshot
84
+ # @return [boolean] hide_scrollbars flag
85
+ # @!attribute [rw] scroll_to_region
86
+ # If set to +true+ browser will scroll to specified region (even it is out of viewport window)
87
+ # when check_region is called
88
+ # @return [boolean] scroll_to_region flag
89
+ # @!attribute [rw] stitch_mode
90
+ # May be set to :CSS or :SCROLL (:SCROLL is default).
91
+ # When :CSS - SDK will use CSS transitions to perform scrolling, otherwise it will use Javascript
92
+ # window.scroll_to() function for scrolling purposes
93
+ # @return [boolean] stitch_mode (:CSS or :SCROLL)
94
+ # @!attribute [Applitools::RectangleSize] explicit_entire_size
95
+ # May be set to an Applitools::RectangleSize instance or +nil+ (default).
96
+ # @return [Applitools::RectangleSize] explicit_entire_size
97
+
98
+ attr_accessor :base_agent_id, :screenshot, :force_full_page_screenshot, :hide_scrollbars,
99
+ :wait_before_screenshots, :debug_screenshots, :stitch_mode, :disable_horizontal_scrolling,
100
+ :disable_vertical_scrolling, :explicit_entire_size, :debug_screenshot_provider, :stitching_overlap,
101
+ :full_page_capture_algorithm_left_top_offset, :screenshot_type, :send_dom, :use_dom, :enable_patterns
102
+ attr_reader :driver
103
+
104
+ def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
105
+
106
+ # Creates a new (possibly disabled) Eyes instance that interacts with the
107
+ # Eyes Server at the specified url.
108
+ #
109
+ # @param server_url The Eyes Server URL.
110
+ def initialize(server_url = nil)
111
+ super
112
+ self.base_agent_id = "eyes.selenium.ruby/#{Applitools::VERSION}".freeze
113
+ self.check_frame_or_element = false
114
+ self.region_to_check = nil
115
+ self.force_full_page_screenshot = false
116
+ self.dont_get_title = false
117
+ self.hide_scrollbars = false
118
+ self.device_pixel_ratio = UNKNOWN_DEVICE_PIXEL_RATIO
119
+ self.stitch_mode = Applitools::STITCH_MODE[:scroll]
120
+ self.wait_before_screenshots = DEFAULT_WAIT_BEFORE_SCREENSHOTS
121
+ self.region_visibility_strategy = MoveToRegionVisibilityStrategy.new
122
+ self.debug_screenshots = false
123
+ self.debug_screenshot_provider = Applitools::DebugScreenshotProvider.new
124
+ .tag_access { tag_for_debug }
125
+ .debug_flag_access { debug_screenshots }
126
+ self.disable_horizontal_scrolling = false
127
+ self.disable_vertical_scrolling = false
128
+ self.explicit_entire_size = nil
129
+ self.force_driver_resolution_as_viewport_size = false
130
+ self.stitching_overlap = DEFAULT_STITCHING_OVERLAP
131
+ self.full_page_capture_algorithm_left_top_offset = Applitools::Location::TOP_LEFT
132
+ self.send_dom = false
133
+ self.use_dom = false
134
+ self.enable_patterns = false
135
+ self.prevent_dom_processing = false
136
+ end
137
+
138
+ # Starts a test
139
+ #
140
+ # @param options [Hash] options
141
+ # @option options :driver The driver that controls the browser hosting the application
142
+ # under the test. (*Required* option)
143
+ # @option options [String] :app_name The name of the application under the test. (*Required* option)
144
+ # @option options [String] :test_name The test name (*Required* option)
145
+ # @option options [String | Hash] :viewport_size The required browser's viewport size
146
+ # (i.e., the visible part of the document's body) or +nil+ to use the current window's viewport.
147
+ # @option options :session_type The type of the test (e.g., standard test / visual performance test).
148
+ # Default value is 'SEQUENTAL'
149
+ # @return [Applitools::Selenium::Driver] A wrapped web driver which enables Eyes
150
+ # trigger recording and frame handling
151
+ def open(options = {})
152
+ original_driver = options.delete(:driver)
153
+ options[:viewport_size] = Applitools::RectangleSize.from_any_argument options[:viewport_size] if
154
+ options[:viewport_size]
155
+ Applitools::ArgumentGuard.not_nil original_driver, 'options[:driver]'
156
+ Applitools::ArgumentGuard.hash options, 'open(options)', [:app_name, :test_name]
157
+
158
+ if disabled?
159
+ logger.info('Ignored')
160
+ return driver
161
+ end
162
+
163
+ @driver = self.class.eyes_driver(original_driver, self)
164
+ perform_driver_specific_settings(original_driver)
165
+
166
+ self.device_pixel_ratio = UNKNOWN_DEVICE_PIXEL_RATIO
167
+ self.position_provider = self.class.position_provider(
168
+ stitch_mode, driver, disable_horizontal_scrolling, disable_vertical_scrolling, explicit_entire_size
169
+ )
170
+
171
+ self.eyes_screenshot_factory = lambda do |image|
172
+ Applitools::Selenium::ViewportScreenshot.new(
173
+ image, driver: @driver, force_offset: position_provider.force_offset
174
+ )
175
+ end
176
+
177
+ open_base(options) do
178
+ self.viewport_size = nil if force_driver_resolution_as_viewport_size
179
+ ensure_running_session
180
+ end
181
+ @driver
182
+ end
183
+
184
+ def perform_driver_specific_settings(original_driver)
185
+ modifier = original_driver.class.to_s.downcase.gsub(/::/, '_')
186
+ method_name = "perform_driver_settings_for_#{modifier}"
187
+ send(method_name) if respond_to?(method_name, :include_private)
188
+ end
189
+
190
+ private :perform_driver_specific_settings
191
+
192
+ # Sets the stitch mode.
193
+ #
194
+ # @param [Hash] value The desired type of stitching (:SCROLL is default).
195
+ # @option value [Symbol] :css use Css to perform stitching.
196
+ # @option value [Symbol] :scroll Scroll to perform stitching.
197
+ # @return [Symbol] The type of stitching.
198
+ def stitch_mode=(value)
199
+ @stitch_mode = if value.to_s.upcase == Applitools::STITCH_MODE[:css].to_s
200
+ Applitools::STITCH_MODE[:css]
201
+ else
202
+ Applitools::STITCH_MODE[:scroll]
203
+ end
204
+ unless driver.nil?
205
+ self.position_provider = self.class.position_provider(
206
+ stitch_mode, driver, disable_horizontal_scrolling, disable_vertical_scrolling, explicit_entire_size
207
+ )
208
+ end
209
+ if stitch_mode == Applitools::STITCH_MODE[:css]
210
+ @css_transition_original_hide_scrollbars = hide_scrollbars
211
+ self.hide_scrollbars = true
212
+ else
213
+ self.hide_scrollbars = @css_transition_original_hide_scrollbars || false
214
+ end
215
+ value
216
+ end
217
+
218
+ # Takes a snapshot of the application under test and matches it with the expected output.
219
+ #
220
+ # @param [String] tag An optional tag to be assosiated with the snapshot.
221
+ # @param [Fixnum] match_timeout The amount of time to retry matching (seconds)
222
+ def check_window(tag = nil, match_timeout = USE_DEFAULT_MATCH_TIMEOUT)
223
+ target = Applitools::Selenium::Target.window.tap do |t|
224
+ t.timeout(match_timeout)
225
+ t.fully if force_full_page_screenshot
226
+ end
227
+ check(tag, target)
228
+ end
229
+
230
+ # @!visibility private
231
+ def title
232
+ return driver.title unless dont_get_title
233
+ rescue StandardError => e
234
+ logger.warn "failed (#{e.message})"
235
+ self.dont_get_title = false
236
+ ''
237
+ end
238
+
239
+ # @!visibility private
240
+ def get_viewport_size(web_driver = driver)
241
+ Applitools::ArgumentGuard.not_nil 'web_driver', web_driver
242
+ Applitools::Utils::EyesSeleniumUtils.extract_viewport_size(driver)
243
+ end
244
+
245
+ # Takes a snapshot and matches it with the expected output.
246
+ #
247
+ # @param [String] name The name of the tag.
248
+ # @param [Applitools::Selenium::Target] target which area of the window to check.
249
+ # @return [Applitools::MatchResult] The match results.
250
+ def check(name, target)
251
+ logger.info "check(#{name}) is called"
252
+ self.tag_for_debug = name
253
+ Applitools::ArgumentGuard.is_a? target, 'target', Applitools::Selenium::Target
254
+ target_to_check = target.finalize
255
+ original_overflow = nil
256
+ original_position_provider = position_provider
257
+ original_force_full_page_screenshot = force_full_page_screenshot
258
+
259
+ eyes_element = nil
260
+ timeout = target_to_check.options[:timeout] || USE_DEFAULT_MATCH_TIMEOUT
261
+
262
+ self.eyes_screenshot_factory = lambda do |image|
263
+ Applitools::Selenium::ViewportScreenshot.new(
264
+ image,
265
+ region_provider: region_to_check
266
+ )
267
+ end
268
+
269
+ # self.prevent_dom_processing = !((!target.options[:send_dom].nil? && target.options[:send_dom]) ||
270
+ # send_dom || stitch_mode == Applitools::STITCH_MODE[:css])
271
+
272
+ self.prevent_dom_processing = !((!target.options[:send_dom].nil? && target.options[:send_dom]) ||
273
+ send_dom)
274
+
275
+ check_in_frame target_frames: target_to_check.frames do
276
+ begin
277
+ match_data = Applitools::MatchWindowData.new
278
+ match_data.tag = name
279
+ update_default_settings(match_data)
280
+ eyes_element = target_to_check.region_to_check.call(driver)
281
+
282
+ unless force_full_page_screenshot
283
+ region_visibility_strategy.move_to_region original_position_provider,
284
+ Applitools::Location.new(eyes_element.location.x.to_i, eyes_element.location.y.to_i)
285
+ driver.find_element(:css, 'html').scroll_data_attribute = true
286
+ end
287
+
288
+ region_provider = Applitools::Selenium::RegionProvider.new(driver, region_for_element(eyes_element))
289
+
290
+ self.region_to_check = region_provider
291
+
292
+ match_data.read_target(target_to_check, driver)
293
+ match_data.use_dom = use_dom unless match_data.use_dom
294
+ match_data.enable_patterns = enable_patterns unless match_data.enable_patterns
295
+
296
+ is_element = eyes_element.is_a? Applitools::Selenium::Element
297
+ inside_a_frame = !driver.frame_chain.empty?
298
+
299
+ self.screenshot_type = self.class.obtain_screenshot_type(
300
+ is_element,
301
+ inside_a_frame,
302
+ target_to_check.options[:stitch_content],
303
+ force_full_page_screenshot
304
+ )
305
+
306
+ case screenshot_type
307
+ when ENTIRE_ELEMENT_SCREENSHOT
308
+ if eyes_element.is_a? Applitools::Selenium::Element
309
+ original_overflow = eyes_element.overflow
310
+ eyes_element.overflow = 'hidden'
311
+ eyes_element.scroll_data_attribute = true
312
+ eyes_element.overflow_data_attribute = original_overflow
313
+ self.position_provider = Applitools::Selenium::CssTranslateElementPositionProvider.new(
314
+ driver,
315
+ eyes_element
316
+ )
317
+ end
318
+ end
319
+
320
+ check_window_base(
321
+ region_provider, timeout, match_data
322
+ )
323
+ ensure
324
+ eyes_element.overflow = original_overflow unless original_overflow.nil?
325
+ self.check_frame_or_element = false
326
+ self.force_full_page_screenshot = original_force_full_page_screenshot
327
+ self.position_provider = original_position_provider
328
+ self.region_to_check = nil
329
+ self.full_page_capture_algorithm_left_top_offset = Applitools::Location::TOP_LEFT
330
+ region_visibility_strategy.return_to_original_position original_position_provider
331
+ end
332
+ # rubocop:enable BlockLength
333
+ end
334
+ self.prevent_dom_processing = false
335
+ end
336
+
337
+ # Validates the contents of an iframe and matches it with the expected output.
338
+ #
339
+ # @param [Hash] options The specific parameters of the desired screenshot.
340
+ # @option options [Array] :target_frames The frames to check.
341
+ def check_in_frame(options)
342
+ frames = options.delete :target_frames
343
+
344
+ Applitools::ArgumentGuard.is_a? options, 'options', Hash
345
+ Applitools::ArgumentGuard.is_a? frames, 'target_frames: []', Array
346
+
347
+ return yield if block_given? && frames.empty?
348
+
349
+ original_frame_chain = driver.frame_chain
350
+
351
+ logger.info 'Switching to target frame according to frames path...'
352
+ driver.switch_to.frames(frames_path: frames)
353
+ frame_chain_to_reset = driver.frame_chain
354
+ logger.info 'Done!'
355
+
356
+ ensure_frame_visible
357
+
358
+ yield if block_given?
359
+
360
+ reset_frames_scroll_position(frame_chain_to_reset)
361
+
362
+ logger.info 'Switching back into top level frame...'
363
+ driver.switch_to.default_content
364
+ return unless original_frame_chain
365
+ logger.info 'Switching back into original frame...'
366
+ driver.switch_to.frames frame_chain: original_frame_chain
367
+ end
368
+
369
+ # Creates a region instance.
370
+ #
371
+ # @param [Applitools::Element] element The element.
372
+ # @return [Applitools::Region] The relevant region.
373
+ def region_for_element(element)
374
+ return element if element.is_a? Applitools::Region
375
+
376
+ p = element.location
377
+ d = element.size
378
+
379
+ border_left_width = element.border_left_width
380
+ border_top_width = element.border_top_width
381
+ border_right_width = element.border_right_width
382
+ border_bottom_width = element.border_bottom_width
383
+
384
+ Applitools::Region.new(
385
+ p.x.round + border_left_width,
386
+ p.y.round + border_top_width,
387
+ d.width - border_left_width - border_right_width,
388
+ d.height - border_top_width - border_bottom_width
389
+ ).tap do |r|
390
+ border_padding = Applitools::PaddingBounds.new(
391
+ border_left_width,
392
+ border_top_width,
393
+ border_right_width,
394
+ border_bottom_width
395
+ )
396
+ r.padding(border_padding)
397
+ end
398
+ end
399
+
400
+ private :check_in_frame
401
+ private :region_for_element
402
+
403
+ # Takes a snapshot of the application under test and matches a region of
404
+ # a specific element with the expected region output.
405
+ #
406
+ # @param [Applitools::Selenium::Element] element Represents a region to check.
407
+ # @param [Symbol] how a finder, such :css or :id. Selects a finder will be used to find an element
408
+ # See Selenium::Webdriver::Element#find_element documentation for full list of possible finders.
409
+ # @param [String] what The value will be passed to a specified finder. If finder is :css it must be a css selector.
410
+ # @param [Hash] options
411
+ # @option options [String] :tag An optional tag to be associated with the snapshot.
412
+ # @option options [Fixnum] :match_timeout The amount of time to retry matching. (Seconds)
413
+ # @option options [Boolean] :stitch_content If set to true, will try to get full content of the element
414
+ # (including hidden content due overflow settings) by scrolling the element,
415
+ # taking and stitching partial screenshots.
416
+ # @example Check region by element
417
+ # check_region(element, tag: 'Check a region by element', match_timeout: 3, stitch_content: false)
418
+ # @example Check region by css selector
419
+ # check_region(:css, '.form-row .input#e_mail', tag: 'Check a region by element', match_timeout: 3,
420
+ # stitch_content: false)
421
+ # @!parse def check_region(element, how=nil, what=nil, options = {}); end
422
+ def check_region(*args)
423
+ options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil }.merge! Applitools::Utils.extract_options!(args)
424
+ target = Applitools::Selenium::Target.new.region(*args).timeout(options[:match_timeout])
425
+ target.fully if options[:stitch_content]
426
+ check(options[:tag], target)
427
+ end
428
+
429
+ # Validates the contents of an iframe and matches it with the expected output.
430
+ #
431
+ # @param [Hash] options The specific parameters of the desired screenshot.
432
+ # @option options [Fixnum] :timeout The amount of time to retry matching. (Seconds)
433
+ # @option options [String] :tag An optional tag to be associated with the snapshot.
434
+ # @option options [String] :frame Frame element or frame name or frame id.
435
+ # @option options [String] :name_or_id The name or id of the target frame (deprecated. use :frame instead).
436
+ # @option options [String] :frame_element The frame element (deprecated. use :frame instead).
437
+ # @return [Applitools::MatchResult] The match results.
438
+
439
+ def check_frame(options = {})
440
+ options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil }.merge!(options)
441
+ frame = options[:frame] || options[:frame_element] || options[:name_or_id]
442
+ target = Applitools::Selenium::Target.frame(frame).timeout(options[:timeout]).fully
443
+ check(options[:tag], target)
444
+ end
445
+
446
+ # Validates the contents of a region in an iframe and matches it with the expected output.
447
+ #
448
+ # @param [Hash] options The specific parameters of the desired screenshot.
449
+ # @option options [String] :name_or_id The name or id of the target frame (deprecated. use :frame instead).
450
+ # @option options [String] :frame_element The frame element (deprecated. use :frame instead).
451
+ # @option options [String] :frame Frame element or frame name or frame id.
452
+ # @option options [String] :tag An optional tag to be associated with the snapshot.
453
+ # @option options [Symbol] :by By which identifier to find the region (e.g :css, :id).
454
+ # @option options [Fixnum] :timeout The amount of time to retry matching. (Seconds)
455
+ # @option options [Boolean] :stitch_content Whether to stitch the content or not.
456
+ # @return [Applitools::MatchResult] The match results.
457
+ def check_region_in_frame(options = {})
458
+ options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil, stitch_content: false }.merge!(options)
459
+ Applitools::ArgumentGuard.not_nil options[:by], 'options[:by]'
460
+ Applitools::ArgumentGuard.is_a? options[:by], 'options[:by]', Array
461
+
462
+ how_what = options.delete(:by)
463
+ frame = options[:frame] || options[:frame_element] || options[:name_or_id]
464
+
465
+ target = Applitools::Selenium::Target.new.timeout(options[:timeout])
466
+ target.frame(frame) if frame
467
+ target.fully if options[:stitch_content]
468
+ target.region(*how_what)
469
+
470
+ check(options[:tag], target)
471
+ end
472
+
473
+ # Use this method to perform seamless testing with selenium through eyes driver.
474
+ # It yields a block and passes to it an Applitools::Selenium::Driver instance, which wraps standard driver.
475
+ # Using Selenium methods inside the 'test' block will send the messages to Selenium
476
+ # after creating the Eyes triggers for them. Options are similar to {open}
477
+ # @yieldparam driver [Applitools::Selenium::Driver] Gives a driver to a block, which translates calls to a native
478
+ # Selemium::Driver instance
479
+ # @example
480
+ # eyes.test(app_name: 'my app', test_name: 'my test') do |driver|
481
+ # driver.get "http://www.google.com"
482
+ # driver.check_window("initial")
483
+ # end
484
+ def test(options = {}, &_block)
485
+ open(options)
486
+ yield(driver)
487
+ close
488
+ ensure
489
+ abort_if_not_closed
490
+ end
491
+
492
+ # @!visibility private
493
+ def scroll_to_region
494
+ region_visibility_strategy.is_a? Applitools::Selenium::MoveToRegionVisibilityStrategy
495
+ end
496
+
497
+ # @!visibility private
498
+ def scroll_to_region=(value)
499
+ if value
500
+ self.region_visibility_strategy = Applitools::Selenium::MoveToRegionVisibilityStrategy.new
501
+ else
502
+ self.region_visibility_strategy = Applitools::Selenium::NopRegionVisibilityStrategy.new
503
+ end
504
+ end
505
+
506
+ def dom_data
507
+ return {} if prevent_dom_processing
508
+ begin
509
+ Applitools::Selenium::DomCapture.get_window_dom(driver, logger)
510
+ rescue Applitools::EyesError => e
511
+ logger.error "DOM capture failed! #{e.message}"
512
+ return {}
513
+ end
514
+ end
515
+
516
+ private
517
+
518
+ attr_accessor :check_frame_or_element, :region_to_check, :dont_get_title,
519
+ :device_pixel_ratio, :position_provider, :scale_provider, :tag_for_debug,
520
+ :region_visibility_strategy, :eyes_screenshot_factory, :force_driver_resolution_as_viewport_size,
521
+ :prevent_dom_processing
522
+
523
+ def image_provider
524
+ Applitools::Selenium::TakesScreenshotImageProvider.new(
525
+ driver,
526
+ debug_screenshot_provider: debug_screenshot_provider
527
+ )
528
+ end
529
+
530
+ def capture_screenshot
531
+ logger.info 'Getting screenshot (capture_screenshot() has been invoked)'
532
+
533
+ update_scaling_params
534
+
535
+ if hide_scrollbars
536
+ begin
537
+ original_overflow = Applitools::Utils::EyesSeleniumUtils.hide_scrollbars driver
538
+ driver.find_element(:css, 'html').overflow_data_attribute = original_overflow
539
+ rescue Applitools::EyesDriverOperationException => e
540
+ logger.warn "Failed to hide scrollbars! Error: #{e.message}"
541
+ end
542
+ end
543
+
544
+ begin
545
+ algo = Applitools::Selenium::FullPageCaptureAlgorithm.new(
546
+ debug_screenshot_provider: debug_screenshot_provider
547
+ )
548
+ case screenshot_type
549
+ when ENTIRE_ELEMENT_SCREENSHOT
550
+ self.screenshot = entire_element_screenshot(algo)
551
+ when FULLPAGE_SCREENSHOT
552
+ self.screenshot = full_page_screenshot(algo)
553
+ when VIEWPORT_SCREENSHOT
554
+ self.screenshot = viewport_screenshot
555
+ end
556
+ ensure
557
+ begin
558
+ Applitools::Utils::EyesSeleniumUtils.set_overflow driver, original_overflow
559
+ rescue Applitools::EyesDriverOperationException => e
560
+ logger.warn "Failed to revert overflow! Error: #{e.message}"
561
+ end
562
+ end
563
+ end
564
+
565
+ def full_page_screenshot(algo)
566
+ logger.info 'Full page screenshot requested'
567
+ original_frame = driver.frame_chain
568
+ driver.switch_to.default_content
569
+ region_provider = Applitools::Selenium::RegionProvider.new(driver, Applitools::Region::EMPTY)
570
+
571
+ full_page_image = algo.get_stitched_region(
572
+ image_provider: image_provider,
573
+ region_to_check: region_provider,
574
+ origin_provider: Applitools::Selenium::ScrollPositionProvider.new(driver),
575
+ position_provider: position_provider,
576
+ scale_provider: scale_provider,
577
+ cut_provider: cut_provider,
578
+ wait_before_screenshots: wait_before_screenshots,
579
+ eyes_screenshot_factory: eyes_screenshot_factory,
580
+ stitching_overlap: stitching_overlap
581
+ )
582
+
583
+ # binding.pry
584
+ unless original_frame.empty?
585
+ logger.info 'Switching back to original frame...'
586
+ driver.switch_to.frames frame_chain: original_frame
587
+ logger.info 'Done switching!'
588
+ end
589
+ logger.info 'Creating EyesWebDriver screenshot instance..'
590
+ result = Applitools::Selenium::FullpageScreenshot.new(
591
+ full_page_image,
592
+ region_provider: region_to_check
593
+ )
594
+ logger.info 'Done creating EyesWebDriver screenshot instance!'
595
+ result
596
+ end
597
+
598
+ def entire_element_screenshot(algo)
599
+ logger.info 'Entire element screenshot requested'
600
+ entire_frame_or_element = algo.get_stitched_region(
601
+ image_provider: image_provider,
602
+ region_to_check: region_to_check,
603
+ origin_provider: position_provider,
604
+ position_provider: position_provider,
605
+ scale_provider: scale_provider,
606
+ cut_provider: cut_provider,
607
+ wait_before_screenshots: wait_before_screenshots,
608
+ eyes_screenshot_factory: eyes_screenshot_factory,
609
+ stitching_overlap: stitching_overlap,
610
+ top_left_position: full_page_capture_algorithm_left_top_offset
611
+ )
612
+
613
+ logger.info 'Building screenshot object (EyesStitchedElementScreenshot)...'
614
+ result = Applitools::Selenium::EntireElementScreenshot.new(
615
+ entire_frame_or_element,
616
+ region_provider: region_to_check
617
+ )
618
+ logger.info 'Done!'
619
+ result
620
+ end
621
+
622
+ def viewport_screenshot
623
+ logger.info 'Viewport screenshot requested'
624
+ sleep wait_before_screenshots
625
+ image = image_provider.take_screenshot
626
+ scale_provider.scale_image(image) if scale_provider
627
+ local_cut_provider = (
628
+ cut_provider || Applitools::Selenium::FixedCutProvider.viewport(image, viewport_size, region_to_check)
629
+ )
630
+ local_cut_provider.cut(image) if local_cut_provider
631
+ eyes_screenshot_factory.call(image)
632
+ end
633
+
634
+ def vp_size=(value, skip_check_if_open = false)
635
+ raise Applitools::EyesNotOpenException.new 'set_viewport_size: Eyes not open!' unless skip_check_if_open || open?
636
+ original_frame = driver.frame_chain
637
+ driver.switch_to.default_content
638
+ begin
639
+ Applitools::Utils::EyesSeleniumUtils.set_viewport_size driver, value
640
+ rescue => e
641
+ logger.error e.class.to_s
642
+ logger.error e.message
643
+ raise Applitools::TestFailedError.new "#{e.class} - #{e.message}"
644
+ ensure
645
+ driver.switch_to.frames(frame_chain: original_frame)
646
+ end
647
+ end
648
+
649
+ alias set_viewport_size vp_size=
650
+
651
+ def get_driver(options)
652
+ # TODO: remove the "browser" related block when possible. It's for backward compatibility.
653
+ if options.key?(:browser)
654
+ logger.warn('"browser" key is deprecated, please use "driver" instead.')
655
+ return options[:browser]
656
+ end
657
+
658
+ options.fetch(:driver, nil)
659
+ end
660
+
661
+ def update_scaling_params
662
+ return unless device_pixel_ratio == UNKNOWN_DEVICE_PIXEL_RATIO
663
+
664
+ logger.info 'Trying to extract device pixel ratio...'
665
+ begin
666
+ self.device_pixel_ratio = Applitools::Utils::EyesSeleniumUtils.device_pixel_ratio(driver)
667
+ rescue Applitools::EyesDriverOperationException
668
+ logger.warn 'Failed to extract device pixel ratio! Using default.'
669
+ self.device_pixel_ratio = DEFAULT_DEVICE_PIXEL_RATIO
670
+ end
671
+
672
+ logger.info "Device pixel_ratio: #{device_pixel_ratio}"
673
+ logger.info 'Setting scale provider...'
674
+
675
+ begin
676
+ self.scale_provider = Applitools::Selenium::ContextBasedScaleProvider.new(position_provider.entire_size,
677
+ viewport_size, device_pixel_ratio)
678
+ rescue StandardError
679
+ logger.info 'Failed to set ContextBasedScaleProvider'
680
+ logger.info 'Using FixedScaleProvider instead'
681
+ self.scale_provider = Applitools::FixedScaleProvider.new(1.to_f / device_pixel_ratio)
682
+ end
683
+ logger.info 'Done!'
684
+ end
685
+
686
+ def _add_text_trigger(control, text)
687
+ unless last_screenshot
688
+ logger.info "Ignoring #{text} (no screenshot)"
689
+ return
690
+ end
691
+
692
+ unless driver.frame_chain.same_frame_chain? last_screenshot.frame_chain
693
+ logger.info "Ignoring #{text} (different_frame)"
694
+ return
695
+ end
696
+
697
+ add_text_trigger_base(control, text)
698
+ end
699
+
700
+ def add_text_trigger(control, text)
701
+ if disabled?
702
+ logger.info "Ignoring #{text} (disabled)"
703
+ return
704
+ end
705
+
706
+ Applitools::ArgumentGuard.not_nil control, 'control'
707
+ return _add_text_trigger(control, text) if control.is_a? Applitools::Region
708
+
709
+ pl = control.location
710
+ ds = control.size
711
+
712
+ element_region = Applitools::Region.new(pl.x, pl.y, ds.width, ds.height)
713
+
714
+ return _add_text_trigger(element_region, text) if control.is_a? Applitools::Selenium::Element
715
+ end
716
+
717
+ def add_mouse_trigger(mouse_action, element)
718
+ if disabled?
719
+ logger.info "Ignoring #{mouse_action} (disabled)"
720
+ return
721
+ end
722
+
723
+ if element.is_a? Hash
724
+ return add_mouse_trigger_by_region_and_location(mouse_action, element[:region], element[:location]) if
725
+ element.key?(:location) && element.key?(:region)
726
+ raise Applitools::EyesIllegalArgument.new 'Element[] doesn\'t contain required keys!'
727
+ end
728
+
729
+ Applitools::ArgumentGuard.not_nil element, 'element'
730
+ Applitools::ArgumentGuard.is_a? element, 'element', Applitools::Selenium::Element
731
+
732
+ pl = element.location
733
+ ds = element.size
734
+
735
+ element_region = Applitools::Region.new(pl.x, pl.y, ds.width, ds.height)
736
+
737
+ unless last_screenshot
738
+ logger.info "Ignoring #{mouse_action} (no screenshot)"
739
+ return
740
+ end
741
+
742
+ unless driver.frame_chain.same_frame_chain? last_screenshot.frame_chain
743
+ logger.info "Ignoring #{mouse_action} (different_frame)"
744
+ return
745
+ end
746
+
747
+ add_mouse_trigger_base(mouse_action, element_region, element_region.middle_offset)
748
+ end
749
+
750
+ # control - Region
751
+ # cursor - Location
752
+ def add_mouse_trigger_by_region_and_location(mouse_action, control, cursor)
753
+ unless last_screenshot
754
+ logger.info "Ignoring #{mouse_action} (no screenshot)"
755
+ return
756
+ end
757
+
758
+ Applitools::ArgumentGuard.is_a? control, 'control', Applitools::Region
759
+ Applitools::ArgumentGuard.is_a? cursor, 'cursor', Applitools::Location
760
+
761
+ if driver.frame_chain.same_frame_chain? last_screenshot.frame_chain
762
+ logger.info "Ignoring #{mouse_action} (different_frame)"
763
+ return
764
+ end
765
+
766
+ add_mouse_trigger_base(mouse_action, control, cursor)
767
+ end
768
+
769
+ public :add_text_trigger, :add_mouse_trigger, :add_mouse_trigger_by_region_and_location
770
+
771
+ protected
772
+
773
+ def app_environment
774
+ app_env = super
775
+ if app_env.os.nil?
776
+ logger.info 'No OS set, checking for mobile OS...'
777
+ underlying_driver = Applitools::Utils::EyesSeleniumUtils.mobile_device?(driver)
778
+ unless underlying_driver.nil?
779
+ logger.info 'Mobile device detected! Checking device type...'
780
+ if Applitools::Utils::EyesSeleniumUtils.android?(underlying_driver)
781
+ logger.info 'Android detected...'
782
+ platform_name = 'Android'
783
+ elsif Applitools::Utils::EyesSeleniumUtils.ios?(underlying_driver)
784
+ logger.info 'iOS detected...'
785
+ platform_name = 'iOS'
786
+ else
787
+ logger.info 'Unknown device type'
788
+ end
789
+ end
790
+
791
+ if platform_name && !platform_name.empty?
792
+ os = platform_name
793
+ platform_version = Applitools::Utils::EyesSeleniumUtils.platform_version(underlying_driver).to_s
794
+ unless platform_version.empty?
795
+ major_version = platform_version.split(/\./).first
796
+ os << " #{major_version}"
797
+ end
798
+ logger.info "Setting OS: #{os}"
799
+ app_env.os = os
800
+ end
801
+ else
802
+ logger.info 'No mobile OS detected.'
803
+ end
804
+ app_env
805
+ end
806
+
807
+ def inferred_environment
808
+ return @inferred_environment unless @inferred_environment.nil?
809
+
810
+ user_agent = driver.user_agent
811
+ return "useragent: #{user_agent}" if user_agent && !user_agent.empty?
812
+
813
+ nil
814
+ end
815
+
816
+ def ensure_frame_visible
817
+ original_fc = driver.frame_chain
818
+ return original_fc if original_fc.empty?
819
+ fc = Applitools::Selenium::FrameChain.new other: original_fc
820
+ until fc.empty?
821
+ driver.switch_to.parent_frame
822
+ position_provider.position = fc.pop.location
823
+ end
824
+ driver.switch_to.frames(frame_chain: original_fc)
825
+ original_fc
826
+ end
827
+
828
+ def reset_frames_scroll_position(original_fc)
829
+ return original_fc if original_fc.empty?
830
+ fc = Applitools::Selenium::FrameChain.new other: original_fc
831
+ until fc.empty?
832
+ driver.switch_to.parent_frame
833
+ position_provider.position = fc.pop.parent_scroll_position
834
+ end
835
+ driver.switch_to.frames(frame_chain: original_fc)
836
+ original_fc
837
+ end
838
+
839
+ class << self
840
+ def position_provider(stitch_mode, driver, disable_horizontal = false, disable_vertical = false,
841
+ explicit_entire_size = nil)
842
+
843
+ max_width = nil
844
+ max_height = nil
845
+ unless explicit_entire_size.nil?
846
+ max_width = explicit_entire_size.width
847
+ max_height = explicit_entire_size.height
848
+ end
849
+ case stitch_mode
850
+ when :SCROLL
851
+ Applitools::Selenium::ScrollPositionProvider.new(driver, disable_horizontal, disable_vertical,
852
+ max_width, max_height)
853
+ when :CSS
854
+ Applitools::Selenium::CssTranslatePositionProvider.new(driver, disable_horizontal, disable_vertical,
855
+ max_width, max_height)
856
+ end
857
+ end
858
+ end
859
+ end
860
+ end