eyes_selenium 3.14.10 → 3.15.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/lib/applitools/selenium/concerns/browser_types.rb +15 -0
  3. data/lib/applitools/selenium/concerns/browsers_info.rb +16 -0
  4. data/lib/applitools/selenium/concerns/external_css_resources.rb +31 -0
  5. data/lib/applitools/selenium/concerns/render_browser_info_fluent.rb +42 -0
  6. data/lib/applitools/selenium/concerns/render_resources.rb +15 -0
  7. data/lib/applitools/selenium/concerns/rgrid_dom.rb +46 -0
  8. data/lib/applitools/selenium/concerns/stitch_modes.rb +15 -0
  9. data/lib/applitools/selenium/concerns/test_list.rb +23 -0
  10. data/lib/applitools/selenium/eyes.rb +13 -854
  11. data/lib/applitools/selenium/scripts/process_page_and_serialize.rb +519 -0
  12. data/lib/applitools/selenium/selenium_configuration.rb +64 -0
  13. data/lib/applitools/selenium/selenium_eyes.rb +860 -0
  14. data/lib/applitools/selenium/target.rb +2 -2
  15. data/lib/applitools/selenium/visual_grid/eyes_connector.rb +170 -0
  16. data/lib/applitools/selenium/visual_grid/render_browser_info.rb +31 -0
  17. data/lib/applitools/selenium/visual_grid/render_info.rb +9 -0
  18. data/lib/applitools/selenium/visual_grid/render_request.rb +22 -0
  19. data/lib/applitools/selenium/visual_grid/render_requests.rb +11 -0
  20. data/lib/applitools/selenium/visual_grid/render_task.rb +171 -0
  21. data/lib/applitools/selenium/visual_grid/resource_cache.rb +51 -0
  22. data/lib/applitools/selenium/visual_grid/running_test.rb +198 -0
  23. data/lib/applitools/selenium/visual_grid/thread_pool.rb +94 -0
  24. data/lib/applitools/selenium/visual_grid/vg_resource.rb +37 -0
  25. data/lib/applitools/selenium/visual_grid/vg_task.rb +46 -0
  26. data/lib/applitools/selenium/visual_grid/visual_grid_eyes.rb +236 -0
  27. data/lib/applitools/selenium/visual_grid/visual_grid_runner.rb +57 -0
  28. data/lib/applitools/version.rb +1 -1
  29. data/lib/eyes_selenium.rb +3 -0
  30. metadata +46 -7
@@ -0,0 +1,198 @@
1
+ require 'state_machine'
2
+ require 'digest'
3
+
4
+ module Applitools
5
+ module Selenium
6
+ class RunningTest
7
+ extend Forwardable
8
+ def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
9
+
10
+ state_machine :initial => :new do
11
+ state :new do
12
+ def close
13
+ becomes_completed
14
+ end
15
+
16
+ def score
17
+ 0
18
+ end
19
+
20
+ def queue
21
+ Applitools::Selenium::VisualGridRunner::EMPTY_QUEUE
22
+ end
23
+ end
24
+
25
+ state :not_rendered do
26
+ def score
27
+ render_queue.length * 10
28
+ end
29
+
30
+ def queue
31
+ render_queue
32
+ end
33
+ end
34
+
35
+ state :opened do
36
+ def score
37
+ task_queue.length
38
+ end
39
+
40
+ def queue
41
+ return Applitools::Selenium::VisualGridRunner::EMPTY_QUEUE unless task_lock.nil?
42
+ self.task_lock = task_queue.last unless task_queue.last.nil?
43
+ task_queue
44
+ end
45
+ end
46
+
47
+ state :rendered do
48
+ def score
49
+ open_queue.length
50
+ end
51
+
52
+ def queue
53
+ open_queue
54
+ end
55
+ end
56
+
57
+ state :tested do
58
+ def score
59
+ close_queue.length
60
+ end
61
+
62
+ def queue
63
+ close_queue
64
+ end
65
+ end
66
+
67
+ state :completed do
68
+ def score
69
+ 0
70
+ end
71
+
72
+ def queue
73
+ Applitools::Selenium::VisualGridRunner::EMPTY_QUEUE
74
+ end
75
+ end
76
+
77
+ state :new, :not_rendered, :opened, :rendered, :tested do
78
+ def close
79
+ self.test_result = nil
80
+ close_task = Applitools::Selenium::VGTask.new("close #{browser_info}") do
81
+ eyes.close(false)
82
+ end
83
+ close_task.on_task_succeeded do |task_result|
84
+ self.test_result = task_result
85
+ end.on_task_error do |e|
86
+ self.pending_exceptions << e
87
+ end.on_task_completed do
88
+ watch_close[close_task] = true
89
+ becomes_completed if all_tasks_completed?(watch_close)
90
+ end
91
+ close_queue << close_task
92
+ watch_close[close_task] = false
93
+ end
94
+ end
95
+
96
+ event :becomes_not_rendered do
97
+ transition :new => :not_rendered
98
+ end
99
+
100
+ event :becomes_opened do
101
+ transition :rendered => :opened
102
+ end
103
+
104
+ event :becomes_rendered do
105
+ transition :not_rendered => :rendered
106
+ end
107
+
108
+ event :becomes_tested do
109
+ transition :opened => :tested
110
+ end
111
+
112
+ event :becomes_completed do
113
+ transition [:not_rendered, :rendered, :opened, :tested] => :completed
114
+ end
115
+ end
116
+
117
+ attr_accessor :open_queue, :task_queue, :render_queue, :close_queue, :watch_open, :watch_task, :watch_render, :watch_close
118
+
119
+ attr_accessor :eyes, :browser_info, :test_result, :pending_exceptions, :driver, :task_lock
120
+
121
+ def initialize(eyes, browser_info, driver)
122
+ Applitools::ArgumentGuard.is_a? eyes, 'eyes', Applitools::Selenium::EyesConnector
123
+ Applitools::ArgumentGuard.is_a? browser_info, 'browser_info', Applitools::Selenium::RenderBrowserInfo
124
+
125
+ self.eyes = eyes
126
+ self.browser_info = browser_info
127
+ self.driver = driver
128
+
129
+ self.open_queue = []
130
+ self.task_queue = []
131
+ self.render_queue = []
132
+ self.close_queue = []
133
+
134
+ self.watch_open = {}
135
+ self.watch_task = {}
136
+ self.watch_render = {}
137
+ self.watch_close = {}
138
+
139
+ self.task_lock = nil
140
+
141
+ self.pending_exceptions = []
142
+ super()
143
+ init
144
+ end
145
+
146
+ def init
147
+ open_task = Applitools::Selenium::VGTask.new("open #{browser_info}") { eyes.open(driver, browser_info) }
148
+
149
+ open_task.on_task_succeeded { watch_open[open_task] = true; becomes_opened if all_tasks_completed?(watch_open) }.
150
+ on_task_error { |e| pending_exceptions << e; becomes_completed }
151
+ open_queue << open_task
152
+ watch_open[open_task] = false
153
+ end
154
+
155
+ def check(tag, target, script_result, visual_grid_manager, mod = nil)
156
+ render_task = RenderTask.new(
157
+ "Render #{eyes.config.short_description} - #{tag}",
158
+ script_result,
159
+ self,
160
+ visual_grid_manager.resource_cache,
161
+ visual_grid_manager.put_cache,
162
+ visual_grid_manager.rendering_info(eyes.server_connector),
163
+ eyes.server_connector,
164
+ mod
165
+ )
166
+
167
+ check_task = VGTask.new("perform check #{tag} #{target}") do
168
+ eyes.check(tag, target, render_task.uuid)
169
+ end
170
+
171
+ check_task.on_task_completed do
172
+ watch_task[check_task] = true
173
+ self.task_lock = nil if task_lock.uuid == check_task.uuid
174
+ becomes_tested if all_tasks_completed?(watch_task)
175
+ end
176
+
177
+ task_queue.insert(0, check_task)
178
+ watch_task[check_task] = false
179
+
180
+ render_task.on_task_succeeded do |r|
181
+ eyes.render_status_for_task(render_task.uuid, r) if r
182
+ watch_render[render_task] = true
183
+ becomes_rendered if all_tasks_completed?(watch_render)
184
+ end.on_task_error do
185
+ becomes_completed
186
+ end
187
+ render_queue << render_task
188
+ watch_render[render_task] = false
189
+ end
190
+
191
+ def all_tasks_completed?(watch)
192
+ return true if state_name == :completed
193
+ uniq_values = watch.values.uniq
194
+ uniq_values.count == 1 && uniq_values.first == true
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,94 @@
1
+ require 'thread'
2
+
3
+ module Applitools
4
+ module Selenium
5
+ class VGThreadPool
6
+ extend Forwardable
7
+ attr_accessor :concurrency
8
+ def_delegator 'Applitools::EyesLogger', :logger
9
+
10
+ def initialize(concurrency = 10)
11
+ self.concurrency = concurrency
12
+ @stopped = true
13
+ @thread_group = ThreadGroup.new
14
+ @semaphore = Mutex.new
15
+ @next_task_block = nil
16
+ @watchdog = nil
17
+ end
18
+
19
+ def on_next_task_needed(&block)
20
+ @next_task_block = block if block_given?
21
+ end
22
+
23
+ def start
24
+ @semaphore.synchronize { @stopped = false }
25
+ init_or_renew_threads
26
+ @watchdog = Thread.new do
27
+ catch(:exit) do
28
+ loop do
29
+ throw :exit if stopped?
30
+ sleep 5
31
+ init_or_renew_threads
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def stop
38
+ @watchdog.exit
39
+ @semaphore.synchronize do
40
+ @stopped = true
41
+ end
42
+ @thread_group.list.each do |t|
43
+ t.join
44
+ end
45
+ stopped?
46
+ end
47
+
48
+ def stopped?
49
+ @semaphore.synchronize { @stopped }
50
+ end
51
+
52
+ private
53
+
54
+ def next_task
55
+ @semaphore.synchronize do
56
+ return @next_task_block.call if @next_task_block && @next_task_block.respond_to?(:call)
57
+ end
58
+ nil
59
+ end
60
+
61
+ def init_or_renew_threads
62
+ (concurrency - @thread_group.list.count).times do
63
+ logger.debug 'starting new thread (task worker)'
64
+ next_thread
65
+ end
66
+ end
67
+
68
+ def next_thread
69
+ thread = Thread.new do
70
+ begin
71
+ catch(:exit) do
72
+ loop do
73
+ throw :exit if stopped?
74
+ task_to_run = next_task
75
+ if task_to_run && task_to_run.respond_to?(:call)
76
+ logger.debug "Executing new task... #{task_to_run.name}"
77
+ task_to_run.call
78
+ logger.debug 'Done!'
79
+ else
80
+ sleep 0.5
81
+ end
82
+ end
83
+ end
84
+ rescue => e
85
+ Applitools::EyesLogger.logger.error "Failed to execute task - #{task_to_run.name}"
86
+ Applitools::EyesLogger.logger.error e.message
87
+ Applitools::EyesLogger.logger.error e.backtrace.join( ' ')
88
+ end
89
+ end
90
+ @thread_group.add thread
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,37 @@
1
+ require 'base64'
2
+ require 'digest'
3
+ module Applitools
4
+ module Selenium
5
+ class VGResource
6
+ include Applitools::Jsonable
7
+ json_fields :contentType, :hash, :hashFormat
8
+ attr_accessor :url, :content
9
+ alias :content_type :contentType
10
+ alias :content_type= :contentType=
11
+
12
+ class << self
13
+ def parse_blob_from_script(blob)
14
+ content = Base64.decode64(blob["value"])
15
+ self.new blob["url"], blob["type"], content
16
+ end
17
+
18
+ def parse_response(url, response)
19
+ return self.new(url, 'application/empty-response', '') unless response.status == 200
20
+ self.new(url, response.headers['Content-Type'], response.body)
21
+ end
22
+ end
23
+
24
+ def initialize(url, content_type, content)
25
+ self.url = URI(url)
26
+ self.content_type = content_type
27
+ self.content = content
28
+ self.hash = Digest::SHA256.hexdigest(content)
29
+ self.hashFormat = 'sha256'
30
+ end
31
+
32
+ def stringify
33
+ url.to_s + content_type.to_s + hash
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ require 'securerandom'
2
+ module Applitools
3
+ module Selenium
4
+ class VGTask
5
+ attr_accessor :name, :uuid
6
+ def initialize(name, &block)
7
+ self.name = name
8
+ @block_to_run = block if block_given?
9
+ @callback = nil
10
+ @error_callback = nil
11
+ @completed_callback = nil
12
+ self.uuid = SecureRandom.uuid
13
+ end
14
+
15
+ def on_task_succeeded(&block)
16
+ @callback = block if block_given?
17
+ self
18
+ end
19
+
20
+ def on_task_error(&block)
21
+ @error_callback = block if block_given?
22
+ self
23
+ end
24
+
25
+ def on_task_completed(&block)
26
+ @completed_callback = block if block_given?
27
+ self
28
+ end
29
+
30
+ def call
31
+ return unless @block_to_run.respond_to? :call
32
+ begin
33
+ res = @block_to_run.call
34
+ @callback.call(res) if @callback.respond_to? :call
35
+ rescue StandardError => e
36
+ Applitools::EyesLogger.logger.error 'Failed to execute task!'
37
+ Applitools::EyesLogger.logger.error e.message
38
+ Applitools::EyesLogger.logger.error e.backtrace.join('\n\t')
39
+ @error_callback.call(e) if @error_callback.respond_to? :call
40
+ ensure
41
+ @completed_callback.call if @completed_callback.respond_to? :call
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,236 @@
1
+ require 'applitools/selenium/selenium_configuration'
2
+ module Applitools
3
+ module Selenium
4
+ class VisualGridEyes
5
+ extend Forwardable
6
+
7
+ def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
8
+
9
+ attr_accessor :visual_grid_manager, :driver, :current_url, :current_config, :fetched_cache_map, :config
10
+ attr_accessor :test_list
11
+
12
+ attr_accessor :api_key, :server_url, :proxy, :opened
13
+
14
+ def_delegators 'config', *Applitools::Selenium::SeleniumConfiguration.methods_to_delegate
15
+ def_delegators 'config', *Applitools::EyesBaseConfiguration.methods_to_delegate
16
+
17
+ def initialize(visual_grid_manager, server_url = nil)
18
+ ensure_config
19
+ self.visual_grid_manager = visual_grid_manager
20
+ self.test_list = Applitools::Selenium::TestList.new
21
+ self.opened = false
22
+ end
23
+
24
+ def ensure_config
25
+ self.config = Applitools::Selenium::SeleniumConfiguration.new
26
+ end
27
+
28
+
29
+ def open(*args)
30
+ self.test_list = Applitools::Selenium::TestList.new
31
+ options = Applitools::Utils.extract_options!(args)
32
+ Applitools::ArgumentGuard.hash(options, 'options', [:driver])
33
+
34
+ # self.current_config = options.delete(:config)
35
+ # self.current_config = yield(Applitools::Selenium::SeleniumConfiguration.new) if block_given?
36
+
37
+ # Applitools::ArgumentGuard.is_a? options[:driver], 'options[:driver]', ::Selenium::WebDriver
38
+ # Applitools::ArgumentGuard.is_a? current_config, 'options[:config]', Applitools::Selenium::SeleniumConfiguration
39
+
40
+ # batch_info.name = config.app_name
41
+ self.driver = options.delete(:driver)
42
+ self.current_url = driver.current_url
43
+
44
+ visual_grid_manager.open(self)
45
+
46
+ logger.info("getting all browsers info...")
47
+ browsers_info_list = config.browsers_info
48
+ logger.info("creating test descriptors for each browser info...")
49
+ browsers_info_list.each do |bi|
50
+ test_list.push Applitools::Selenium::RunningTest.new(eyes_connector, bi, driver)
51
+ end
52
+ self.opened = true
53
+ driver
54
+ end
55
+
56
+ def eyes_connector
57
+ logger.info("creating VisualGridEyes server connector")
58
+ ::Applitools::Selenium::EyesConnector.new(server_url).tap do |connector|
59
+ connector.batch = batch_info
60
+ connector.config = config.deep_clone
61
+ connector.proxy = proxy if proxy.is_a? Applitools::Connectivity::Proxy
62
+ end
63
+ end
64
+
65
+ def check(tag, target)
66
+ script = <<-END
67
+ var callback = arguments[arguments.length - 1]; return (#{Applitools::Selenium::Scripts::PROCESS_RESOURCES})().then(JSON.stringify).then(callback, function(err) {callback(err.stack || err.toString())});
68
+ END
69
+
70
+ script_result = driver.execute_async_script(script).freeze
71
+ mod = Digest::SHA2.hexdigest(script_result)
72
+ test_list.each do |test|
73
+ test.check(tag, target, script_result.dup, visual_grid_manager, mod)
74
+ end
75
+ test_list.each { |t| t.becomes_not_rendered}
76
+ end
77
+
78
+ def close(throw_exception = true)
79
+ return false if test_list.empty?
80
+ test_list.each do |t|
81
+ t.close
82
+ end
83
+
84
+ while (!((states = test_list.map(&:state_name).uniq).count == 1 && states.first == :completed)) do
85
+ sleep 0.5
86
+ end
87
+ self.opened = false
88
+
89
+ test_list.select { |t| t.pending_exceptions && !t.pending_exceptions.empty? }.each do |t|
90
+ t.pending_exceptions.each do |e|
91
+ raise e
92
+ end
93
+ end
94
+
95
+ if throw_exception
96
+ test_list.map(&:test_result).compact.each do |r|
97
+ raise Applitools::NewTestError.new new_test_error_message(r), r if r.new?
98
+ raise Applitools::DiffsFoundError.new diffs_found_error_message(r), r if r.unresolved? && !r.new?
99
+ raise Applitools::TestFailedError.new test_failed_error_message(r), r if r.failed?
100
+ end
101
+ end
102
+ test_list.map(&:test_result).first
103
+ end
104
+
105
+ def open?
106
+ opened
107
+ end
108
+
109
+ def get_all_test_results
110
+ test_list.map(&:test_result)
111
+ end
112
+
113
+ def new_test_error_message(result)
114
+ original_results = result.original_results
115
+ "New test '#{original_results['name']}' " \
116
+ "of '#{original_results['appName']}' " \
117
+ "Please approve the baseline at #{original_results['appUrls']['session']} "
118
+ end
119
+
120
+ def diffs_found_error_message(result)
121
+ original_results = result.original_results
122
+ "Test '#{original_results['name']}' " \
123
+ "of '#{original_results['appname']}' " \
124
+ "detected differences! See details at #{original_results['appUrls']['session']}"
125
+ end
126
+
127
+ def test_failed_error_message(result)
128
+ original_results = result.original_results
129
+ "Test '#{original_results['name']}' of '#{original_results['appName']}' " \
130
+ "is failed! See details at #{original_results['appUrls']['session']}"
131
+ end
132
+ private :new_test_error_message, :diffs_found_error_message, :test_failed_error_message
133
+
134
+ # Takes a snapshot of the application under test and matches it with the expected output.
135
+ #
136
+ # @param [String] tag An optional tag to be assosiated with the snapshot.
137
+ # @param [Fixnum] match_timeout The amount of time to retry matching (seconds)
138
+ def check_window(tag = nil, match_timeout = USE_DEFAULT_MATCH_TIMEOUT)
139
+ target = Applitools::Selenium::Target.window.tap do |t|
140
+ t.timeout(match_timeout)
141
+ t.fully if force_full_page_screenshot
142
+ end
143
+ check(tag, target)
144
+ end
145
+
146
+ # Takes a snapshot of the application under test and matches a region of
147
+ # a specific element with the expected region output.
148
+ #
149
+ # @param [Applitools::Selenium::Element] element Represents a region to check.
150
+ # @param [Symbol] how a finder, such :css or :id. Selects a finder will be used to find an element
151
+ # See Selenium::Webdriver::Element#find_element documentation for full list of possible finders.
152
+ # @param [String] what The value will be passed to a specified finder. If finder is :css it must be a css selector.
153
+ # @param [Hash] options
154
+ # @option options [String] :tag An optional tag to be associated with the snapshot.
155
+ # @option options [Fixnum] :match_timeout The amount of time to retry matching. (Seconds)
156
+ # @option options [Boolean] :stitch_content If set to true, will try to get full content of the element
157
+ # (including hidden content due overflow settings) by scrolling the element,
158
+ # taking and stitching partial screenshots.
159
+ # @example Check region by element
160
+ # check_region(element, tag: 'Check a region by element', match_timeout: 3, stitch_content: false)
161
+ # @example Check region by css selector
162
+ # check_region(:css, '.form-row .input#e_mail', tag: 'Check a region by element', match_timeout: 3,
163
+ # stitch_content: false)
164
+ # @!parse def check_region(element, how=nil, what=nil, options = {}); end
165
+ def check_region(*args)
166
+ options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil }.merge! Applitools::Utils.extract_options!(args)
167
+ target = Applitools::Selenium::Target.new.region(*args).timeout(options[:match_timeout])
168
+ target.fully if options[:stitch_content]
169
+ check(options[:tag], target)
170
+ end
171
+
172
+ # Validates the contents of an iframe and matches it with the expected output.
173
+ #
174
+ # @param [Hash] options The specific parameters of the desired screenshot.
175
+ # @option options [Fixnum] :timeout The amount of time to retry matching. (Seconds)
176
+ # @option options [String] :tag An optional tag to be associated with the snapshot.
177
+ # @option options [String] :frame Frame element or frame name or frame id.
178
+ # @option options [String] :name_or_id The name or id of the target frame (deprecated. use :frame instead).
179
+ # @option options [String] :frame_element The frame element (deprecated. use :frame instead).
180
+ # @return [Applitools::MatchResult] The match results.
181
+
182
+ def check_frame(options = {})
183
+ options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil }.merge!(options)
184
+ frame = options[:frame] || options[:frame_element] || options[:name_or_id]
185
+ target = Applitools::Selenium::Target.frame(frame).timeout(options[:timeout]).fully
186
+ check(options[:tag], target)
187
+ end
188
+
189
+ # Validates the contents of a region in an iframe and matches it with the expected output.
190
+ #
191
+ # @param [Hash] options The specific parameters of the desired screenshot.
192
+ # @option options [String] :name_or_id The name or id of the target frame (deprecated. use :frame instead).
193
+ # @option options [String] :frame_element The frame element (deprecated. use :frame instead).
194
+ # @option options [String] :frame Frame element or frame name or frame id.
195
+ # @option options [String] :tag An optional tag to be associated with the snapshot.
196
+ # @option options [Symbol] :by By which identifier to find the region (e.g :css, :id).
197
+ # @option options [Fixnum] :timeout The amount of time to retry matching. (Seconds)
198
+ # @option options [Boolean] :stitch_content Whether to stitch the content or not.
199
+ # @return [Applitools::MatchResult] The match results.
200
+ def check_region_in_frame(options = {})
201
+ options = { timeout: USE_DEFAULT_MATCH_TIMEOUT, tag: nil, stitch_content: false }.merge!(options)
202
+ Applitools::ArgumentGuard.not_nil options[:by], 'options[:by]'
203
+ Applitools::ArgumentGuard.is_a? options[:by], 'options[:by]', Array
204
+
205
+ how_what = options.delete(:by)
206
+ frame = options[:frame] || options[:frame_element] || options[:name_or_id]
207
+
208
+ target = Applitools::Selenium::Target.new.timeout(options[:timeout])
209
+ target.frame(frame) if frame
210
+ target.fully if options[:stitch_content]
211
+ target.region(*how_what)
212
+
213
+ check(options[:tag], target)
214
+ end
215
+
216
+ # Use this method to perform seamless testing with selenium through eyes driver.
217
+ # It yields a block and passes to it an Applitools::Selenium::Driver instance, which wraps standard driver.
218
+ # Using Selenium methods inside the 'test' block will send the messages to Selenium
219
+ # after creating the Eyes triggers for them. Options are similar to {open}
220
+ # @yieldparam driver [Applitools::Selenium::Driver] Gives a driver to a block, which translates calls to a native
221
+ # Selemium::Driver instance
222
+ # @example
223
+ # eyes.test(app_name: 'my app', test_name: 'my test') do |driver|
224
+ # driver.get "http://www.google.com"
225
+ # driver.check_window("initial")
226
+ # end
227
+ def test(options = {}, &_block)
228
+ open(options)
229
+ yield(driver)
230
+ close
231
+ ensure
232
+ abort_if_not_closed
233
+ end
234
+ end
235
+ end
236
+ end