eyes_selenium 3.14.10 → 3.15.0.beta

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