eyes_selenium 2.39.1 → 3.0.6
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.
- checksums.yaml +4 -4
- data/lib/applitools/capybara.rb +1 -1
- data/lib/applitools/selenium/border_aware_element_content_location_provider.rb +41 -0
- data/lib/applitools/selenium/browser.rb +1 -0
- data/lib/applitools/selenium/capybara/capybara_settings.rb +8 -0
- data/lib/applitools/selenium/capybara/driver.rb +1 -0
- data/lib/applitools/selenium/context_based_scale_provider.rb +36 -0
- data/lib/applitools/selenium/css_translate_position_provider.rb +66 -0
- data/lib/applitools/selenium/driver.rb +159 -44
- data/lib/applitools/selenium/element.rb +100 -8
- data/lib/applitools/selenium/element_position_provider.rb +48 -0
- data/lib/applitools/selenium/eyes.rb +922 -0
- data/lib/applitools/selenium/eyes_target_locator.rb +191 -0
- data/lib/applitools/selenium/eyes_web_driver_screenshot.rb +274 -0
- data/lib/applitools/selenium/frame.rb +24 -0
- data/lib/applitools/selenium/frame_chain.rb +68 -0
- data/lib/applitools/selenium/full_page_capture_algorithm.rb +162 -0
- data/lib/applitools/selenium/keyboard.rb +1 -0
- data/lib/applitools/selenium/match_window_task.rb +0 -197
- data/lib/applitools/selenium/mouse.rb +1 -0
- data/lib/applitools/selenium/move_to_region_visibility_strategy.rb +33 -0
- data/lib/applitools/selenium/nop_region_visibility_strategy.rb +16 -0
- data/lib/applitools/selenium/scroll_position_provider.rb +52 -0
- data/lib/applitools/selenium/takes_screenshot_image_provider.rb +36 -0
- data/lib/applitools/version.rb +1 -1
- data/lib/eyes_selenium.rb +19 -30
- metadata +24 -254
- data/.gitignore +0 -18
- data/.rspec +0 -2
- data/.rubocop.yml +0 -57
- data/.travis.yml +0 -17
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -13
- data/README.md +0 -38
- data/Rakefile +0 -11
- data/certs/cacert.pem +0 -3557
- data/examples/appium_example_script.rb +0 -59
- data/examples/capybara_example.rb +0 -82
- data/examples/sauce_capybara_example.rb +0 -41
- data/examples/sauce_example.rb +0 -34
- data/examples/simple_test_script.rb +0 -23
- data/examples/watir_test_script.rb +0 -26
- data/eyes_selenium.gemspec +0 -89
- data/lib/applitools/appium_driver.rb +0 -7
- data/lib/applitools/base/batch_info.rb +0 -21
- data/lib/applitools/base/dimension.rb +0 -36
- data/lib/applitools/base/environment.rb +0 -19
- data/lib/applitools/base/image_position.rb +0 -10
- data/lib/applitools/base/mouse_trigger.rb +0 -33
- data/lib/applitools/base/point.rb +0 -34
- data/lib/applitools/base/region.rb +0 -112
- data/lib/applitools/base/server_connector.rb +0 -120
- data/lib/applitools/base/session.rb +0 -15
- data/lib/applitools/base/start_info.rb +0 -34
- data/lib/applitools/base/test_results.rb +0 -28
- data/lib/applitools/base/text_trigger.rb +0 -22
- data/lib/applitools/extensions.rb +0 -17
- data/lib/applitools/eyes.rb +0 -460
- data/lib/applitools/eyes_logger.rb +0 -38
- data/lib/applitools/method_tracer.rb +0 -22
- data/lib/applitools/poltergeist/applitools_compatible.rb +0 -28
- data/lib/applitools/poltergeist/driver.rb +0 -11
- data/lib/applitools/sauce.rb +0 -2
- data/lib/applitools/selenium/match_window_data.rb +0 -28
- data/lib/applitools/selenium_webdriver.rb +0 -8
- data/lib/applitools/utils/image_delta_compressor.rb +0 -148
- data/lib/applitools/utils/image_utils.rb +0 -143
- data/lib/applitools/utils/utils.rb +0 -49
- data/lib/applitools/watir_browser.rb +0 -8
- data/spec/driver_passthrough_spec.rb +0 -68
- data/spec/fixtures/static_test_file.html +0 -15
- data/spec/spec_helper.rb +0 -17
@@ -1,7 +1,35 @@
|
|
1
1
|
module Applitools::Selenium
|
2
2
|
class Element < SimpleDelegator
|
3
|
+
JS_GET_COMPUTED_STYLE_FORMATTED_STR = <<-JS.freeze
|
4
|
+
var elem = arguments[0];
|
5
|
+
var styleProp = '%s';
|
6
|
+
if (window.getComputedStyle) {
|
7
|
+
return window.getComputedStyle(elem, null)
|
8
|
+
.getPropertyValue(styleProp);
|
9
|
+
} else if (elem.currentStyle) {
|
10
|
+
return elem.currentStyle[styleProp];
|
11
|
+
} else {
|
12
|
+
return null;
|
13
|
+
};
|
14
|
+
JS
|
15
|
+
|
16
|
+
JS_GET_SCROLL_LEFT = 'return arguments[0].scrollLeft;'.freeze
|
17
|
+
JS_GET_SCROLL_TOP = 'return arguments[0].scrollTop;'.freeze
|
18
|
+
JS_GET_SCROLL_WIDTH = 'return arguments[0].scrollWidth;'.freeze
|
19
|
+
JS_GET_SCROLL_HEIGHT = 'return arguments[0].scrollHeight;'.freeze
|
20
|
+
|
21
|
+
JS_SCROLL_TO_FORMATTED_STR = <<-JS.freeze
|
22
|
+
arguments[0].scrollLeft = %d;
|
23
|
+
arguments[0].scrollTop = %d;
|
24
|
+
JS
|
25
|
+
|
26
|
+
JS_GET_OVERFLOW = 'return arguments[0].style.overflow;'.freeze
|
27
|
+
JS_SET_OVERFLOW_FORMATTED_STR = "arguments[0].style.overflow = '%s'".freeze
|
28
|
+
|
3
29
|
TRACE_PREFIX = 'EyesWebElement'.freeze
|
4
30
|
|
31
|
+
# def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
|
32
|
+
|
5
33
|
def initialize(driver, element)
|
6
34
|
super(element)
|
7
35
|
|
@@ -15,10 +43,8 @@ module Applitools::Selenium
|
|
15
43
|
protected :web_element
|
16
44
|
|
17
45
|
def click
|
18
|
-
|
19
|
-
|
20
|
-
@driver.user_inputs << Applitools::Base::MouseTrigger.new(:click, current_control, offset)
|
21
|
-
|
46
|
+
@driver.add_mouse_trigger(Applitools::MouseTrigger::MOUSE_ACTION[:click], self)
|
47
|
+
# logger.info "click(#{bounds})";
|
22
48
|
web_element.click
|
23
49
|
end
|
24
50
|
|
@@ -37,16 +63,14 @@ module Applitools::Selenium
|
|
37
63
|
alias eql? ==
|
38
64
|
|
39
65
|
def send_keys(*args)
|
40
|
-
current_control = region
|
41
66
|
Selenium::WebDriver::Keys.encode(args).each do |key|
|
42
|
-
@driver.
|
67
|
+
@driver.add_text_trigger(self, key.to_s)
|
43
68
|
end
|
44
|
-
|
45
69
|
web_element.send_keys(*args)
|
46
70
|
end
|
47
71
|
alias send_key send_keys
|
48
72
|
|
49
|
-
def
|
73
|
+
def bounds
|
50
74
|
point = location
|
51
75
|
left = point.x
|
52
76
|
top = point.y
|
@@ -83,6 +107,74 @@ module Applitools::Selenium
|
|
83
107
|
super(*args).map { |e| self.class.new driver, e }
|
84
108
|
end
|
85
109
|
|
110
|
+
def overflow
|
111
|
+
driver.execute_script(JS_GET_OVERFLOW, __getobj__).to_s
|
112
|
+
end
|
113
|
+
|
114
|
+
def overflow=(overflow)
|
115
|
+
driver.execute_script(JS_SET_OVERFLOW_FORMATTED_STR % overflow, self)
|
116
|
+
end
|
117
|
+
|
118
|
+
def computed_style(prop_style)
|
119
|
+
driver.execute_script(JS_GET_COMPUTED_STYLE_FORMATTED_STR % prop_style, self).to_s
|
120
|
+
end
|
121
|
+
|
122
|
+
def computed_style_integer(prop_style)
|
123
|
+
computed_style(prop_style).gsub(/px/, '').to_i.round
|
124
|
+
end
|
125
|
+
|
126
|
+
def border_left_width
|
127
|
+
computed_style_integer(:'border-left-width')
|
128
|
+
end
|
129
|
+
|
130
|
+
def border_top_width
|
131
|
+
computed_style_integer(:'border-top-width')
|
132
|
+
end
|
133
|
+
|
134
|
+
def border_right_width
|
135
|
+
computed_style_integer(:'border-right-width')
|
136
|
+
end
|
137
|
+
|
138
|
+
def border_bottom_width
|
139
|
+
computed_style_integer(:'border-bottom-width')
|
140
|
+
end
|
141
|
+
|
142
|
+
def padding_left_width
|
143
|
+
computed_style_integer(:'padding-left')
|
144
|
+
end
|
145
|
+
|
146
|
+
def padding_right_width
|
147
|
+
computed_style_integer(:'padding-right')
|
148
|
+
end
|
149
|
+
|
150
|
+
def padding_top_width
|
151
|
+
computed_style_integer(:'padding-top')
|
152
|
+
end
|
153
|
+
|
154
|
+
def padding_bottom_width
|
155
|
+
computed_style_integer(:'padding-bottom')
|
156
|
+
end
|
157
|
+
|
158
|
+
def scroll_left
|
159
|
+
Integer driver.execute_script(JS_GET_SCROLL_LEFT, self).to_s
|
160
|
+
end
|
161
|
+
|
162
|
+
def scroll_top
|
163
|
+
Integer driver.execute_script(JS_GET_SCROLL_TOP, self).to_s
|
164
|
+
end
|
165
|
+
|
166
|
+
def scroll_width
|
167
|
+
Integer driver.execute_script(JS_GET_SCROLL_WIDTH, self).to_s
|
168
|
+
end
|
169
|
+
|
170
|
+
def scroll_height
|
171
|
+
Integer driver.execute_script(JS_GET_SCROLL_HEIGHT, self).to_s
|
172
|
+
end
|
173
|
+
|
174
|
+
def scroll_to(location)
|
175
|
+
driver.execute_script format(JS_SCROLL_TO_FORMATTED_STR, location.x, location.y), self
|
176
|
+
end
|
177
|
+
|
86
178
|
private
|
87
179
|
|
88
180
|
attr_reader :driver
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Applitools::Selenium
|
2
|
+
# @!visibility private
|
3
|
+
class ElementPositionProvider
|
4
|
+
extend Forwardable
|
5
|
+
def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
|
6
|
+
|
7
|
+
def initialize(executor, passed_element)
|
8
|
+
Applitools::ArgumentGuard.not_nil 'executor', executor
|
9
|
+
Applitools::ArgumentGuard.not_nil 'passed_element', passed_element
|
10
|
+
self.driver = executor
|
11
|
+
self.element = passed_element
|
12
|
+
self.element = Applitools::Selenium::Element.new(driver, element) unless
|
13
|
+
element.is_a? Applitools::Selenium::Element
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_position
|
17
|
+
logger.info 'current_position() has called.'
|
18
|
+
result = Applitools::Location.for element.scroll_left, element.scroll_top
|
19
|
+
logger.info "Current position is #{result}"
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
def entire_size
|
24
|
+
logger.info 'entire_size()'
|
25
|
+
result = Applitools::RectangleSize.new(element.scroll_width, element.scroll_height)
|
26
|
+
logger.info "Entire size: #{result}"
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def state
|
31
|
+
current_position
|
32
|
+
end
|
33
|
+
|
34
|
+
def restore_state(value)
|
35
|
+
self.position = value
|
36
|
+
end
|
37
|
+
|
38
|
+
def position=(location)
|
39
|
+
logger.info "Scrolling element to #{location}"
|
40
|
+
element.scroll_to location
|
41
|
+
logger.info 'Done scrolling element!'
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
attr_accessor :element, :driver
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,922 @@
|
|
1
|
+
module Applitools::Selenium
|
2
|
+
# The main API gateway for the SDK
|
3
|
+
class Eyes < Applitools::EyesBase
|
4
|
+
# @!visibility private
|
5
|
+
UNKNOWN_DEVICE_PIXEL_RATIO = 0
|
6
|
+
|
7
|
+
# The pixel ratio will be used if detection of device pixel ratio is failed
|
8
|
+
DEFAULT_DEVICE_PIXEL_RATIO = 1
|
9
|
+
|
10
|
+
DEFAULT_WAIT_BEFORE_SCREENSHOTS = 0.1 # Seconds
|
11
|
+
|
12
|
+
USE_DEFAULT_MATCH_TIMEOUT = -1
|
13
|
+
|
14
|
+
# @!visibility private
|
15
|
+
STICH_MODE = {
|
16
|
+
:scroll => :SCROLL,
|
17
|
+
:css => :CSS
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
extend Forwardable
|
21
|
+
# @!visibility public
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def eyes_driver(driver, eyes = nil)
|
25
|
+
if driver.respond_to? :driver_for_eyes
|
26
|
+
driver.driver_for_eyes eyes
|
27
|
+
elsif driver.is_a? Capybara::Poltergeist::Driver
|
28
|
+
Applitools::Poltergeist::Driver.new(eyes, driver: driver)
|
29
|
+
else
|
30
|
+
unless driver.is_a?(Applitools::Selenium::Driver)
|
31
|
+
Applitools::EyesLogger.warn("Unrecognized driver type: (#{driver.class.name})!")
|
32
|
+
is_mobile_device = driver.respond_to?(:capabilities) && driver.capabilities['platformName']
|
33
|
+
Applitools::Selenium::Driver.new(eyes, driver: driver, is_mobile_device: is_mobile_device)
|
34
|
+
end
|
35
|
+
raise Applitools::EyesError.new "Unknown driver #{driver}!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_viewport_size(driver, viewport_size)
|
40
|
+
Applitools::ArgumentGuard.not_nil(driver, 'Driver')
|
41
|
+
Applitools::ArgumentGuard.not_nil(viewport_size, 'viewport_size')
|
42
|
+
Applitools::ArgumentGuard.is_a?(viewport_size, 'viewport_size', Applitools::RectangleSize)
|
43
|
+
begin
|
44
|
+
Applitools::Utils::EyesSeleniumUtils.set_viewport_size eyes_driver(driver), viewport_size
|
45
|
+
rescue => e
|
46
|
+
Applitools::EyesLogger.error e.class
|
47
|
+
Applitools::EyesLogger.error e.message
|
48
|
+
raise Applitools::EyesError.new 'Failed to set viewport size!'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!attribute [rw] force_full_page_screenshot
|
54
|
+
# Forces a full page screenshot (by scrolling and stitching) if the
|
55
|
+
# browser only supports viewport screenshots.
|
56
|
+
# @return [boolean] force full page screenshot flag
|
57
|
+
# @!attribute [rw] wait_before_screenshots
|
58
|
+
# Sets the time to wait just before taking a screenshot (e.g., to allow
|
59
|
+
# positioning to stabilize when performing a full page stitching).
|
60
|
+
# @return [Float] The time to wait (Seconds). Values
|
61
|
+
# smaller or equal to 0, will cause the default value to be used.
|
62
|
+
# @!attribute [rw] hide_scrollbars
|
63
|
+
# Turns on/off hiding scrollbars before taking a screenshot
|
64
|
+
# @return [boolean] hide_scrollbars flag
|
65
|
+
# @!attribute [rw] scroll_to_region
|
66
|
+
# If set to +true+ browser will scroll to specified region (even it is out of viewport window)
|
67
|
+
# when check_region is called
|
68
|
+
# @return [boolean] scroll_to_region flag
|
69
|
+
# @!attribute [rw] stitch_mode
|
70
|
+
# May be set to :CSS or :SCROLL (:SCROLL is default).
|
71
|
+
# When :CSS - SDK will use CSS transitions to perform scrolling, otherwise it will use Javascript
|
72
|
+
# window.scroll_to() function for scrolling purposes
|
73
|
+
# @return [boolean] stitch_mode (:CSS or :SCROLL)
|
74
|
+
|
75
|
+
attr_accessor :base_agent_id, :screenshot, :force_full_page_screenshot, :hide_scrollbars,
|
76
|
+
:wait_before_screenshots, :debug_screenshot, :stitch_mode
|
77
|
+
attr_reader :driver
|
78
|
+
|
79
|
+
def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
|
80
|
+
|
81
|
+
# Creates a new (possibly disabled) Eyes instance that interacts with the
|
82
|
+
# Eyes Server at the specified url.
|
83
|
+
# @param server_url The Eyes Server URL
|
84
|
+
def initialize(server_url = nil)
|
85
|
+
super
|
86
|
+
self.base_agent_id = "eyes.selenium.ruby/#{Applitools::VERSION}".freeze
|
87
|
+
self.check_frame_or_element = false
|
88
|
+
self.region_to_check = nil
|
89
|
+
self.force_full_page_screenshot = false
|
90
|
+
self.dont_get_title = false
|
91
|
+
self.hide_scrollbars = false
|
92
|
+
self.device_pixel_ratio = UNKNOWN_DEVICE_PIXEL_RATIO
|
93
|
+
self.stitch_mode = STICH_MODE[:scroll]
|
94
|
+
self.wait_before_screenshots = DEFAULT_WAIT_BEFORE_SCREENSHOTS
|
95
|
+
self.region_visibility_strategy = MoveToRegionVisibilityStrategy.new
|
96
|
+
self.debug_screenshot = false
|
97
|
+
end
|
98
|
+
|
99
|
+
# Starts a test
|
100
|
+
# @param options [Hash] options
|
101
|
+
# @option options :driver The driver that controls the browser hosting the application
|
102
|
+
# under the test. (*Required* option)
|
103
|
+
# @option options [String] :app_name The name of the application under the test. (*Required* option)
|
104
|
+
# @option options [String] :test_name The test name (*Required* option)
|
105
|
+
# @option options [String | Hash] :viewport_size The required browser's viewport size
|
106
|
+
# (i.e., the visible part of the document's body) or +nil+ to use the current window's viewport.
|
107
|
+
# @option options :session_type The type of the test (e.g., standard test / visual performance test).
|
108
|
+
# Default value is 'SEQUENTAL'
|
109
|
+
# @return [Applitools::Selenium::Driver] A wrapped web driver which enables Eyes
|
110
|
+
# trigger recording and frame handling
|
111
|
+
def open(options = {})
|
112
|
+
driver = options.delete(:driver)
|
113
|
+
options[:viewport_size] = Applitools::RectangleSize.from_any_argument options[:viewport_size] if
|
114
|
+
options[:viewport_size]
|
115
|
+
Applitools::ArgumentGuard.not_nil driver, 'options[:driver]'
|
116
|
+
Applitools::ArgumentGuard.hash options, 'open(options)', [:app_name, :test_name]
|
117
|
+
|
118
|
+
if disabled?
|
119
|
+
logger.info('Ignored')
|
120
|
+
return driver
|
121
|
+
end
|
122
|
+
|
123
|
+
@driver = self.class.eyes_driver(driver, self)
|
124
|
+
|
125
|
+
# if driver.respond_to? :driver_for_eyes
|
126
|
+
# @driver = driver.driver_for_eyes self
|
127
|
+
# elsif driver.is_a? Capybara::Poltergeist::Driver
|
128
|
+
# @driver = Applitools::Poltergeist::Driver.new(self, driver: driver)
|
129
|
+
# else
|
130
|
+
# unless driver.is_a?(Applitools::Selenium::Driver)
|
131
|
+
# logger.warn("Unrecognized driver type: (#{driver.class.name})!")
|
132
|
+
# is_mobile_device = driver.respond_to?(:capabilities) && driver.capabilities['platformName']
|
133
|
+
# @driver = Applitools::Selenium::Driver.new(self, driver: driver, is_mobile_device: is_mobile_device)
|
134
|
+
# end
|
135
|
+
# end
|
136
|
+
|
137
|
+
self.device_pixel_ratio = UNKNOWN_DEVICE_PIXEL_RATIO
|
138
|
+
|
139
|
+
self.position_provider = self.class.position_provider(stitch_mode, driver)
|
140
|
+
|
141
|
+
self.eyes_screenshot_factory = lambda do |image|
|
142
|
+
Applitools::Selenium::EyesWebDriverScreenshot.new(
|
143
|
+
image, driver: @driver, force_offset: position_provider.force_offset
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
open_base options
|
148
|
+
@driver
|
149
|
+
end
|
150
|
+
|
151
|
+
def stitch_mode=(value)
|
152
|
+
@stitch_mode = value.to_s.upcase == STICH_MODE[:css].to_s ? STICH_MODE[:css] : STICH_MODE[:scroll]
|
153
|
+
self.position_provider = self.class.position_provider(stitch_mode, driver) unless driver.nil?
|
154
|
+
if stitch_mode == STICH_MODE[:css]
|
155
|
+
@css_transition_original_hide_scrollbars = hide_scrollbars
|
156
|
+
self.hide_scrollbars = true
|
157
|
+
else
|
158
|
+
self.hide_scrollbars = @css_transition_original_hide_scrollbars || false
|
159
|
+
end
|
160
|
+
value
|
161
|
+
end
|
162
|
+
|
163
|
+
# Takes a snapshot of the application under test and matches it with the expected output.
|
164
|
+
# @param [String] tag An optional tag to be assosiated with the snapshot.
|
165
|
+
# @param [Fixnum] match_timeout The amount of time to retry matching (seconds)
|
166
|
+
def check_window(tag = nil, match_timeout = USE_DEFAULT_MATCH_TIMEOUT)
|
167
|
+
self.tag_for_debug = tag
|
168
|
+
self.screenshot_name_enumerator = nil
|
169
|
+
if disabled?
|
170
|
+
logger.info "check_window(#{tag}, #{match_timeout}): Ignored"
|
171
|
+
return
|
172
|
+
end
|
173
|
+
|
174
|
+
logger.info "check_window(match_timeout: #{match_timeout}, tag: #{tag}): Ignored" if disabled?
|
175
|
+
logger.info "check_window(match_timeout: #{match_timeout}, tag: #{tag})"
|
176
|
+
|
177
|
+
region_provider = Object.new
|
178
|
+
region_provider.instance_eval do
|
179
|
+
define_singleton_method :region do
|
180
|
+
Applitools::Region::EMPTY
|
181
|
+
end
|
182
|
+
define_singleton_method :coordinate_type do
|
183
|
+
nil
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
self.eyes_screenshot_factory = lambda do |image|
|
188
|
+
Applitools::Selenium::EyesWebDriverScreenshot.new(
|
189
|
+
image, driver: driver, force_offset: position_provider.force_offset
|
190
|
+
)
|
191
|
+
end
|
192
|
+
|
193
|
+
check_window_base region_provider, tag, false, match_timeout
|
194
|
+
end
|
195
|
+
|
196
|
+
# @!visibility private
|
197
|
+
def title
|
198
|
+
return driver.title unless dont_get_title
|
199
|
+
rescue StandardError => e
|
200
|
+
logger.warn "failed (#{e.message})"
|
201
|
+
self.dont_get_title = false
|
202
|
+
''
|
203
|
+
end
|
204
|
+
|
205
|
+
# @!visibility private
|
206
|
+
def get_viewport_size(web_driver = driver)
|
207
|
+
Applitools::ArgumentGuard.not_nil 'web_driver', web_driver
|
208
|
+
Applitools::Utils::EyesSeleniumUtils.extract_viewport_size(driver)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Takes a snapshot of the application under test and matches a region of
|
212
|
+
# a specific element with the expected region output.
|
213
|
+
# @param [Applitools::Selenium::Element] element Represents a region to check.
|
214
|
+
# @param [Symbol] how a finder, such :css or :id. Selects a finder will be used to find an element
|
215
|
+
# See Selenium::Webdriver::Element#find_element documentation for full list of possible finders.
|
216
|
+
# @param [String] what The value will be passed to a specified finder. If finder is :css it must be a css selector.
|
217
|
+
# @param [Hash] options
|
218
|
+
# @option options [String] :tag An optional tag to be associated with the snapshot.
|
219
|
+
# @option options [Fixnum] :match_timeout The amount of time to retry matching. (Seconds)
|
220
|
+
# @option options [Boolean] :stitch_content If set to true, will try to get full content of the element
|
221
|
+
# (including hidden content due overflow settings) by scrolling the element,
|
222
|
+
# taking and stitching partial screenshots.
|
223
|
+
# @example Check region by element
|
224
|
+
# check_region(element, tag: 'Check a region by element', match_timeout: 3, stitch_content: false)
|
225
|
+
# @example Check region by css selector
|
226
|
+
# check_region(:css, '.form-row .input#e_mail', tag: 'Check a region by element', match_timeout: 3,
|
227
|
+
# stitch_content: false)
|
228
|
+
# @!parse def check_region(element, how=nil, what=nil, options = {}); end
|
229
|
+
def check_region(*args)
|
230
|
+
options = Applitools::Utils.extract_options! args
|
231
|
+
self.screenshot_name_enumerator = nil
|
232
|
+
if options.delete(:stitch_content)
|
233
|
+
check_element args, options
|
234
|
+
else
|
235
|
+
check_region_ args, options
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def check_frame(options = {})
|
240
|
+
options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil }.merge!(options)
|
241
|
+
|
242
|
+
process_in_frame options do |opts, frame_key|
|
243
|
+
if disabled?
|
244
|
+
logger.info "check_frame(#{frame_key}: #{opts[frame_key]}, timeout: #{opts[:timeout]}, " \
|
245
|
+
"tag: #{opts[:tag]}): Ignored"
|
246
|
+
return
|
247
|
+
end
|
248
|
+
|
249
|
+
logger.info "check_frame(#{frame_key}: #{opts[frame_key]}, timeout: #{opts[:timeout]}, " \
|
250
|
+
"tag: #{opts[:tag]})"
|
251
|
+
check_current_frame opts[:timeout], opts[:tag]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# @param [hash] options
|
256
|
+
# @option options []
|
257
|
+
def check_region_in_frame(options = {})
|
258
|
+
options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil, stitch_content: false }.merge!(options)
|
259
|
+
Applitools::ArgumentGuard.not_nil options[:by], 'options[:by]'
|
260
|
+
Applitools::ArgumentGuard.is_a? options[:by], 'options[:by]', Array
|
261
|
+
|
262
|
+
how_what = options.delete(:by)
|
263
|
+
|
264
|
+
process_in_frame options do |opts, frame_key|
|
265
|
+
if disabled?
|
266
|
+
logger.info "check_region_in_frame(#{frame_key}: #{options[frame_key]}, by: #{options[:by]}, " \
|
267
|
+
"timeout: #{options[:timeout]}, tag: #{options[:tag]}): Ignored)"
|
268
|
+
return
|
269
|
+
end
|
270
|
+
|
271
|
+
check_region(*how_what, tag: opts[:tag], timeout: opts[:timeout], stitch_content: opts[:stitch_content])
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# @!parse def check_region(element, how=nil, what=nil, options = {}); end
|
276
|
+
|
277
|
+
# Use this method to perform seamless testing with selenium through eyes driver.
|
278
|
+
# It yields a block and passes to it an Applitools::Selenium::Driver instance, which wraps standard driver.
|
279
|
+
# Using Selenium methods inside the 'test' block will send the messages to Selenium
|
280
|
+
# after creating the Eyes triggers for them. Options are similar to {open}
|
281
|
+
# @yieldparam driver [Applitools::Selenium::Driver] Gives a driver to a block, which translates calls to a native
|
282
|
+
# Selemium::Driver instance
|
283
|
+
# @example
|
284
|
+
# eyes.test(app_name: 'my app', test_name: 'my test') do |driver|
|
285
|
+
# driver.get "http://www.google.com"
|
286
|
+
# driver.check_window("initial")
|
287
|
+
# end
|
288
|
+
def test(options = {}, &_block)
|
289
|
+
open(options)
|
290
|
+
yield(driver)
|
291
|
+
close
|
292
|
+
ensure
|
293
|
+
abort_if_not_closed
|
294
|
+
end
|
295
|
+
|
296
|
+
# @!visibility private
|
297
|
+
def scroll_to_region
|
298
|
+
region_visibility_strategy.is_a? Applitools::Selenium::MoveToRegionVisibilityStrategy
|
299
|
+
end
|
300
|
+
|
301
|
+
# @!visibility private
|
302
|
+
def scroll_to_region=(value)
|
303
|
+
if value
|
304
|
+
self.region_visibility_strategy = Applitools::Selenium::MoveToRegionVisibilityStrategy.new
|
305
|
+
else
|
306
|
+
self.region_visibility_strategy = Applitools::Selenium::NopRegionVisibilityStrategy.new
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
# @param [Hash] options
|
311
|
+
# @option [Fixnum] :index
|
312
|
+
# @option [String] :name_or_id
|
313
|
+
# @option [Applitools::Selenium::Element] :frame_element
|
314
|
+
# @option [Array] :frames_path
|
315
|
+
# @option [Applitools::Selenium::FrameChain] :frame_chain
|
316
|
+
# @option [Fixnum] :timeout
|
317
|
+
# @option [String] :tag
|
318
|
+
def check_frame__(options = {})
|
319
|
+
options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil }.merge!(options)
|
320
|
+
|
321
|
+
unless options[:index] ||
|
322
|
+
options[:name_or_id] ||
|
323
|
+
options[:frame_element] ||
|
324
|
+
options[:frame_chain] ||
|
325
|
+
options[:frames_path]
|
326
|
+
raise Applitools::EyesIllegalArgument.new 'You must pass :index or :name_or_id or :frame_element option' \
|
327
|
+
' or :frame_chain option or :frames_path option'
|
328
|
+
end
|
329
|
+
|
330
|
+
if (needed_keys = (options.keys & %i(index name_or_id frame_element frame_chain frames_path))).length == 1
|
331
|
+
frame_key = needed_keys.first
|
332
|
+
else
|
333
|
+
raise Applitools::EyesIllegalArgument.new 'You\'ve passed some extra keys!' \
|
334
|
+
'Only one of :index, :name_or_id or :frame_elenent or :frame_chain or :frames_path is allowed.'
|
335
|
+
end
|
336
|
+
|
337
|
+
if disabled?
|
338
|
+
logger.info "check_frame(#{frame_key}: #{options[frame_key]}, timeout: #{options[:timeout]}," \
|
339
|
+
" tag: #{options[:tag]}): Ignored"
|
340
|
+
return
|
341
|
+
end
|
342
|
+
|
343
|
+
frame_or_frames = options[frame_key]
|
344
|
+
if frame_or_frames.respond_to? :pop
|
345
|
+
frame_to_check = frame_or_frames.pop
|
346
|
+
original_frame_chain = driver.frame_chain
|
347
|
+
logger.info 'Switching to parent frame according to frames path...'
|
348
|
+
driver.switch_to.frames(frame_key => frame_or_frames)
|
349
|
+
logger.info 'Done!'
|
350
|
+
case frame_to_check
|
351
|
+
when String
|
352
|
+
frame_options = { name_or_id: frame_to_check }
|
353
|
+
when Applitools::Selenium::Element
|
354
|
+
frame_options = { frame_element: frame_to_check }
|
355
|
+
else
|
356
|
+
raise Applitools::EyesError.new "Unknown frame class: #{frame_to_check.class}"
|
357
|
+
end
|
358
|
+
else
|
359
|
+
frame_options = { frame_key => options[frame_key] }
|
360
|
+
end
|
361
|
+
|
362
|
+
logger.info "check_frame(#{frame_key}: #{options[frame_key]}, timeout: #{options[:timeout]}," /
|
363
|
+
" tag: #{options[:tag]})"
|
364
|
+
logger.info 'Switching to requested frame...'
|
365
|
+
|
366
|
+
driver.switch_to.frame frame_options
|
367
|
+
logger.info 'Done!'
|
368
|
+
|
369
|
+
check_current_frame options[:timeout], options[:tag]
|
370
|
+
|
371
|
+
logger.info 'Switching back to parent_frame...'
|
372
|
+
driver.switch_to.parent_frame
|
373
|
+
logger.info 'Done!'
|
374
|
+
return unless original_frame_chain
|
375
|
+
|
376
|
+
logger.info 'Switching back into original frame...'
|
377
|
+
driver.switch_to.frames frame_chain: original_frame_chain
|
378
|
+
end
|
379
|
+
|
380
|
+
private
|
381
|
+
|
382
|
+
attr_accessor :check_frame_or_element, :region_to_check, :dont_get_title,
|
383
|
+
:device_pixel_ratio, :position_provider, :scale_provider, :tag_for_debug,
|
384
|
+
:region_visibility_strategy, :eyes_screenshot_factory
|
385
|
+
|
386
|
+
def process_in_frame(options = {})
|
387
|
+
unless options[:index] ||
|
388
|
+
options[:name_or_id] ||
|
389
|
+
options[:frame_element] ||
|
390
|
+
options[:frame_chain] ||
|
391
|
+
options[:frames_path]
|
392
|
+
raise Applitools::EyesIllegalArgument.new 'You must pass :index or :name_or_id or :frame_element option' /
|
393
|
+
'or :frame_chain option or :frames_path option'
|
394
|
+
end
|
395
|
+
|
396
|
+
if (needed_keys = (options.keys & %i(index name_or_id frame_element frame_chain frames_path))).length == 1
|
397
|
+
frame_key = needed_keys.first
|
398
|
+
else
|
399
|
+
raise Applitools::EyesIllegalArgument.new 'You\'ve passed some extra keys!' /
|
400
|
+
'Only one of :index, :name_or_id or :frame_elenent or :frame_chain or :frames_path is allowed.'
|
401
|
+
end
|
402
|
+
|
403
|
+
frame_or_frames = options[frame_key]
|
404
|
+
if frame_or_frames.respond_to? :pop
|
405
|
+
frame_to_check = frame_or_frames.pop
|
406
|
+
original_frame_chain = driver.frame_chain
|
407
|
+
logger.info 'Switching to parent frame according to frames path...'
|
408
|
+
driver.switch_to.frames(frame_key => frame_or_frames)
|
409
|
+
logger.info 'Done!'
|
410
|
+
case frame_to_check
|
411
|
+
when String
|
412
|
+
frame_options = { name_or_id: frame_to_check }
|
413
|
+
when Applitools::Selenium::Element
|
414
|
+
frame_options = { frame_element: frame_to_check }
|
415
|
+
else
|
416
|
+
raise Applitools::EyesError.new "Unknown frame class: #{frame_to_check.class}"
|
417
|
+
end
|
418
|
+
else
|
419
|
+
frame_options = { frame_key => options[frame_key] }
|
420
|
+
end
|
421
|
+
|
422
|
+
logger.info 'Switching to requested frame...'
|
423
|
+
|
424
|
+
driver.switch_to.frame frame_options
|
425
|
+
logger.info 'Done!'
|
426
|
+
|
427
|
+
yield(options, frame_key) if block_given?
|
428
|
+
|
429
|
+
logger.info 'Switching back to parent_frame...'
|
430
|
+
driver.switch_to.parent_frame
|
431
|
+
logger.info 'Done!'
|
432
|
+
|
433
|
+
return unless original_frame_chain
|
434
|
+
|
435
|
+
logger.info 'Switching back into original frame...'
|
436
|
+
driver.switch_to.frames frame_chain: original_frame_chain
|
437
|
+
end
|
438
|
+
|
439
|
+
def capture_screenshot
|
440
|
+
image_provider = Applitools::Selenium::TakesScreenshotImageProvider.new driver,
|
441
|
+
debug_screenshot: debug_screenshot, name_enumerator: screenshot_name_enumerator
|
442
|
+
logger.info 'Getting screenshot (capture_screenshot() has been invoked)'
|
443
|
+
|
444
|
+
update_scaling_params
|
445
|
+
|
446
|
+
if hide_scrollbars
|
447
|
+
begin
|
448
|
+
original_overflow = Applitools::Utils::EyesSeleniumUtils.hide_scrollbars driver
|
449
|
+
rescue Applitools::EyesDriverOperationException => e
|
450
|
+
logger.warn "Failed to hide scrollbars! Error: #{e.message}"
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
begin
|
455
|
+
if check_frame_or_element
|
456
|
+
logger.info 'Check frame/element requested'
|
457
|
+
algo = Applitools::Selenium::FullPageCaptureAlgorithm.new
|
458
|
+
|
459
|
+
entire_frame_or_element = algo.get_stiched_region(
|
460
|
+
image_provider: image_provider,
|
461
|
+
region_to_check: region_to_check,
|
462
|
+
origin_provider: position_provider,
|
463
|
+
position_provider: position_provider,
|
464
|
+
scale_provider: scale_provider,
|
465
|
+
cut_provider: cut_provider,
|
466
|
+
wait_before_screenshots: wait_before_screenshots,
|
467
|
+
eyes_screenshot_factory: eyes_screenshot_factory
|
468
|
+
)
|
469
|
+
|
470
|
+
logger.info 'Building screenshot object...'
|
471
|
+
self.screenshot = Applitools::Selenium::EyesWebDriverScreenshot.new entire_frame_or_element,
|
472
|
+
driver: driver,
|
473
|
+
entire_frame_size: Applitools::RectangleSize.new(entire_frame_or_element.width,
|
474
|
+
entire_frame_or_element.height)
|
475
|
+
elsif force_full_page_screenshot
|
476
|
+
logger.info 'Full page screenshot requested'
|
477
|
+
original_frame = driver.frame_chain
|
478
|
+
driver.switch_to.default_content
|
479
|
+
algo = Applitools::Selenium::FullPageCaptureAlgorithm.new
|
480
|
+
region_provider = Object.new
|
481
|
+
region_provider.instance_eval do
|
482
|
+
def region
|
483
|
+
Applitools::Region::EMPTY
|
484
|
+
end
|
485
|
+
|
486
|
+
def coordinate_type
|
487
|
+
nil
|
488
|
+
end
|
489
|
+
end
|
490
|
+
full_page_image = algo.get_stiched_region image_provider: image_provider,
|
491
|
+
region_to_check: region_provider,
|
492
|
+
origin_provider: Applitools::Selenium::ScrollPositionProvider.new(driver),
|
493
|
+
position_provider: position_provider,
|
494
|
+
scale_provider: scale_provider,
|
495
|
+
cut_provider: cut_provider,
|
496
|
+
wait_before_screenshots: wait_before_screenshots,
|
497
|
+
eyes_screenshot_factory: eyes_screenshot_factory
|
498
|
+
|
499
|
+
driver.switch_to.frame original_frame unless driver.frame_chain.empty?
|
500
|
+
Applitools::Selenium::EyesWebDriverScreenshot.new full_page_image, driver: driver
|
501
|
+
else
|
502
|
+
logger.info 'Screenshot requested...'
|
503
|
+
image = image_provider.take_screenshot
|
504
|
+
scale_provider.scale_image(image) if scale_provider
|
505
|
+
cut_provider.cut(image) if cut_provider
|
506
|
+
self.screenshot = eyes_screenshot_factory.call(image)
|
507
|
+
end
|
508
|
+
ensure
|
509
|
+
begin
|
510
|
+
Applitools::Utils::EyesSeleniumUtils.set_overflow driver, original_overflow
|
511
|
+
rescue Applitools::EyesDriverOperationException => e
|
512
|
+
logger.warn "Failed to revert overflow! Error: #{e.message}"
|
513
|
+
end
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def vp_size=(value)
|
518
|
+
raise Applitools::EyesNotOpenException.new 'set_viewport_size: Eyes not open!' unless open?
|
519
|
+
original_frame = driver.frame_chain
|
520
|
+
driver.switch_to.default_content
|
521
|
+
begin
|
522
|
+
Applitools::Utils::EyesSeleniumUtils.set_viewport_size driver, value
|
523
|
+
rescue => e
|
524
|
+
logger.error e.class
|
525
|
+
logger.error e.message
|
526
|
+
raise Applitools::TestFailedError.new 'Failed to set viewport size!'
|
527
|
+
ensure
|
528
|
+
driver.switch_to.frames(frame_chain: original_frame)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
alias set_viewport_size vp_size=
|
533
|
+
|
534
|
+
def get_driver(options)
|
535
|
+
# TODO: remove the "browser" related block when possible. It's for backward compatibility.
|
536
|
+
if options.key?(:browser)
|
537
|
+
logger.warn('"browser" key is deprecated, please use "driver" instead.')
|
538
|
+
return options[:browser]
|
539
|
+
end
|
540
|
+
|
541
|
+
options.fetch(:driver, nil)
|
542
|
+
end
|
543
|
+
|
544
|
+
def update_scaling_params
|
545
|
+
return unless device_pixel_ratio == UNKNOWN_DEVICE_PIXEL_RATIO
|
546
|
+
|
547
|
+
logger.info 'Trying to extract device pixel ratio...'
|
548
|
+
begin
|
549
|
+
self.device_pixel_ratio = Applitools::Utils::EyesSeleniumUtils.device_pixel_ratio(driver)
|
550
|
+
rescue Applitools::EyesDriverOperationException
|
551
|
+
logger.warn 'Failed to extract device pixel ratio! Using default.'
|
552
|
+
self.device_pixel_ratio = DEFAULT_DEVICE_PIXEL_RATIO
|
553
|
+
end
|
554
|
+
|
555
|
+
logger.info "Device pixel_ratio: #{device_pixel_ratio}"
|
556
|
+
logger.info 'Setting scale provider...'
|
557
|
+
|
558
|
+
begin
|
559
|
+
self.scale_provider = Applitools::Selenium::ContextBasedScaleProvider.new(position_provider.entire_size,
|
560
|
+
viewport_size, device_pixel_ratio)
|
561
|
+
rescue StandardError
|
562
|
+
logger.info 'Failed to set ContextBasedScaleProvider'
|
563
|
+
logger.info 'Using FixedScaleProvider instead'
|
564
|
+
self.scale_provider = Applitools::FixedScaleProvider.new(1.to_f / device_pixel_ratio)
|
565
|
+
end
|
566
|
+
logger.info 'Done!'
|
567
|
+
end
|
568
|
+
|
569
|
+
def _add_text_trigger(control, text)
|
570
|
+
unless last_screenshot
|
571
|
+
logger.info "Ignoring #{text} (no screenshot)"
|
572
|
+
return
|
573
|
+
end
|
574
|
+
|
575
|
+
unless driver.frame_chain.same_frame_chain? last_screenshot.frame_chain
|
576
|
+
logger.info "Ignoring #{text} (different_frame)"
|
577
|
+
return
|
578
|
+
end
|
579
|
+
|
580
|
+
add_text_trigger_base(control, text)
|
581
|
+
end
|
582
|
+
|
583
|
+
def add_text_trigger(control, text)
|
584
|
+
if disabled?
|
585
|
+
logger.info "Ignoring #{text} (disabled)"
|
586
|
+
return
|
587
|
+
end
|
588
|
+
|
589
|
+
Applitools::ArgumentGuard.not_nil control, 'control'
|
590
|
+
return _add_text_trigger(control, text) if control.is_a? Applitools::Region
|
591
|
+
|
592
|
+
pl = control.location
|
593
|
+
ds = control.size
|
594
|
+
|
595
|
+
element_region = Applitools::Region.new(pl.x, pl.y, ds.width, ds.height)
|
596
|
+
|
597
|
+
return _add_text_trigger(element_region, text) if control.is_a? Applitools::Selenium::Element
|
598
|
+
end
|
599
|
+
|
600
|
+
def add_mouse_trigger(mouse_action, element)
|
601
|
+
if disabled?
|
602
|
+
logger.info "Ignoring #{mouse_action} (disabled)"
|
603
|
+
return
|
604
|
+
end
|
605
|
+
|
606
|
+
if element.is_a? Hash
|
607
|
+
return add_mouse_trigger_by_region_and_location(mouse_action, element[:region], element[:location]) if
|
608
|
+
element.key?(:location) && element.key?(:region)
|
609
|
+
raise Applitools::EyesIllegalArgument.new 'Element[] doesn\'t contain required keys!'
|
610
|
+
end
|
611
|
+
|
612
|
+
Applitools::ArgumentGuard.not_nil element, 'element'
|
613
|
+
Applitools::ArgumentGuard.is_a? element, 'element', Applitools::Selenium::Element
|
614
|
+
|
615
|
+
pl = element.location
|
616
|
+
ds = element.size
|
617
|
+
|
618
|
+
element_region = Applitools::Region.new(pl.x, pl.y, ds.width, ds.height)
|
619
|
+
|
620
|
+
unless last_screenshot
|
621
|
+
logger.info "Ignoring #{mouse_action} (no screenshot)"
|
622
|
+
return
|
623
|
+
end
|
624
|
+
|
625
|
+
unless driver.frame_chain.same_frame_chain? last_screenshot.frame_chain
|
626
|
+
logger.info "Ignoring #{mouse_action} (different_frame)"
|
627
|
+
return
|
628
|
+
end
|
629
|
+
|
630
|
+
add_mouse_trigger_base(mouse_action, element_region, element_region.middle_offset)
|
631
|
+
end
|
632
|
+
|
633
|
+
# control - Region
|
634
|
+
# cursor - Location
|
635
|
+
def add_mouse_trigger_by_region_and_location(mouse_action, control, cursor)
|
636
|
+
unless last_screenshot
|
637
|
+
logger.info "Ignoring #{mouse_action} (no screenshot)"
|
638
|
+
return
|
639
|
+
end
|
640
|
+
|
641
|
+
Applitools::ArgumentGuard.is_a? control, 'control', Applitools::Region
|
642
|
+
Applitools::ArgumentGuard.is_a? cursor, 'cursor', Applitools::Location
|
643
|
+
|
644
|
+
if driver.frame_chain.same_frame_chain? last_screenshot.frame_chain
|
645
|
+
logger.info "Ignoring #{mouse_action} (different_frame)"
|
646
|
+
return
|
647
|
+
end
|
648
|
+
|
649
|
+
add_mouse_trigger_base(mouse_action, control, cursor)
|
650
|
+
end
|
651
|
+
|
652
|
+
protected
|
653
|
+
|
654
|
+
def check_current_frame(match_timeout, tag)
|
655
|
+
logger.info "check_current_frame(#{match_timeout}, #{tag})"
|
656
|
+
self.check_frame_or_element = true
|
657
|
+
|
658
|
+
region_provider = Object.new.tap do |provider|
|
659
|
+
provider.instance_eval do
|
660
|
+
define_singleton_method :region do
|
661
|
+
Applitools::Region::EMPTY
|
662
|
+
end
|
663
|
+
define_singleton_method :coordinate_type do
|
664
|
+
nil
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
self.region_to_check = Object.new.tap do |provider|
|
670
|
+
current_frame_size = lambda do
|
671
|
+
frame_region = Applitools::Region.from_location_size(
|
672
|
+
Applitools::Location.new(0, 0), driver.frame_chain!.current_frame.size
|
673
|
+
)
|
674
|
+
begin
|
675
|
+
frame_region.intersect Applitools::Region.from_location_size(
|
676
|
+
Applitools::Location.new(0, 0),
|
677
|
+
Applitools::Utils::EyesSeleniumUtils.entire_page_size(driver)
|
678
|
+
)
|
679
|
+
frame_region
|
680
|
+
ensure
|
681
|
+
frame_region
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
provider.instance_eval do
|
686
|
+
define_singleton_method :region do
|
687
|
+
current_frame_size.call
|
688
|
+
end
|
689
|
+
define_singleton_method :coordinate_type do
|
690
|
+
Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative]
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
self.eyes_screenshot_factory = lambda do |image|
|
696
|
+
Applitools::Selenium::EyesWebDriverScreenshot.new(
|
697
|
+
image, driver: driver, force_offset: position_provider.force_offset
|
698
|
+
)
|
699
|
+
end
|
700
|
+
|
701
|
+
check_window_base region_provider, tag, false, match_timeout
|
702
|
+
end
|
703
|
+
|
704
|
+
def app_environment
|
705
|
+
app_env = super
|
706
|
+
if app_env.os.nil?
|
707
|
+
logger.info 'No OS set, checking for mobile OS...'
|
708
|
+
underlying_driver = Applitools::Utils::EyesSeleniumUtils.mobile_device?
|
709
|
+
unless underlying_driver.nil?
|
710
|
+
logger.info 'Mobile device detected! Checking device type...'
|
711
|
+
if Applitools::Utils::EyesSeleniumUtils.android?(underlying_driver)
|
712
|
+
logger.info 'Android detected...'
|
713
|
+
platform_name = 'Android'
|
714
|
+
elsif Applitools::Utils::EyesSeleniumUtils.ios?(underlying_driver)
|
715
|
+
logger.info 'iOS detected...'
|
716
|
+
platform_name = 'iOS'
|
717
|
+
else
|
718
|
+
logger.info 'Unknown device type'
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
if platform_name && !platform_name.empty?
|
723
|
+
os = platform_name
|
724
|
+
platform_version = Applitools::Utils::EyesSeleniumUtils.platform_version(underlying_driver).to_s
|
725
|
+
unless platform_version.empty?
|
726
|
+
major_version = platform_version.split(/\./).first
|
727
|
+
os << " #{major_version}"
|
728
|
+
end
|
729
|
+
logger.info "Setting OS: #{os}"
|
730
|
+
app_env.os = os
|
731
|
+
end
|
732
|
+
else
|
733
|
+
logger.info 'No mobile OS detected.'
|
734
|
+
end
|
735
|
+
app_env
|
736
|
+
end
|
737
|
+
|
738
|
+
# check a region, specified by element_or_selector parameter
|
739
|
+
# @param [Array] element_or_selector Array, which contains Applitools::Selenium::Element or [:finder, :value]
|
740
|
+
# pair should be used in find_element
|
741
|
+
# @param [Hash] options
|
742
|
+
# @option options [String] :tag
|
743
|
+
# @option options [Float] :tmatch_timeout
|
744
|
+
def check_region_(element_or_selector, options = {})
|
745
|
+
selector = element_or_selector if Applitools::Selenium::Driver::FINDERS.keys.include? element_or_selector.first
|
746
|
+
element = element_or_selector.first if element_or_selector.first.instance_of? Applitools::Selenium::Element
|
747
|
+
element = driver.find_element(*selector) unless element
|
748
|
+
raise Applitools::EyesIllegalArgument.new 'You should pass :selector or :element!' unless element
|
749
|
+
|
750
|
+
if !options[:tag].nil? && !options[:tag].empty?
|
751
|
+
tag = options[:tag]
|
752
|
+
self.tag_for_debug = tag
|
753
|
+
end
|
754
|
+
|
755
|
+
match_timeout = options[:match_timeout] || USE_DEFAULT_MATCH_TIMEOUT
|
756
|
+
|
757
|
+
logger.info "check_region(element, #{match_timeout}, #{tag}): Ignored" && return if disabled?
|
758
|
+
Applitools::ArgumentGuard.not_nil 'options[:element]', element
|
759
|
+
logger.info "check_region(element: element, #{match_timeout}, #{tag})"
|
760
|
+
|
761
|
+
location_as_point = element.location
|
762
|
+
region_visibility_strategy.move_to_region position_provider,
|
763
|
+
Applitools::Location.new(location_as_point.x.to_i, location_as_point.y.to_i)
|
764
|
+
|
765
|
+
region_provider = Object.new.tap do |prov|
|
766
|
+
prov.instance_eval do
|
767
|
+
define_singleton_method :region do
|
768
|
+
p = element.location
|
769
|
+
d = element.size
|
770
|
+
Applitools::Region.from_location_size p, d
|
771
|
+
end
|
772
|
+
|
773
|
+
define_singleton_method :coordinate_type do
|
774
|
+
Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative]
|
775
|
+
end
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
self.eyes_screenshot_factory = lambda do |image|
|
780
|
+
Applitools::Selenium::EyesWebDriverScreenshot.new(image, driver: driver)
|
781
|
+
end
|
782
|
+
|
783
|
+
result = check_window_base region_provider, tag, false, match_timeout
|
784
|
+
|
785
|
+
logger.info 'Done! trying to scroll back to original position...'
|
786
|
+
region_visibility_strategy.return_to_original_position position_provider
|
787
|
+
logger.info 'Done!'
|
788
|
+
result
|
789
|
+
end
|
790
|
+
|
791
|
+
# Checks an element, specified by +element_or_selector+ parameter
|
792
|
+
# @param [Array] element_or_selector Array, which contains Applitools::Selenium::Element or [:finder, :value]
|
793
|
+
# pair should be used in find_element
|
794
|
+
# @param [Hash] options
|
795
|
+
# @option options [String] :tag
|
796
|
+
# @option options [Float] :tmatch_timeout
|
797
|
+
|
798
|
+
def check_element(element_or_selector, options = {})
|
799
|
+
selector = element_or_selector if Applitools::Selenium::Driver::FINDERS.keys.include? element_or_selector.first
|
800
|
+
if !options[:tag].nil? && !options[:tag].empty?
|
801
|
+
tag = options[:tag]
|
802
|
+
self.tag_for_debug = tag
|
803
|
+
end
|
804
|
+
match_timeout = options[:match_timeout] || USE_DEFAULT_MATCH_TIMEOUT
|
805
|
+
|
806
|
+
if disabled?
|
807
|
+
logger.info "check_element(#{options.inject([]) { |res, (k, v)| res << "#{k}: #{v}" }.join(', ')}):" /
|
808
|
+
' Ignored'
|
809
|
+
return
|
810
|
+
end
|
811
|
+
|
812
|
+
eyes_element = element_or_selector.first if element_or_selector.first.instance_of? Applitools::Selenium::Element
|
813
|
+
eyes_element = driver.find_element(*selector) unless eyes_element
|
814
|
+
raise Applitools::EyesIllegalArgument.new 'You should pass :selector or :element!' unless eyes_element
|
815
|
+
eyes_element = Applitools::Selenium::Element.new(driver, eyes_element) unless
|
816
|
+
eyes_element.is_a? Applitools::Selenium::Element
|
817
|
+
|
818
|
+
location_as_point = eyes_element.location
|
819
|
+
region_visibility_strategy.move_to_region position_provider,
|
820
|
+
Applitools::Location.new(location_as_point.x.to_i, location_as_point.y.to_i)
|
821
|
+
|
822
|
+
original_overflow = nil
|
823
|
+
original_position_provider = position_provider
|
824
|
+
|
825
|
+
begin
|
826
|
+
self.check_frame_or_element = true
|
827
|
+
self.position_provider = Applitools::Selenium::ElementPositionProvider.new driver, eyes_element
|
828
|
+
original_overflow = eyes_element.overflow
|
829
|
+
eyes_element.overflow = 'hidden'
|
830
|
+
|
831
|
+
p = eyes_element.location
|
832
|
+
d = eyes_element.size
|
833
|
+
|
834
|
+
border_left_width = eyes_element.border_left_width
|
835
|
+
border_top_width = eyes_element.border_top_width
|
836
|
+
border_right_width = eyes_element.border_right_width
|
837
|
+
border_bottom_width = eyes_element.border_bottom_width
|
838
|
+
|
839
|
+
element_region = Applitools::Region.new(
|
840
|
+
p.x + border_left_width,
|
841
|
+
p.y + border_top_width,
|
842
|
+
d.width - border_left_width - border_right_width,
|
843
|
+
d.height - border_top_width - border_bottom_width
|
844
|
+
)
|
845
|
+
|
846
|
+
logger.info "Element region: #{element_region}"
|
847
|
+
|
848
|
+
self.region_to_check = Object.new.tap do |prov|
|
849
|
+
prov.instance_eval do
|
850
|
+
define_singleton_method :region do
|
851
|
+
element_region
|
852
|
+
end
|
853
|
+
|
854
|
+
define_singleton_method :coordinate_type do
|
855
|
+
Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative]
|
856
|
+
end
|
857
|
+
end
|
858
|
+
end
|
859
|
+
|
860
|
+
base_check_region_provider = Object.new.tap do |prov|
|
861
|
+
prov.instance_eval do
|
862
|
+
define_singleton_method :region do
|
863
|
+
Applitools::Region::EMPTY
|
864
|
+
end
|
865
|
+
|
866
|
+
define_singleton_method :coordinate_type do
|
867
|
+
nil
|
868
|
+
end
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
872
|
+
self.eyes_screenshot_factory = lambda do |image|
|
873
|
+
Applitools::Selenium::EyesWebDriverScreenshot.new(
|
874
|
+
image, driver: driver, force_offset: position_provider.state
|
875
|
+
)
|
876
|
+
end
|
877
|
+
|
878
|
+
check_window_base base_check_region_provider, tag, false, match_timeout
|
879
|
+
ensure
|
880
|
+
eyes_element.overflow = original_overflow unless original_overflow.nil?
|
881
|
+
self.check_frame_or_element = false
|
882
|
+
self.position_provider = original_position_provider
|
883
|
+
self.region_to_check = nil
|
884
|
+
|
885
|
+
region_visibility_strategy.return_to_original_position position_provider
|
886
|
+
end
|
887
|
+
end
|
888
|
+
|
889
|
+
def screenshot_name_enumerator
|
890
|
+
@name_enumerator ||= Enumerator.new do |y|
|
891
|
+
counter = 1
|
892
|
+
loop do
|
893
|
+
y << "#{tag_for_debug.gsub(/\s+/, '_')}__#{Time.now.strftime('%Y_%m_%d_%H_%M')}__#{counter}.png"
|
894
|
+
counter += 1
|
895
|
+
end
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
# Resets screenshot_names sequence to initial state.
|
900
|
+
# @param [Boolean] value should be false or nil to reset the sequence. Takes no effect if +true+ passed
|
901
|
+
def screenshot_name_enumerator=(value)
|
902
|
+
@name_enumerator = nil unless value
|
903
|
+
end
|
904
|
+
|
905
|
+
def inferred_environment
|
906
|
+
user_agent = driver.user_agent
|
907
|
+
return "useragent: #{user_agent}" if user_agent && !user_agent.empty?
|
908
|
+
nil
|
909
|
+
end
|
910
|
+
|
911
|
+
class << self
|
912
|
+
def position_provider(stitch_mode, driver)
|
913
|
+
case stitch_mode
|
914
|
+
when :SCROLL
|
915
|
+
Applitools::Selenium::ScrollPositionProvider.new(driver)
|
916
|
+
when :CSS
|
917
|
+
Applitools::Selenium::CssTranslatePositionProvider.new(driver)
|
918
|
+
end
|
919
|
+
end
|
920
|
+
end
|
921
|
+
end
|
922
|
+
end
|