percy-selenium 1.1.1 → 1.1.2.pre.beta.0
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/Gemfile +1 -1
- data/lib/cache.rb +49 -0
- data/lib/driver_metadata.rb +57 -0
- data/lib/percy.rb +261 -39
- data/lib/version.rb +1 -1
- data/package.json +1 -1
- data/spec/lib/percy/cache_spec.rb +127 -0
- data/spec/lib/percy/driver_metadata_spec.rb +139 -0
- data/spec/lib/percy/percy_spec.rb +1025 -37
- data/spec/spec_helper.rb +1 -1
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa2f41ca77fb6671ac305eb3ff0a99f61cef87b290b885ed8c16a462a865bbb9
|
|
4
|
+
data.tar.gz: f45b3651099a2f84d098d4eb48db83b3685646ad3902b85a10b5f865a3b9fd48
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 821f02d3e049227bb1e294402cfb37ce097c21c775d3c8870871cc650db787784696ef55e0df593d817123a86664e8e868a9171526168a9d3d72835208d08c56
|
|
7
|
+
data.tar.gz: 7e54763dd644d9a65489d2d9c043d0e82bee79637467b812e2ff9f80267d943c0fcf722095ca4c94b35a24258158e8af96c713edb6c27bf14aaee64dd3a86fc0
|
data/Gemfile
CHANGED
data/lib/cache.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
class Cache
|
|
2
|
+
CACHE = {} # rubocop:disable Style/MutableConstant
|
|
3
|
+
CACHE_TIMEOUT = 5 * 60 # 300 seconds
|
|
4
|
+
TIMEOUT_KEY = 'last_access_time'.freeze
|
|
5
|
+
MUTEX = Mutex.new
|
|
6
|
+
|
|
7
|
+
# Caching Keys
|
|
8
|
+
CAPABILITIES = 'capabilities'.freeze
|
|
9
|
+
COMMAND_EXECUTOR_URL = 'command_executor_url'.freeze
|
|
10
|
+
|
|
11
|
+
def self.check_types(session_id, property)
|
|
12
|
+
raise TypeError, 'Argument session_id should be string' unless session_id.is_a?(String)
|
|
13
|
+
raise TypeError, 'Argument property should be string' unless property.is_a?(String)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.set_cache(session_id, property, value)
|
|
17
|
+
check_types(session_id, property)
|
|
18
|
+
MUTEX.synchronize do
|
|
19
|
+
session = CACHE[session_id] || {}
|
|
20
|
+
session[TIMEOUT_KEY] = Time.now.to_f
|
|
21
|
+
session[property] = value
|
|
22
|
+
CACHE[session_id] = session
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.get_cache(session_id, property)
|
|
27
|
+
check_types(session_id, property)
|
|
28
|
+
MUTEX.synchronize do
|
|
29
|
+
cleanup_cache
|
|
30
|
+
session = CACHE[session_id] || {}
|
|
31
|
+
session[property]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.clear_cache!
|
|
36
|
+
MUTEX.synchronize do
|
|
37
|
+
CACHE.clear
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.cleanup_cache
|
|
42
|
+
now = Time.now.to_f
|
|
43
|
+
CACHE.delete_if do |_, session|
|
|
44
|
+
timestamp = session[TIMEOUT_KEY]
|
|
45
|
+
timestamp && (now - timestamp >= CACHE_TIMEOUT)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
private_class_method :cleanup_cache
|
|
49
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require_relative 'cache'
|
|
2
|
+
|
|
3
|
+
class DriverMetaData
|
|
4
|
+
def initialize(driver)
|
|
5
|
+
@driver = driver
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def session_id
|
|
9
|
+
@driver.session_id
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def command_executor_url
|
|
13
|
+
cached = Cache.get_cache(session_id, Cache::COMMAND_EXECUTOR_URL)
|
|
14
|
+
return cached unless cached.nil?
|
|
15
|
+
|
|
16
|
+
url = nil
|
|
17
|
+
begin
|
|
18
|
+
raw = @driver.send(:bridge).http.send(:server_url)
|
|
19
|
+
url = raw.to_s unless raw.nil?
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
if defined?(Percy)
|
|
22
|
+
Percy.log("Could not get command_executor_url via bridge.http.server_url: #{e}", 'debug')
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
if url.nil? || url.empty?
|
|
27
|
+
begin
|
|
28
|
+
raw = @driver.send(:bridge).http.instance_variable_get(:@server_url)
|
|
29
|
+
url = raw.to_s unless raw.nil?
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
Percy.log("Could not get @server_url instance variable: #{e}", 'debug') if defined?(Percy)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
url ||= ''
|
|
36
|
+
Cache.set_cache(session_id, Cache::COMMAND_EXECUTOR_URL, url)
|
|
37
|
+
url
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def capabilities
|
|
41
|
+
cached = Cache.get_cache(session_id, Cache::CAPABILITIES)
|
|
42
|
+
return cached unless cached.nil?
|
|
43
|
+
|
|
44
|
+
caps = begin
|
|
45
|
+
@driver.capabilities.as_json
|
|
46
|
+
rescue StandardError
|
|
47
|
+
begin
|
|
48
|
+
@driver.capabilities.to_h
|
|
49
|
+
rescue StandardError
|
|
50
|
+
{}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Cache.set_cache(session_id, Cache::CAPABILITIES, caps)
|
|
55
|
+
caps
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/percy.rb
CHANGED
|
@@ -3,15 +3,32 @@ require 'json'
|
|
|
3
3
|
require 'version'
|
|
4
4
|
require 'net/http'
|
|
5
5
|
require 'selenium-webdriver'
|
|
6
|
+
require_relative 'driver_metadata'
|
|
6
7
|
|
|
7
8
|
module Percy
|
|
8
9
|
CLIENT_INFO = "percy-selenium-ruby/#{VERSION}".freeze
|
|
9
10
|
ENV_INFO = "selenium/#{Selenium::WebDriver::VERSION} ruby/#{RUBY_VERSION}".freeze
|
|
10
11
|
|
|
12
|
+
SESSION_TYPE_AUTOMATE = 'automate'.freeze
|
|
13
|
+
SESSION_TYPE_WEB = 'web'.freeze
|
|
14
|
+
|
|
11
15
|
PERCY_DEBUG = ENV['PERCY_LOGLEVEL'] == 'debug'
|
|
12
16
|
PERCY_SERVER_ADDRESS = ENV['PERCY_SERVER_ADDRESS'] || 'http://localhost:5338'
|
|
13
17
|
LABEL = "[\u001b[35m" + (PERCY_DEBUG ? 'percy:ruby' : 'percy') + "\u001b[39m]"
|
|
14
|
-
|
|
18
|
+
RESPONSIVE_CAPTURE_SLEEP_TIME = ENV['RESPONSIVE_CAPTURE_SLEEP_TIME'] ||
|
|
19
|
+
ENV['RESONSIVE_CAPTURE_SLEEP_TIME']
|
|
20
|
+
|
|
21
|
+
def self.responsive_capture_reload_page?
|
|
22
|
+
val = ENV['PERCY_RESPONSIVE_CAPTURE_RELOAD_PAGE'] ||
|
|
23
|
+
ENV['PERCY_RESONSIVE_CAPTURE_RELOAD_PAGE'] || 'false'
|
|
24
|
+
val.casecmp('true') == 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.responsive_capture_min_height?
|
|
28
|
+
val = ENV['PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT'] ||
|
|
29
|
+
ENV['PERCY_RESONSIVE_CAPTURE_MIN_HEIGHT'] || 'false'
|
|
30
|
+
val.casecmp('true') == 0
|
|
31
|
+
end
|
|
15
32
|
|
|
16
33
|
def self.create_region(
|
|
17
34
|
bounding_box: nil, element_xpath: nil, element_css: nil, padding: nil,
|
|
@@ -49,16 +66,23 @@ module Percy
|
|
|
49
66
|
region
|
|
50
67
|
end
|
|
51
68
|
|
|
52
|
-
# Take a DOM snapshot and post it to the snapshot endpoint
|
|
53
69
|
def self.snapshot(driver, name, options = {})
|
|
54
70
|
return unless percy_enabled?
|
|
55
71
|
|
|
72
|
+
if @session_type == SESSION_TYPE_AUTOMATE
|
|
73
|
+
raise StandardError, 'Invalid function call - percy_snapshot(). ' \
|
|
74
|
+
'Please use percy_screenshot() function while using Percy with Automate. ' \
|
|
75
|
+
'For more information on usage of percy_screenshot(), ' \
|
|
76
|
+
'refer https://www.browserstack.com/docs/percy/integrate/functional-and-visual'
|
|
77
|
+
end
|
|
78
|
+
|
|
56
79
|
begin
|
|
57
|
-
|
|
80
|
+
percy_dom_script = fetch_percy_dom
|
|
81
|
+
driver.execute_script(percy_dom_script)
|
|
58
82
|
dom_snapshot = if responsive_snapshot_capture?(options)
|
|
59
|
-
capture_responsive_dom(driver, options)
|
|
83
|
+
capture_responsive_dom(driver, options, percy_dom_script: percy_dom_script)
|
|
60
84
|
else
|
|
61
|
-
get_serialized_dom(driver, options)
|
|
85
|
+
get_serialized_dom(driver, options, percy_dom_script: percy_dom_script)
|
|
62
86
|
end
|
|
63
87
|
|
|
64
88
|
response = fetch('percy/snapshot',
|
|
@@ -69,11 +93,11 @@ module Percy
|
|
|
69
93
|
environment_info: ENV_INFO,
|
|
70
94
|
**options,)
|
|
71
95
|
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
body = JSON.parse(response.body)
|
|
97
|
+
unless body['success']
|
|
98
|
+
raise StandardError, body['error']
|
|
74
99
|
end
|
|
75
100
|
|
|
76
|
-
body = JSON.parse(response.body)
|
|
77
101
|
body['data']
|
|
78
102
|
rescue StandardError => e
|
|
79
103
|
log("Could not take DOM snapshot '#{name}'")
|
|
@@ -82,7 +106,6 @@ module Percy
|
|
|
82
106
|
end
|
|
83
107
|
|
|
84
108
|
def self.get_browser_instance(driver)
|
|
85
|
-
# this means it is a capybara session
|
|
86
109
|
if driver.respond_to?(:driver) && driver.driver.respond_to?(:browser)
|
|
87
110
|
return driver.driver.browser.manage
|
|
88
111
|
end
|
|
@@ -90,28 +113,131 @@ module Percy
|
|
|
90
113
|
driver.manage
|
|
91
114
|
end
|
|
92
115
|
|
|
93
|
-
def self.get_serialized_dom(driver, options)
|
|
116
|
+
def self.get_serialized_dom(driver, options, percy_dom_script: nil)
|
|
94
117
|
dom_snapshot = driver.execute_script("return PercyDOM.serialize(#{options.to_json})")
|
|
118
|
+
begin
|
|
119
|
+
page_origin = get_origin(driver.current_url)
|
|
120
|
+
iframes = percy_dom_script ? driver.find_elements(:tag_name, 'iframe') : []
|
|
121
|
+
if iframes.any?
|
|
122
|
+
processed_frames = []
|
|
123
|
+
iframes.each do |frame|
|
|
124
|
+
frame_src = frame.attribute('src')
|
|
125
|
+
next if unsupported_iframe_src?(frame_src)
|
|
126
|
+
|
|
127
|
+
begin
|
|
128
|
+
frame_origin = get_origin(URI.join(driver.current_url, frame_src).to_s)
|
|
129
|
+
rescue StandardError => e
|
|
130
|
+
log("Skipping iframe \"#{frame_src}\": #{e}", 'debug')
|
|
131
|
+
next
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
next if frame_origin == page_origin
|
|
135
|
+
|
|
136
|
+
result = process_frame(driver, frame, options, percy_dom_script)
|
|
137
|
+
processed_frames << result if result
|
|
138
|
+
end
|
|
139
|
+
dom_snapshot['corsIframes'] = processed_frames if processed_frames.any?
|
|
140
|
+
end
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
log("Failed to process cross-origin iframes: #{e}", 'debug')
|
|
143
|
+
begin
|
|
144
|
+
driver.switch_to.default_content
|
|
145
|
+
rescue StandardError
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
end
|
|
95
149
|
|
|
96
150
|
dom_snapshot['cookies'] = get_browser_instance(driver).all_cookies
|
|
97
151
|
dom_snapshot
|
|
98
152
|
end
|
|
99
153
|
|
|
100
|
-
def self.
|
|
101
|
-
|
|
154
|
+
def self.unsupported_iframe_src?(src)
|
|
155
|
+
src.nil? || src.empty? || src == 'about:blank' ||
|
|
156
|
+
src.start_with?('javascript:') || src.start_with?('data:') || src.start_with?('vbscript:')
|
|
157
|
+
end
|
|
102
158
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
159
|
+
def self.get_origin(url)
|
|
160
|
+
uri = URI.parse(url)
|
|
161
|
+
raise URI::InvalidURIError, "no host in #{url}" if uri.host.nil?
|
|
162
|
+
|
|
163
|
+
netloc = uri.host.to_s
|
|
164
|
+
default_ports = {'http' => 80, 'https' => 443}
|
|
165
|
+
netloc += ":#{uri.port}" if uri.port && uri.port != default_ports[uri.scheme]
|
|
166
|
+
"#{uri.scheme}://#{netloc}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def self.process_frame(driver, frame_element, options, percy_dom_script)
|
|
170
|
+
frame_url = frame_element.attribute('src') || 'unknown-src'
|
|
171
|
+
iframe_snapshot = nil
|
|
172
|
+
|
|
173
|
+
begin
|
|
174
|
+
driver.switch_to.frame(frame_element)
|
|
175
|
+
begin
|
|
176
|
+
driver.execute_script(percy_dom_script)
|
|
177
|
+
iframe_options = options.merge('enableJavaScript' => true)
|
|
178
|
+
iframe_snapshot =
|
|
179
|
+
driver.execute_script("return PercyDOM.serialize(#{iframe_options.to_json})")
|
|
180
|
+
rescue StandardError => e
|
|
181
|
+
log("Failed to process cross-origin frame #{frame_url}: #{e}", 'debug')
|
|
182
|
+
ensure
|
|
183
|
+
begin
|
|
184
|
+
driver.switch_to.default_content
|
|
185
|
+
rescue StandardError
|
|
186
|
+
begin
|
|
187
|
+
driver.switch_to.parent_frame
|
|
188
|
+
rescue StandardError
|
|
189
|
+
nil
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
rescue StandardError => e
|
|
194
|
+
log("Failed to switch to frame #{frame_url}: #{e}", 'debug')
|
|
195
|
+
begin
|
|
196
|
+
driver.switch_to.default_content
|
|
197
|
+
rescue StandardError
|
|
198
|
+
nil
|
|
199
|
+
end
|
|
200
|
+
return nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
return nil if iframe_snapshot.nil?
|
|
204
|
+
|
|
205
|
+
percy_element_id = frame_element.attribute('data-percy-element-id')
|
|
206
|
+
unless percy_element_id
|
|
207
|
+
log("Skipping frame #{frame_url}: no matching percyElementId found", 'debug')
|
|
208
|
+
return nil
|
|
109
209
|
end
|
|
110
210
|
|
|
111
|
-
|
|
211
|
+
{
|
|
212
|
+
'iframeData' => {'percyElementId' => percy_element_id},
|
|
213
|
+
'iframeSnapshot' => iframe_snapshot,
|
|
214
|
+
'frameUrl' => frame_url,
|
|
215
|
+
}
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def self.get_responsive_widths(widths = [])
|
|
219
|
+
begin
|
|
220
|
+
widths_list = widths.is_a?(Array) ? widths : []
|
|
221
|
+
query_param = widths_list.any? ? "?widths=#{widths_list.join(',')}" : ''
|
|
222
|
+
response = fetch("percy/widths-config#{query_param}")
|
|
223
|
+
data = JSON.parse(response.body)
|
|
224
|
+
widths_data = data['widths']
|
|
225
|
+
unless widths_data.is_a?(Array)
|
|
226
|
+
msg = 'Update Percy CLI to the latest version to use responsiveSnapshotCapture'
|
|
227
|
+
raise StandardError, msg
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
widths_data
|
|
231
|
+
rescue StandardError => e
|
|
232
|
+
log("Failed to get responsive widths: #{e}.", 'debug')
|
|
233
|
+
raise StandardError, 'Update Percy CLI to the latest version to use ' \
|
|
234
|
+
'responsiveSnapshotCapture'
|
|
235
|
+
end
|
|
112
236
|
end
|
|
113
237
|
|
|
114
238
|
def self.change_window_dimension_and_wait(driver, width, height, resize_count)
|
|
239
|
+
log("Attempting to resize window to #{width}x#{height}", 'debug')
|
|
240
|
+
|
|
115
241
|
begin
|
|
116
242
|
if driver.capabilities.browser_name == 'chrome' && driver.respond_to?(:execute_cdp)
|
|
117
243
|
driver.execute_cdp('Emulation.setDeviceMetricsOverride', {
|
|
@@ -119,50 +245,89 @@ module Percy
|
|
|
119
245
|
},)
|
|
120
246
|
else
|
|
121
247
|
get_browser_instance(driver).window.resize_to(width, height)
|
|
248
|
+
driver.execute_script("window.dispatchEvent(new Event('resize'));")
|
|
122
249
|
end
|
|
123
250
|
rescue StandardError => e
|
|
124
251
|
log("Resizing using cdp failed, falling back to driver for width #{width} #{e}", 'debug')
|
|
125
252
|
get_browser_instance(driver).window.resize_to(width, height)
|
|
253
|
+
driver.execute_script("window.dispatchEvent(new Event('resize'));")
|
|
126
254
|
end
|
|
127
255
|
|
|
128
256
|
begin
|
|
129
257
|
wait = Selenium::WebDriver::Wait.new(timeout: 1)
|
|
130
258
|
wait.until { driver.execute_script('return window.resizeCount') == resize_count }
|
|
259
|
+
actual_size = driver.execute_script('return { w: window.innerWidth, h: window.innerHeight }')
|
|
260
|
+
log("Resize successful. New Viewport Size: #{actual_size['w']}x#{actual_size['h']}", 'debug')
|
|
131
261
|
rescue Selenium::WebDriver::Error::TimeoutError
|
|
132
262
|
log("Timed out waiting for window resize event for width #{width}", 'debug')
|
|
133
263
|
end
|
|
134
264
|
end
|
|
135
265
|
|
|
136
|
-
def self.capture_responsive_dom(driver, options)
|
|
137
|
-
widths =
|
|
266
|
+
def self.capture_responsive_dom(driver, options, percy_dom_script: nil)
|
|
267
|
+
widths = get_responsive_widths(options[:widths] || [])
|
|
138
268
|
dom_snapshots = []
|
|
139
269
|
window_size = get_browser_instance(driver).window.size
|
|
140
270
|
current_width = window_size.width
|
|
141
271
|
current_height = window_size.height
|
|
142
272
|
last_window_width = current_width
|
|
273
|
+
last_window_height = current_height
|
|
143
274
|
resize_count = 0
|
|
144
275
|
driver.execute_script('PercyDOM.waitForResize()')
|
|
276
|
+
target_height = current_height
|
|
145
277
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
278
|
+
if responsive_capture_min_height?
|
|
279
|
+
min = options[:minHeight] || @cli_config&.dig('snapshot', 'minHeight')
|
|
280
|
+
if min
|
|
281
|
+
target_height = min
|
|
282
|
+
else
|
|
283
|
+
log('PERCY_RESPONSIVE_CAPTURE_MIN_HEIGHT is enabled but no minHeight value ' \
|
|
284
|
+
'was provided in options or CLI config; using current window height', 'debug',)
|
|
151
285
|
end
|
|
286
|
+
end
|
|
152
287
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
288
|
+
begin
|
|
289
|
+
widths.each do |width_dict|
|
|
290
|
+
width = width_dict['width']
|
|
291
|
+
height = width_dict['height'] || target_height
|
|
292
|
+
|
|
293
|
+
if last_window_width != width || last_window_height != height
|
|
294
|
+
resize_count += 1
|
|
295
|
+
change_window_dimension_and_wait(driver, width, height, resize_count)
|
|
296
|
+
last_window_width = width
|
|
297
|
+
last_window_height = height
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
if responsive_capture_reload_page?
|
|
301
|
+
log("Reloading page for width: #{width}", 'debug')
|
|
302
|
+
begin
|
|
303
|
+
driver.navigate.refresh
|
|
304
|
+
rescue StandardError
|
|
305
|
+
begin
|
|
306
|
+
driver.driver.browser.navigate.refresh
|
|
307
|
+
rescue StandardError => e
|
|
308
|
+
log("Failed to refresh page: #{e}", 'debug')
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
percy_dom_script = fetch_percy_dom
|
|
312
|
+
driver.execute_script(percy_dom_script)
|
|
313
|
+
driver.execute_script('PercyDOM.waitForResize()')
|
|
314
|
+
resize_count = 0
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
sleep(RESPONSIVE_CAPTURE_SLEEP_TIME.to_i) if RESPONSIVE_CAPTURE_SLEEP_TIME
|
|
318
|
+
|
|
319
|
+
dom_snapshot = get_serialized_dom(driver, options, percy_dom_script: percy_dom_script)
|
|
320
|
+
dom_snapshot['width'] = width
|
|
321
|
+
dom_snapshots << dom_snapshot
|
|
322
|
+
end
|
|
323
|
+
ensure
|
|
324
|
+
change_window_dimension_and_wait(driver, current_width, current_height, resize_count + 1)
|
|
158
325
|
end
|
|
159
326
|
|
|
160
|
-
change_window_dimension_and_wait(driver, current_width, current_height, resize_count + 1)
|
|
161
327
|
dom_snapshots
|
|
162
328
|
end
|
|
163
329
|
|
|
164
330
|
def self.responsive_snapshot_capture?(options)
|
|
165
|
-
# Don't run responsive snapshot capture when defer uploads is enabled
|
|
166
331
|
return false if @cli_config&.dig('percy', 'deferUploads')
|
|
167
332
|
|
|
168
333
|
options[:responsive_snapshot_capture] ||
|
|
@@ -170,7 +335,6 @@ module Percy
|
|
|
170
335
|
@cli_config&.dig('snapshot', 'responsiveSnapshotCapture')
|
|
171
336
|
end
|
|
172
337
|
|
|
173
|
-
# Determine if the Percy server is running, caching the result so it is only checked once
|
|
174
338
|
def self.percy_enabled?
|
|
175
339
|
return @percy_enabled unless @percy_enabled.nil?
|
|
176
340
|
|
|
@@ -194,8 +358,8 @@ module Percy
|
|
|
194
358
|
end
|
|
195
359
|
|
|
196
360
|
response_body = JSON.parse(response.body)
|
|
197
|
-
@eligible_widths = response_body['widths']
|
|
198
361
|
@cli_config = response_body['config']
|
|
362
|
+
@session_type = response_body['type']
|
|
199
363
|
@percy_enabled = true
|
|
200
364
|
true
|
|
201
365
|
rescue StandardError => e
|
|
@@ -206,7 +370,6 @@ module Percy
|
|
|
206
370
|
end
|
|
207
371
|
end
|
|
208
372
|
|
|
209
|
-
# Fetch the @percy/dom script, caching the result so it is only fetched once
|
|
210
373
|
def self.fetch_percy_dom
|
|
211
374
|
return @percy_dom unless @percy_dom.nil?
|
|
212
375
|
|
|
@@ -229,8 +392,6 @@ module Percy
|
|
|
229
392
|
end
|
|
230
393
|
end
|
|
231
394
|
|
|
232
|
-
# Make an HTTP request (GET,POST) using Ruby's Net::HTTP. If `data` is present,
|
|
233
|
-
# `fetch` will POST as JSON.
|
|
234
395
|
def self.fetch(url, data = nil)
|
|
235
396
|
uri = URI("#{PERCY_SERVER_ADDRESS}/#{url}")
|
|
236
397
|
|
|
@@ -251,10 +412,71 @@ module Percy
|
|
|
251
412
|
response
|
|
252
413
|
end
|
|
253
414
|
|
|
415
|
+
def self.percy_screenshot(driver, name, options = {})
|
|
416
|
+
return unless percy_enabled?
|
|
417
|
+
|
|
418
|
+
unless @session_type == SESSION_TYPE_AUTOMATE
|
|
419
|
+
raise StandardError, 'Invalid function call - percy_screenshot(). ' \
|
|
420
|
+
'Please use percy_snapshot() function for taking screenshot. ' \
|
|
421
|
+
'percy_screenshot() should be used only while using Percy with Automate. ' \
|
|
422
|
+
'For more information on usage of percy_snapshot(), ' \
|
|
423
|
+
'refer doc for your language https://www.browserstack.com/docs/percy/integrate/overview'
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
begin
|
|
427
|
+
options = options.dup
|
|
428
|
+
metadata = get_driver_metadata(driver)
|
|
429
|
+
|
|
430
|
+
if options.key?(:ignoreRegionSeleniumElements)
|
|
431
|
+
options[:ignore_region_selenium_elements] = options.delete(:ignoreRegionSeleniumElements)
|
|
432
|
+
end
|
|
433
|
+
if options.key?(:considerRegionSeleniumElements)
|
|
434
|
+
options[:consider_region_selenium_elements] =
|
|
435
|
+
options.delete(:considerRegionSeleniumElements)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
ignore_region_elements =
|
|
439
|
+
get_element_ids(options.delete(:ignore_region_selenium_elements) || [])
|
|
440
|
+
consider_region_elements =
|
|
441
|
+
get_element_ids(options.delete(:consider_region_selenium_elements) || [])
|
|
442
|
+
|
|
443
|
+
options[:ignore_region_elements] = ignore_region_elements
|
|
444
|
+
options[:consider_region_elements] = consider_region_elements
|
|
445
|
+
|
|
446
|
+
response = fetch('percy/automateScreenshot',
|
|
447
|
+
client_info: CLIENT_INFO,
|
|
448
|
+
environment_info: ENV_INFO,
|
|
449
|
+
sessionId: metadata.session_id,
|
|
450
|
+
commandExecutorUrl: metadata.command_executor_url,
|
|
451
|
+
capabilities: metadata.capabilities,
|
|
452
|
+
snapshotName: name,
|
|
453
|
+
options: options,)
|
|
454
|
+
|
|
455
|
+
body = JSON.parse(response.body)
|
|
456
|
+
unless body['success']
|
|
457
|
+
raise StandardError, body['error']
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
body['data']
|
|
461
|
+
rescue StandardError => e
|
|
462
|
+
log("Could not take Screenshot '#{name}'")
|
|
463
|
+
log(e, 'debug')
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def self.get_driver_metadata(driver)
|
|
468
|
+
DriverMetaData.new(driver)
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def self.get_element_ids(elements)
|
|
472
|
+
elements.map(&:id)
|
|
473
|
+
end
|
|
474
|
+
|
|
254
475
|
def self._clear_cache!
|
|
255
476
|
@percy_dom = nil
|
|
256
477
|
@percy_enabled = nil
|
|
257
|
-
@eligible_widths = nil
|
|
258
478
|
@cli_config = nil
|
|
479
|
+
@session_type = nil
|
|
480
|
+
Cache.clear_cache!
|
|
259
481
|
end
|
|
260
482
|
end
|
data/lib/version.rb
CHANGED