eyes_selenium 3.18.2 → 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: false
2
+
3
+ require 'selenium-webdriver'
4
+
5
+
6
+ module Applitools
7
+ module Selenium
8
+ class DomSnapshotScript
9
+
10
+ attr_accessor :driver
11
+
12
+ def initialize(driver)
13
+ self.driver = driver
14
+ end
15
+
16
+ def create_dom_snapshot(
17
+ dont_fetch_resources,
18
+ urls_to_skip,
19
+ cross_origin_rendering,
20
+ use_cookies
21
+ )
22
+ serialize_resources = true
23
+ compress_resources = false
24
+ script = DomSnapshotScript.new(driver, urls_to_skip, dont_fetch_resources, serialize_resources, compress_resources)
25
+ snapshotter = RecursiveSnapshotter.new(driver, script, cross_origin_rendering, use_cookies)
26
+
27
+ begin
28
+ snapshotter.create_cross_frames_dom_snapshots
29
+ rescue StandardError => e
30
+ Applitools::EyesLogger.error e.class.to_s
31
+ Applitools::EyesLogger.error e.message
32
+ raise ::Applitools::EyesError.new 'Error while getting dom snapshot!'
33
+ end
34
+
35
+ end
36
+
37
+ class DomSnapshotScript
38
+ DOM_EXTRACTION_TIMEOUT = 300
39
+
40
+ attr_accessor :driver, :urls_to_skip, :dont_fetch_resources, :serialize_resources, :compress_resources
41
+
42
+ def initialize(driver, urls_to_skip, dont_fetch_resources, serialize_resources = false, compress_resources = false)
43
+ self.driver = driver
44
+ self.urls_to_skip = urls_to_skip
45
+ self.dont_fetch_resources = dont_fetch_resources
46
+ self.compress_resources = compress_resources
47
+ self.serialize_resources = serialize_resources
48
+ end
49
+
50
+ def process_page_script
51
+ Applitools::Selenium::Scripts::PROCESS_PAGE_AND_POLL
52
+ end
53
+
54
+ def script_options
55
+ options = []
56
+ options.push("dontFetchResources: #{dont_fetch_resources}")
57
+ options.push("skipResources: [#{urls_to_skip}]")
58
+ options.push("compressResources: #{compress_resources}")
59
+ options.push("serializeResources: #{serialize_resources}")
60
+ "{#{options.join(', ')}}"
61
+ end
62
+
63
+ def script
64
+ "#{process_page_script} return __processPageAndSerializePoll(#{script_options});"
65
+ end
66
+
67
+ def run
68
+ Applitools::EyesLogger.info 'Trying to get DOM snapshot...'
69
+
70
+ script_thread = Thread.new do
71
+ result = {}
72
+ while result['status'] != 'SUCCESS'
73
+ Thread.current[:script_result] = driver.execute_script(script)
74
+ begin
75
+ Thread.current[:result] = result = Oj.load(Thread.current[:script_result])
76
+ sleep 0.5
77
+ rescue Oj::ParseError => e
78
+ Applitools::EyesLogger.warn e.message
79
+ end
80
+ end
81
+ end
82
+ sleep 0.5
83
+ script_thread_result = script_thread.join(DOM_EXTRACTION_TIMEOUT)
84
+ raise ::Applitools::EyesError.new 'Timeout error while getting dom snapshot!' unless script_thread_result
85
+ Applitools::EyesLogger.info 'Done!'
86
+
87
+ script_thread_result[:result]['value']
88
+ end
89
+
90
+ end
91
+
92
+
93
+ class RecursiveSnapshotter
94
+
95
+ POLL_INTERVAL_MS = 0.5
96
+
97
+ attr_accessor :should_skip_failed_frames
98
+
99
+ attr_accessor :driver, :script, :cross_origin_rendering, :use_cookies
100
+
101
+ def initialize(driver, script, cross_origin_rendering, use_cookies)
102
+ self.should_skip_failed_frames = true
103
+ self.driver = driver
104
+ self.script = script
105
+ self.cross_origin_rendering = cross_origin_rendering
106
+ self.use_cookies = use_cookies
107
+ end
108
+
109
+ def create_cross_frames_dom_snapshots
110
+ dom = create_dom_snapshot_threaded
111
+ process_dom_snapshot_frames dom
112
+ dom
113
+ end
114
+
115
+ def create_dom_snapshot_threaded
116
+ script.run
117
+ end
118
+
119
+ def process_dom_snapshot_frames dom
120
+ dom["cookies"] = driver.manage.all_cookies if use_cookies
121
+
122
+ dom["frames"].each do |frame|
123
+ selector = frame['selector']
124
+ unless selector
125
+ Applitools::EyesLogger.warn "inner frame with null selector"
126
+ next
127
+ end
128
+ begin
129
+ original_frame_chain = driver.frame_chain
130
+ frame_element = driver.find_element(:css, selector)
131
+ frame_src = frame_element.attribute('src')
132
+ Applitools::EyesLogger.info "process_dom_snapshot_frames src = #{frame_src}"
133
+ driver.switch_to.frame(frame_element)
134
+
135
+ process_dom_snapshot_frames frame
136
+
137
+ driver.switch_to.default_content
138
+ unless original_frame_chain.empty?
139
+ driver.switch_to.frames frame_chain: original_frame_chain
140
+ end
141
+ rescue StandardError => e
142
+ Applitools::EyesLogger.error e.class.to_s
143
+ Applitools::EyesLogger.error e.message
144
+ if should_skip_failed_frames
145
+ Applitools::EyesLogger.warn "failed switching to frame #{selector}"
146
+ else
147
+ raise ::Applitools::EyesError.new 'failed switching to frame'
148
+ end
149
+ end
150
+ end
151
+
152
+ self.snapshot_and_process_cors_frames(dom) if self.cross_origin_rendering
153
+ end
154
+
155
+ def snapshot_and_process_cors_frames(dom)
156
+ dom["crossFrames"].each do |frame|
157
+ selector = frame['selector']
158
+ unless selector
159
+ Applitools::EyesLogger.warn "cross frame with null selector"
160
+ next
161
+ end
162
+ frame_index = frame['index']
163
+ begin
164
+ original_frame_chain = driver.frame_chain
165
+ frame_element = driver.find_element(:css, selector) #
166
+ frame_src = frame_element.attribute('src')
167
+ Applitools::EyesLogger.info "snapshot_and_process_cors_frames src = #{frame_src}"
168
+ driver.switch_to.frame(frame_element)
169
+
170
+ frame_dom = create_cross_frames_dom_snapshots
171
+ dom['frames'] ||= []
172
+ dom['frames'].push frame_dom
173
+ frame_url = frame_dom['url']
174
+ dom['cdt'][frame_index]['attributes'].push({ 'name' => 'data-applitools-src', 'value' => frame_url })
175
+ Applitools::EyesLogger.info "Created cross origin frame snapshot #{frame_url}"
176
+ process_dom_snapshot_frames frame_dom
177
+
178
+ driver.switch_to.default_content
179
+ unless original_frame_chain.empty?
180
+ driver.switch_to.frames(frame_chain: original_frame_chain)
181
+ end
182
+ rescue StandardError => e
183
+ Applitools::EyesLogger.error e.class.to_s
184
+ Applitools::EyesLogger.error e.message
185
+ if should_skip_failed_frames
186
+ Applitools::EyesLogger.warn "failed extracting and processing cross frame #{selector}"
187
+ else
188
+ raise ::Applitools::EyesError.new 'failed switching to frame'
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ end
195
+ end
196
+ end
197
+ end
@@ -36,6 +36,10 @@ module Applitools
36
36
  ios_device_info.device_name
37
37
  end
38
38
 
39
+ def to_hash
40
+ {iosDeviceInfo: ios_device_info.to_hash}
41
+ end
42
+
39
43
  private
40
44
 
41
45
  class EmulationInfo < EmulationBaseInfo
@@ -49,6 +53,14 @@ module Applitools
49
53
  version: 'latest'
50
54
  }
51
55
  end
56
+
57
+ def to_hash
58
+ {
59
+ deviceName: device_name,
60
+ screenOrientation: screen_orientation,
61
+ iosVersion: 'latest'
62
+ }
63
+ end
52
64
  end
53
65
  end
54
66
  end
@@ -1,5 +1,8 @@
1
1
  module IosDeviceName
2
2
  extend self
3
+ IPhone_13_Pro_Max = 'iPhone 13 Pro Max'
4
+ IPhone_13_Pro = 'iPhone 13 Pro'
5
+ IPhone_13 = 'iPhone 13'
3
6
  IPhone_12_Pro_Max = 'iPhone 12 Pro Max'
4
7
  IPhone_12_Pro = 'iPhone 12 Pro'
5
8
  IPhone_12 = 'iPhone 12'
@@ -14,10 +17,14 @@ module IosDeviceName
14
17
  IPhone_7 = 'iPhone 7'
15
18
  IPad_Pro_3 = 'iPad Pro (12.9-inch) (3rd generation)'
16
19
  IPad_7 = 'iPad (7th generation)'
20
+ IPad_9 = 'iPad (9th generation)'
17
21
  IPad_Air_2 = 'iPad Air (2nd generation)'
18
22
 
19
23
  def enum_values
20
24
  [
25
+ IPhone_13_Pro_Max,
26
+ IPhone_13_Pro,
27
+ IPhone_13,
21
28
  IPhone_12_Pro_Max,
22
29
  IPhone_12_Pro,
23
30
  IPhone_12,
@@ -32,6 +39,7 @@ module IosDeviceName
32
39
  IPhone_7,
33
40
  IPad_Pro_3,
34
41
  IPad_7,
42
+ IPad_9,
35
43
  IPad_Air_2
36
44
  ]
37
45
  end
@@ -188,12 +188,13 @@ module Applitools
188
188
  end
189
189
 
190
190
  fetch_block = proc do |_s, key|
191
- resp_proc = proc { |u| server_connector.download_resource(u, ua_string) }
191
+ resp_proc = proc { |u, cookies| server_connector.download_resource(u, ua_string, cookies) }
192
192
  retry_count = 3
193
+ matching_cookies = data['cookies'].to_a.select {|c| is_cookie_for_url(c, key)}
193
194
  response = nil
194
195
  loop do
195
196
  retry_count -= 1
196
- response = resp_proc.call(key.dup)
197
+ response = resp_proc.call(key.dup, matching_cookies)
197
198
  break unless response.status != 200 && retry_count > 0
198
199
  end
199
200
  Applitools::Selenium::VGResource.parse_response(
@@ -287,6 +288,24 @@ module Applitools
287
288
  running_tests << running_test
288
289
  running_tests.length - 1
289
290
  end
291
+
292
+ def is_cookie_for_url(cookie, url)
293
+ return false if cookie[:secure] && url.scheme != 'https'
294
+ return false if cookie[:expires] && DateTime.now > cookie[:expires]
295
+
296
+ subdomains_allowed = cookie[:domain].start_with? '.'
297
+ domain = subdomains_allowed ? cookie[:domain][1..-1] : cookie[:domain]
298
+ domain_match = url.hostname === domain
299
+ subdomain_match = url.hostname.end_with?('.' + domain)
300
+ return false unless domain_match || (subdomains_allowed && subdomain_match)
301
+
302
+ path = cookie[:path]
303
+ path = path.end_with?('/') ? path[0..-2] : path
304
+
305
+ return true if url.path === path
306
+ return true if url.path.start_with?(path + '/')
307
+ false
308
+ end
290
309
  end
291
310
  end
292
311
  end
@@ -58,7 +58,8 @@ module Applitools
58
58
  end
59
59
 
60
60
  def init_or_renew_threads
61
- (concurrency - @thread_group.list.count).times do
61
+ one_concurrency = 1 # Thread's moved to universal server
62
+ (one_concurrency - @thread_group.list.count).times do
62
63
  logger.debug 'starting new thread (task worker)'
63
64
  next_thread
64
65
  end
@@ -7,8 +7,14 @@ require 'securerandom'
7
7
  module Applitools
8
8
  module Selenium
9
9
  class VisualGridEyes
10
+ # new open, with eyes-manager
11
+ include Applitools::UniversalEyesOpen
12
+ # all checks here
13
+ include Applitools::UniversalEyesChecks
14
+ # add extract_text, extract_text_regions, locate
15
+ include Applitools::UniversalNewApi
16
+
10
17
  include Applitools::Selenium::Concerns::SeleniumEyes
11
- DOM_EXTRACTION_TIMEOUT = 300 # seconds or 5 minutes
12
18
  USE_DEFAULT_MATCH_TIMEOUT = -1
13
19
  extend Forwardable
14
20
 
@@ -27,6 +33,10 @@ module Applitools
27
33
  def_delegators 'config', *Applitools::Selenium::Configuration.methods_to_delegate
28
34
  def_delegators 'config', *Applitools::EyesBaseConfiguration.methods_to_delegate
29
35
 
36
+ alias runner visual_grid_manager
37
+ attr_accessor :universal_eyes, :universal_driver
38
+ attr_accessor :debug_screenshots, :save_failed_tests, :scale_ratio, :disabled, :stitching_overlap, :compare_with_parent_branch
39
+
30
40
  def initialize(visual_grid_manager, server_url = nil)
31
41
  ensure_config
32
42
  @server_connector = Applitools::Connectivity::ServerConnector.new(server_url)
@@ -44,6 +54,7 @@ module Applitools
44
54
 
45
55
  def ensure_config
46
56
  self.config = Applitools::Selenium::Configuration.new
57
+ self.send_dom = true
47
58
  end
48
59
 
49
60
  def full_agent_id
@@ -57,51 +68,60 @@ module Applitools
57
68
  end
58
69
 
59
70
  def open(*args)
60
- self.test_uuid = SecureRandom.uuid
71
+ # self.test_uuid = SecureRandom.uuid
61
72
  options = Applitools::Utils.extract_options!(args)
62
- Applitools::ArgumentGuard.hash(options, 'options', [:driver])
63
-
64
- config.app_name = options[:app_name] if options[:app_name]
65
- config.test_name = options[:test_name] if options[:test_name]
66
- config.agent_run_id = "#{config.test_name}--#{SecureRandom.hex(10)}"
67
-
68
- if config.viewport_size.nil? || config.viewport_size && config.viewport_size.empty?
69
- config.viewport_size = Applitools::RectangleSize.from_any_argument(options[:viewport_size]) if options[:viewport_size]
70
- end
71
-
72
- self.driver = Applitools::Selenium::SeleniumEyes.eyes_driver(options.delete(:driver), self)
73
- self.current_url = driver.current_url
74
-
75
- if viewport_size
76
- set_viewport_size(viewport_size)
77
- else
78
- self.viewport_size = get_viewport_size
79
- end
80
-
81
- visual_grid_manager.open(self)
82
- visual_grid_manager.add_batch(batch.id) do
83
- server_connector.close_batch(batch.id)
84
- end
85
-
86
- logger.info('Getting all browsers info...')
87
- browsers_info_list = config.browsers_info
88
- logger.info('Creating test descriptors for each browser info...')
89
- browsers_info_list.each(viewport_size) do |bi|
90
- test = Applitools::Selenium::RunningTest.new(eyes_connector, bi, driver).tap do |t|
91
- t.on_results_received do |results|
92
- visual_grid_manager.aggregate_result(results)
93
- end
94
- t.test_uuid = test_uuid
95
- end
96
- test_list.push test
97
- end
98
- self.opened = true
99
- driver
73
+ universal_open(options)
74
+ # Applitools::ArgumentGuard.hash(options, 'options', [:driver])
75
+ #
76
+ # config.app_name = options[:app_name] if options[:app_name]
77
+ # config.test_name = options[:test_name] if options[:test_name]
78
+ # config.agent_run_id = "#{config.test_name}--#{SecureRandom.hex(10)}"
79
+ #
80
+ # if config.viewport_size.nil? || config.viewport_size && config.viewport_size.empty?
81
+ # config.viewport_size = Applitools::RectangleSize.from_any_argument(options[:viewport_size]) if options[:viewport_size]
82
+ # end
83
+ #
84
+ # self.driver = Applitools::Selenium::SeleniumEyes.eyes_driver(options.delete(:driver), self)
85
+ # self.current_url = driver.current_url
86
+ #
87
+ # if viewport_size
88
+ # set_viewport_size(viewport_size)
89
+ # else
90
+ # self.viewport_size = get_viewport_size
91
+ # end
92
+ #
93
+ # visual_grid_manager.open(self)
94
+ # visual_grid_manager.add_batch(batch.id) do
95
+ # server_connector.close_batch(batch.id)
96
+ # end
97
+ #
98
+ # logger.info('Getting all browsers info...')
99
+ # browsers_info_list = config.browsers_info
100
+ # logger.info('Creating test descriptors for each browser info...')
101
+ # browsers_info_list.each(viewport_size) do |bi|
102
+ # test = Applitools::Selenium::RunningTest.new(eyes_connector, bi, driver).tap do |t|
103
+ # t.on_results_received do |results|
104
+ # visual_grid_manager.aggregate_result(results)
105
+ # end
106
+ # t.test_uuid = test_uuid
107
+ # end
108
+ # test_list.push test
109
+ # end
110
+ # self.opened = true
111
+ # driver
100
112
  end
101
113
 
102
114
  def get_viewport_size(web_driver = driver)
103
115
  Applitools::ArgumentGuard.not_nil 'web_driver', web_driver
104
- self.utils.extract_viewport_size(driver)
116
+ # self.utils.extract_viewport_size(driver)
117
+ driver_config_json = universal_driver_config(web_driver)
118
+
119
+ Applitools::EyesLogger.debug 'extract_viewport_size()'
120
+ viewport_size = runner.universal_client.core_get_viewport_size(driver_config_json)
121
+ result = Applitools::RectangleSize.new viewport_size[:width], viewport_size[:height]
122
+
123
+ Applitools::EyesLogger.debug "Viewport size is #{result}."
124
+ result
105
125
  end
106
126
 
107
127
  def eyes_connector
@@ -125,42 +145,43 @@ module Applitools
125
145
  tag = first_arg[:name] || first_arg[:tag]
126
146
  end
127
147
 
128
- script = <<-END
129
- #{Applitools::Selenium::Scripts::PROCESS_PAGE_AND_POLL} return __processPageAndSerializePoll(document, {skipResources: [#{visual_grid_manager.resource_cache.urls_to_skip}]});
130
- END
131
148
  render_task = nil
132
149
  target.default_full_page_for_vg
133
150
 
151
+ return universal_check(tag, target)
152
+
134
153
  target_to_check = target.finalize
135
154
  begin
136
155
  check_in_frame(target_frames: target_to_check.frames) do
137
156
  sleep wait_before_screenshots
138
157
  Applitools::EyesLogger.info 'Trying to get DOM snapshot...'
139
-
140
- script_thread = Thread.new do
141
- result = {}
142
- while result['status'] != 'SUCCESS'
143
- Thread.current[:script_result] = driver.execute_script(script)
144
- begin
145
- Thread.current[:result] = result = Oj.load(Thread.current[:script_result])
146
- sleep 0.5
147
- rescue Oj::ParseError => e
148
- Applitools::EyesLogger.warn e.message
149
- end
150
- end
158
+ begin
159
+ dont_fetch_resources = self.dont_fetch_resources
160
+ enable_cross_origin_rendering = self.enable_cross_origin_rendering
161
+ use_cookies = !self.dont_use_cookies
162
+ urls_to_skip = visual_grid_manager.resource_cache.urls_to_skip
163
+ dom_script = Applitools::Selenium::DomSnapshotScript.new driver
164
+
165
+ script_dom = dom_script.create_dom_snapshot(
166
+ dont_fetch_resources,
167
+ urls_to_skip,
168
+ enable_cross_origin_rendering,
169
+ use_cookies
170
+ )
171
+ rescue StandardError => e
172
+ Applitools::EyesLogger.error e.class.to_s
173
+ Applitools::EyesLogger.error e.message
174
+ raise ::Applitools::EyesError.new 'Error while getting dom snapshot!'
151
175
  end
152
- sleep 0.5
153
- script_thread_result = script_thread.join(DOM_EXTRACTION_TIMEOUT)
154
- raise ::Applitools::EyesError.new 'Timeout error while getting dom snapshot!' unless script_thread_result
155
176
  Applitools::EyesLogger.info 'Done!'
156
177
 
157
- mod = Digest::SHA2.hexdigest(script_thread_result[:script_result])
178
+ mod = Digest::SHA2.hexdigest(script_dom.to_s)
158
179
 
159
180
  region_x_paths = get_regions_x_paths(target_to_check)
160
181
 
161
182
  render_task = RenderTask.new(
162
183
  "Render #{config.short_description} - #{tag}",
163
- script_thread_result[:result]['value'],
184
+ script_dom,
164
185
  visual_grid_manager,
165
186
  server_connector,
166
187
  region_x_paths,
@@ -298,21 +319,33 @@ module Applitools
298
319
  end
299
320
 
300
321
  def close(throw_exception = true)
301
- return false if test_list.empty?
302
- close_async
303
-
304
- until (states = test_list.map(&:state_name).uniq).count == 1 && states.first == :completed
305
- sleep 0.5
306
- end
307
- self.opened = false
308
-
309
- test_list.select { |t| t.pending_exceptions && !t.pending_exceptions.empty? }.each do |t|
310
- t.pending_exceptions.each do |e|
311
- raise e
312
- end
313
- end
314
-
315
- all_results = test_list.map(&:test_result).compact
322
+ logger.info "close(#{throw_exception})"
323
+ logger.info 'Ending server session...'
324
+
325
+ universal_results = universal_eyes.close # Array even for one test
326
+ raise Applitools::EyesError.new("Request failed: #{universal_results[:message]}") if server_error?(universal_results)
327
+ key_transformed_results = Applitools::Utils.deep_stringify_keys(universal_results)
328
+ results = key_transformed_results.map {|result| Applitools::TestResults.new(result) }
329
+ # results = results.first if results.size == 1
330
+ # session_results_url = results.url
331
+ all_results = results.compact
332
+
333
+
334
+ # return false if test_list.empty?
335
+ # close_async
336
+ #
337
+ # until (states = test_list.map(&:state_name).uniq).count == 1 && states.first == :completed
338
+ # sleep 0.5
339
+ # end
340
+ # self.opened = false
341
+ #
342
+ # test_list.select { |t| t.pending_exceptions && !t.pending_exceptions.empty? }.each do |t|
343
+ # t.pending_exceptions.each do |e|
344
+ # raise e
345
+ # end
346
+ # end
347
+ #
348
+ # all_results = test_list.map(&:test_result).compact
316
349
  failed_results = all_results.select { |r| !r.as_expected? }
317
350
 
318
351
  if throw_exception
@@ -323,16 +356,18 @@ module Applitools
323
356
  end
324
357
  end
325
358
 
326
- failed_results.empty? ? all_results.first : failed_results
359
+ failed_results.empty? ? all_results.first : failed_results.first
327
360
  end
328
361
 
329
362
  def abort_async
330
363
  test_list.each(&:abort)
364
+ universal_sdk_abort
331
365
  end
332
366
 
333
367
  def abort_if_not_closed
334
368
  self.opened = false
335
369
  test_list.each(&:abort)
370
+ universal_sdk_abort
336
371
  end
337
372
 
338
373
  alias abort abort_if_not_closed
@@ -349,6 +384,8 @@ module Applitools
349
384
 
350
385
  # rubocop:disable Style/AccessorMethodName
351
386
  def set_viewport_size(value)
387
+ # require('pry')
388
+ # binding.pry
352
389
  self.utils.set_viewport_size driver, value
353
390
  rescue => e
354
391
  logger.error e.class.to_s
@@ -400,6 +437,14 @@ module Applitools
400
437
 
401
438
  def add_text_trigger(_control, _text); end
402
439
 
440
+ def disabled=(value)
441
+ @disabled = Applitools::Utils.boolean_value value
442
+ end
443
+
444
+ def disabled?
445
+ @disabled
446
+ end
447
+
403
448
  private :new_test_error_message, :diffs_found_error_message, :test_failed_error_message
404
449
 
405
450
  private
@@ -61,6 +61,10 @@ module Applitools
61
61
  super
62
62
  end
63
63
 
64
+ def universal_eyes_manager_config
65
+ Applitools::UniversalEyesManagerConfig.vg(@thread_pool.concurrency)
66
+ end
67
+
64
68
  private
65
69
 
66
70
  def all_running_tests
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: false
2
2
 
3
3
  module Applitools
4
- VERSION = '3.18.2'.freeze
4
+ VERSION = '4.0.2'.freeze
5
+ UNIVERSAL_VERSION = '1.0.9'.freeze
5
6
  end
data/lib/eyes_selenium.rb CHANGED
@@ -31,5 +31,14 @@ if defined? Selenium::WebDriver::Driver
31
31
  is_mobile_device = capabilities['platformName'] ? true : false
32
32
  Applitools::Selenium::Driver.new(eyes, driver: self, is_mobile_device: is_mobile_device)
33
33
  end
34
+
35
+ def universal_driver_config
36
+ hidden_server_url = bridge.http.send(:server_url).to_s
37
+ {
38
+ serverUrl: hidden_server_url,
39
+ sessionId: session_id,
40
+ capabilities: capabilities.as_json
41
+ }
42
+ end
34
43
  end
35
44
  end