eyes_selenium 3.18.1 → 4.0.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/lib/applitools/selenium/browser_types.rb +10 -10
- data/lib/applitools/selenium/browsers_info.rb +8 -0
- data/lib/applitools/selenium/concerns/selenium_eyes.rb +2 -6
- data/lib/applitools/selenium/configuration.rb +1 -1
- data/lib/applitools/selenium/css_transform/css_transform.rb +2 -2
- data/lib/applitools/selenium/devices.rb +115 -24
- data/lib/applitools/selenium/driver.rb +20 -0
- data/lib/applitools/selenium/eyes.rb +2 -2
- data/lib/applitools/selenium/scripts/process_page_and_poll.rb +3 -4
- data/lib/applitools/selenium/scripts/templates.rb +1 -0
- data/lib/applitools/selenium/selenium_eyes.rb +53 -48
- data/lib/applitools/selenium/target.rb +125 -124
- data/lib/applitools/selenium/visual_grid/chrome_emulation_info.rb +4 -0
- data/lib/applitools/selenium/visual_grid/desktop_browser_info.rb +6 -0
- data/lib/applitools/selenium/visual_grid/dom_snapshot_script.rb +197 -0
- data/lib/applitools/selenium/visual_grid/eyes_connector.rb +3 -0
- data/lib/applitools/selenium/visual_grid/ios_device_info.rb +12 -0
- data/lib/applitools/selenium/visual_grid/ios_device_name.rb +8 -0
- data/lib/applitools/selenium/visual_grid/render_task.rb +21 -2
- data/lib/applitools/selenium/visual_grid/thread_pool.rb +2 -1
- data/lib/applitools/selenium/visual_grid/visual_grid_eyes.rb +122 -76
- data/lib/applitools/selenium/visual_grid/visual_grid_runner.rb +4 -0
- data/lib/applitools/version.rb +2 -1
- data/lib/eyes_selenium.rb +9 -0
- metadata +11 -10
@@ -4,6 +4,7 @@ module Applitools
|
|
4
4
|
extend self
|
5
5
|
def get_script_by_file_name(fname)
|
6
6
|
script = nil
|
7
|
+
return script unless Dir.exist?(Applitools::JS_PATH)
|
7
8
|
begin
|
8
9
|
Dir.chdir(Applitools::JS_PATH) do
|
9
10
|
script = File.open(File.join('@applitools', 'dom-snapshot', 'dist', "#{fname}.js"), 'r').read
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: false
|
2
2
|
|
3
|
+
|
3
4
|
module Applitools::Selenium
|
4
5
|
# The main API gateway for the SDK
|
5
6
|
class SeleniumEyes < Applitools::EyesBase
|
@@ -56,16 +57,7 @@ module Applitools::Selenium
|
|
56
57
|
# @param [Applitools::Selenium::Driver] driver The driver instance.
|
57
58
|
# @param [Hash] viewport_size The required browser's viewport size.
|
58
59
|
def set_viewport_size(driver, viewport_size)
|
59
|
-
|
60
|
-
Applitools::ArgumentGuard.not_nil(viewport_size, 'viewport_size')
|
61
|
-
Applitools::ArgumentGuard.is_a?(viewport_size, 'viewport_size', Applitools::RectangleSize)
|
62
|
-
begin
|
63
|
-
Applitools::Utils::EyesSeleniumUtils.set_viewport_size eyes_driver(driver), viewport_size
|
64
|
-
rescue => e
|
65
|
-
Applitools::EyesLogger.error e.class
|
66
|
-
Applitools::EyesLogger.error e.message
|
67
|
-
raise Applitools::EyesError.new 'Failed to set viewport size!'
|
68
|
-
end
|
60
|
+
super
|
69
61
|
end
|
70
62
|
end
|
71
63
|
|
@@ -100,7 +92,7 @@ module Applitools::Selenium
|
|
100
92
|
:full_page_capture_algorithm_left_top_offset, :screenshot_type, :send_dom, :use_dom, :enable_patterns,
|
101
93
|
:utils,
|
102
94
|
:config
|
103
|
-
|
95
|
+
attr_accessor :driver
|
104
96
|
|
105
97
|
def_delegators 'Applitools::EyesLogger', :logger, :log_handler, :log_handler=
|
106
98
|
def_delegators 'config', *Applitools::Selenium::Configuration.methods_to_delegate
|
@@ -116,7 +108,7 @@ module Applitools::Selenium
|
|
116
108
|
self.base_agent_id = "eyes.selenium.ruby/#{Applitools::VERSION}".freeze
|
117
109
|
self.check_frame_or_element = false
|
118
110
|
self.region_to_check = nil
|
119
|
-
self.force_full_page_screenshot = false
|
111
|
+
# self.force_full_page_screenshot = false
|
120
112
|
self.dont_get_title = false
|
121
113
|
self.device_pixel_ratio = UNKNOWN_DEVICE_PIXEL_RATIO
|
122
114
|
self.stitch_mode = Applitools::STITCH_MODE[:scroll]
|
@@ -162,41 +154,42 @@ module Applitools::Selenium
|
|
162
154
|
# @return [Applitools::Selenium::Driver] A wrapped web driver which enables Eyes
|
163
155
|
# trigger recording and frame handling
|
164
156
|
def open(options = {})
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# Applitools::ArgumentGuard.
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
self.
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
end
|
199
|
-
|
157
|
+
universal_open(options)
|
158
|
+
# original_driver = options.delete(:driver)
|
159
|
+
# options[:viewport_size] = Applitools::RectangleSize.from_any_argument options[:viewport_size] if
|
160
|
+
# options[:viewport_size]
|
161
|
+
# Applitools::ArgumentGuard.not_nil original_driver, 'options[:driver]'
|
162
|
+
# # Applitools::ArgumentGuard.hash options, 'open(options)', [:app_name, :test_name]
|
163
|
+
#
|
164
|
+
# if disabled?
|
165
|
+
# logger.info('Ignored')
|
166
|
+
# return driver
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# @driver = self.class.eyes_driver(original_driver, self)
|
170
|
+
# perform_driver_specific_settings(original_driver)
|
171
|
+
#
|
172
|
+
# self.device_pixel_ratio = UNKNOWN_DEVICE_PIXEL_RATIO
|
173
|
+
# self.position_provider = self.class.position_provider(
|
174
|
+
# stitch_mode, driver, disable_horizontal_scrolling, disable_vertical_scrolling, explicit_entire_size
|
175
|
+
# )
|
176
|
+
#
|
177
|
+
# self.eyes_screenshot_factory = lambda do |image|
|
178
|
+
# Applitools::Selenium::ViewportScreenshot.new(
|
179
|
+
# image, driver: @driver, force_offset: position_provider.force_offset
|
180
|
+
# )
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# open_base(options) do
|
184
|
+
# self.viewport_size = get_viewport_size if force_driver_resolution_as_viewport_size
|
185
|
+
# ensure_running_session
|
186
|
+
# end
|
187
|
+
# if runner
|
188
|
+
# runner.add_batch(batch.id) do
|
189
|
+
# server_connector.close_batch(batch.id)
|
190
|
+
# end
|
191
|
+
# end
|
192
|
+
# @driver
|
200
193
|
end
|
201
194
|
|
202
195
|
def perform_driver_specific_settings(original_driver)
|
@@ -219,7 +212,15 @@ module Applitools::Selenium
|
|
219
212
|
# @!visibility private
|
220
213
|
def get_viewport_size(web_driver = driver)
|
221
214
|
Applitools::ArgumentGuard.not_nil 'web_driver', web_driver
|
222
|
-
self.utils.extract_viewport_size(driver)
|
215
|
+
# self.utils.extract_viewport_size(driver)
|
216
|
+
driver_config_json = web_driver.universal_driver_config
|
217
|
+
|
218
|
+
Applitools::EyesLogger.debug 'extract_viewport_size()'
|
219
|
+
viewport_size = runner.universal_client.core_get_viewport_size(driver_config_json)
|
220
|
+
result = Applitools::RectangleSize.new viewport_size[:width], viewport_size[:height]
|
221
|
+
|
222
|
+
Applitools::EyesLogger.debug "Viewport size is #{result}."
|
223
|
+
result
|
223
224
|
end
|
224
225
|
|
225
226
|
# Takes a snapshot and matches it with the expected output.
|
@@ -242,6 +243,9 @@ module Applitools::Selenium
|
|
242
243
|
logger.info "check(#{name}) is called"
|
243
244
|
self.tag_for_debug = name
|
244
245
|
Applitools::ArgumentGuard.is_a? target, 'target', Applitools::Selenium::Target
|
246
|
+
|
247
|
+
return universal_check(name, target)
|
248
|
+
|
245
249
|
target_to_check = target.finalize
|
246
250
|
original_overflow = nil
|
247
251
|
original_position_provider = position_provider
|
@@ -263,6 +267,7 @@ module Applitools::Selenium
|
|
263
267
|
begin
|
264
268
|
match_data = Applitools::MatchWindowData.new(default_match_settings)
|
265
269
|
match_data.tag = name
|
270
|
+
match_data.variation_group_id = target_to_check.options[:variation_group_id] if target_to_check.options[:variation_group_id]
|
266
271
|
eyes_element = target_to_check.region_to_check.call(driver)
|
267
272
|
|
268
273
|
unless force_full_page_screenshot
|
@@ -49,49 +49,10 @@ module Applitools
|
|
49
49
|
if args.empty?
|
50
50
|
reset_ignore
|
51
51
|
else
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
ignored_regions << case args.first
|
58
|
-
when Applitools::Region
|
59
|
-
proc { args.first.padding(requested_padding) }
|
60
|
-
when ::Selenium::WebDriver::Element
|
61
|
-
proc do |driver, return_element = false|
|
62
|
-
region = applitools_element_from_selenium_element(driver, args.first)
|
63
|
-
padding_proc = proc do |reg|
|
64
|
-
Applitools::Region.from_location_size(
|
65
|
-
reg.location, reg.size
|
66
|
-
).padding(requested_padding)
|
67
|
-
end
|
68
|
-
next region, padding_proc if return_element
|
69
|
-
padding_proc.call(region)
|
70
|
-
end
|
71
|
-
when Applitools::Selenium::Element
|
72
|
-
proc do |_driver, return_element = false|
|
73
|
-
region = args.first
|
74
|
-
padding_proc = proc do |reg|
|
75
|
-
Applitools::Region.from_location_size(
|
76
|
-
reg.location, reg.size
|
77
|
-
).padding(requested_padding)
|
78
|
-
end
|
79
|
-
next region, padding_proc if return_element
|
80
|
-
padding_proc.call(region)
|
81
|
-
end
|
82
|
-
else
|
83
|
-
proc do |driver, return_element = false|
|
84
|
-
region = driver.find_element(*args)
|
85
|
-
padding_proc = proc do |reg|
|
86
|
-
Applitools::Region.from_location_size(
|
87
|
-
reg.location, reg.size
|
88
|
-
).padding(requested_padding)
|
89
|
-
end
|
90
|
-
next region, padding_proc if return_element
|
91
|
-
padding_proc.call(region)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
52
|
+
value = convert_to_universal(args)
|
53
|
+
value = { type: args[0], selector: args[1] } if value.nil?
|
54
|
+
value = value[:selector] if value.is_a?(Hash) && (value[:type].to_s === 'id')
|
55
|
+
ignored_regions << value
|
95
56
|
end
|
96
57
|
self
|
97
58
|
end
|
@@ -120,49 +81,11 @@ module Applitools
|
|
120
81
|
# @!parse def floating(region_or_element, bounds, left,top, right, bottom, padding); end;
|
121
82
|
|
122
83
|
def floating(*args)
|
123
|
-
requested_padding =
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
value = case args.first
|
129
|
-
when Applitools::FloatingRegion
|
130
|
-
args.first.padding(requested_padding)
|
131
|
-
when ::Applitools::Region
|
132
|
-
Applitools::FloatingRegion.any(args.shift, *args).padding(requested_padding)
|
133
|
-
when ::Selenium::WebDriver::Element
|
134
|
-
proc do |driver, return_element = false|
|
135
|
-
args_dup = args.dup
|
136
|
-
region = applitools_element_from_selenium_element(driver, args_dup.shift)
|
137
|
-
padding_proc = proc do |reg|
|
138
|
-
Applitools::FloatingRegion.any(reg, *args_dup).padding(requested_padding)
|
139
|
-
end
|
140
|
-
next region, padding_proc if return_element
|
141
|
-
padding_proc.call(region)
|
142
|
-
end
|
143
|
-
when ::Applitools::Selenium::Element
|
144
|
-
proc do |_driver, return_element = false|
|
145
|
-
args_dup = args.dup
|
146
|
-
region = args_dup.shift
|
147
|
-
padding_proc = proc do |reg|
|
148
|
-
Applitools::FloatingRegion.any(reg, *args_dup).padding(requested_padding)
|
149
|
-
end
|
150
|
-
next region, padding_proc if return_element
|
151
|
-
padding_proc.call(region)
|
152
|
-
end
|
153
|
-
else
|
154
|
-
proc do |driver, return_element = false|
|
155
|
-
args_dup = args.dup
|
156
|
-
region = driver.find_element(args_dup.shift, args_dup.shift)
|
157
|
-
padding_proc = proc do |reg|
|
158
|
-
Applitools::FloatingRegion.any(
|
159
|
-
reg, *args_dup
|
160
|
-
).padding(requested_padding)
|
161
|
-
end
|
162
|
-
next region, padding_proc if return_element
|
163
|
-
padding_proc.call(region)
|
164
|
-
end
|
165
|
-
end
|
84
|
+
requested_padding = get_requested_padding(args)
|
85
|
+
value = convert_to_universal(args)
|
86
|
+
value = { type: args[0], selector: args[1] } if value.nil?
|
87
|
+
value = value[:selector] if value.is_a?(Hash) && (value[:type].to_s === 'id')
|
88
|
+
value = { region: value }.merge(requested_padding)
|
166
89
|
floating_regions << value
|
167
90
|
self
|
168
91
|
end
|
@@ -249,6 +172,12 @@ module Applitools
|
|
249
172
|
self
|
250
173
|
end
|
251
174
|
|
175
|
+
def variation_group_id(value)
|
176
|
+
Applitools::ArgumentGuard.not_nil(value, 'variation_group_id')
|
177
|
+
options[:variation_group_id] = value
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
252
181
|
def frame(*args)
|
253
182
|
element = case args.first
|
254
183
|
when ::Selenium::WebDriver::Element, Applitools::Selenium::Element, String
|
@@ -256,8 +185,9 @@ module Applitools
|
|
256
185
|
else
|
257
186
|
proc { |d| d.find_element(*args) }
|
258
187
|
end
|
259
|
-
frames <<
|
260
|
-
|
188
|
+
frames << element
|
189
|
+
# frames << frame_or_element if frame_or_element
|
190
|
+
# self.frame_or_element = element
|
261
191
|
reset_for_fullscreen
|
262
192
|
self
|
263
193
|
end
|
@@ -274,23 +204,10 @@ module Applitools
|
|
274
204
|
# @!parse def region(element, how, what); end;
|
275
205
|
|
276
206
|
def region(*args)
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
applitools_element_from_selenium_element(driver, args.first)
|
282
|
-
end
|
283
|
-
when Applitools::Selenium::Element, Applitools::Region
|
284
|
-
proc { args.first }
|
285
|
-
when String
|
286
|
-
proc do |driver|
|
287
|
-
driver.find_element(name_or_id: args.first)
|
288
|
-
end
|
289
|
-
else
|
290
|
-
proc do |driver|
|
291
|
-
driver.find_element(*args)
|
292
|
-
end
|
293
|
-
end
|
207
|
+
value = convert_to_universal(args)
|
208
|
+
value = { type: args[0], selector: args[1] } if value.nil?
|
209
|
+
value = value[:selector] if value.is_a?(Hash) && (value[:type].to_s === 'id')
|
210
|
+
self.region_to_check = value
|
294
211
|
self.coordinate_type = Applitools::EyesScreenshot::COORDINATE_TYPES[:context_relative]
|
295
212
|
options[:timeout] = nil
|
296
213
|
reset_ignore
|
@@ -309,11 +226,16 @@ module Applitools
|
|
309
226
|
end
|
310
227
|
|
311
228
|
def before_render_screenshot_hook(hook)
|
312
|
-
|
229
|
+
if hook.is_a?(Hash) && hook[:beforeCaptureScreenshot]
|
230
|
+
options[:script_hooks] = hook
|
231
|
+
else
|
232
|
+
options[:script_hooks][:beforeCaptureScreenshot] = hook
|
233
|
+
end
|
313
234
|
self
|
314
235
|
end
|
315
236
|
|
316
237
|
alias script_hook before_render_screenshot_hook
|
238
|
+
alias hooks before_render_screenshot_hook
|
317
239
|
|
318
240
|
def finalize
|
319
241
|
return self unless frame_or_element
|
@@ -333,11 +255,7 @@ module Applitools
|
|
333
255
|
"The region type should be one of [#{Applitools::AccessibilityRegionType.enum_values.join(', ')}]"
|
334
256
|
end
|
335
257
|
handle_frames
|
336
|
-
padding_proc = proc
|
337
|
-
Applitools::AccessibilityRegion.new(
|
338
|
-
region, options[:type]
|
339
|
-
)
|
340
|
-
end
|
258
|
+
padding_proc = proc { |region| Applitools::AccessibilityRegion.new(region, options[:type]) }
|
341
259
|
|
342
260
|
accessibility_regions << case args.first
|
343
261
|
when ::Selenium::WebDriver::Element
|
@@ -361,30 +279,34 @@ module Applitools
|
|
361
279
|
next element, padding_proc if return_element
|
362
280
|
padding_proc.call(element)
|
363
281
|
end
|
282
|
+
when :css
|
283
|
+
# (:css, '.ignore', type: 'LargeText')
|
284
|
+
{ region: args[1], type: options[:type] }
|
364
285
|
else
|
365
286
|
proc do |driver, return_element = false|
|
366
287
|
elements = driver.find_elements(*args)
|
367
288
|
next elements, padding_proc if return_element
|
368
289
|
elements.map { |e| padding_proc.call(e) }
|
369
290
|
end
|
291
|
+
|
370
292
|
end
|
371
293
|
self
|
372
294
|
end
|
373
295
|
|
374
296
|
def default_full_page_for_vg
|
375
|
-
if options[:stitch_content].nil?
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
end
|
297
|
+
# if options[:stitch_content].nil?
|
298
|
+
# case region_to_check
|
299
|
+
# when nil
|
300
|
+
# fully(true)
|
301
|
+
# when Proc
|
302
|
+
# begin
|
303
|
+
# r = region_to_check.call
|
304
|
+
# # fully(true) if r == Applitools::Region::EMPTY
|
305
|
+
# rescue StandardError
|
306
|
+
# fully(false)
|
307
|
+
# end
|
308
|
+
# end
|
309
|
+
# end
|
388
310
|
nil
|
389
311
|
end
|
390
312
|
|
@@ -392,6 +314,24 @@ module Applitools
|
|
392
314
|
self.convert_coordinates_block = block
|
393
315
|
end
|
394
316
|
|
317
|
+
def scroll_root_element(by, what = nil)
|
318
|
+
options[:scroll_root_element] = if is_element?(by)
|
319
|
+
ref = by.ref
|
320
|
+
ref = by.ref[1] if ref.is_a?(Array) && ref[0] === :element
|
321
|
+
{ elementId: ref }
|
322
|
+
elsif what
|
323
|
+
{ type: by.to_s, selector: what }
|
324
|
+
else
|
325
|
+
by
|
326
|
+
end
|
327
|
+
self
|
328
|
+
end
|
329
|
+
|
330
|
+
def layout_breakpoints(value = true)
|
331
|
+
options[:layout_breakpoints] = value.is_a?(Array) ? value : value
|
332
|
+
self
|
333
|
+
end
|
334
|
+
|
395
335
|
private
|
396
336
|
|
397
337
|
def reset_for_fullscreen
|
@@ -442,6 +382,67 @@ module Applitools
|
|
442
382
|
xpath = driver.execute_script(Applitools::Selenium::Scripts::GET_ELEMENT_XPATH_JS, selenium_element)
|
443
383
|
driver.find_element(:xpath, xpath)
|
444
384
|
end
|
385
|
+
|
386
|
+
def get_requested_padding(args)
|
387
|
+
if args.last.is_a? Applitools::PaddingBounds
|
388
|
+
args.pop
|
389
|
+
elsif args.last.is_a?(Applitools::FloatingBounds)
|
390
|
+
args.pop.to_hash
|
391
|
+
else
|
392
|
+
{}
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
|
397
|
+
|
398
|
+
def is_element?(el)
|
399
|
+
el.is_a?(::Selenium::WebDriver::Element) || (el.is_a?(Applitools::Selenium::Element) && el.respond_to?(:ref))
|
400
|
+
end
|
401
|
+
|
402
|
+
def is_region?(region)
|
403
|
+
region.is_a?(Applitools::FloatingRegion) || region.is_a?(Applitools::Region) # || region.is_a?(Applitools::Selenium::Element)
|
404
|
+
end
|
405
|
+
|
406
|
+
def is_finder?(finders)
|
407
|
+
return false unless finders.is_a?(Array)
|
408
|
+
return false unless finders[1]
|
409
|
+
return true if [:uiautomator, :predicate, :accessibility_id].include?(finders[0].to_sym)
|
410
|
+
Applitools::Selenium::Driver::FINDERS.has_key?(finders[0].to_sym)
|
411
|
+
end
|
412
|
+
|
413
|
+
def convert_to_universal(args)
|
414
|
+
if is_element?(args.first)
|
415
|
+
ref = args.first.ref
|
416
|
+
ref = args.first.ref[1] if ref.is_a?(Array) && ref[0] === :element
|
417
|
+
return { elementId: ref }
|
418
|
+
end
|
419
|
+
return args.first.to_hash if is_region?(args.first)
|
420
|
+
if is_finder?(args)
|
421
|
+
if Applitools::Selenium::Driver::FINDERS.has_key?(args[0])
|
422
|
+
return {type: Applitools::Selenium::Driver::FINDERS[args[0]], selector: args[1]}
|
423
|
+
end
|
424
|
+
case args[0]
|
425
|
+
when :uiautomator # ANDROID_UI_AUTOMATOR: '-android uiautomator'
|
426
|
+
return {type: '-android uiautomator', selector: args[1]}
|
427
|
+
when :predicate # IOS_PREDICATE: '-ios predicate string',
|
428
|
+
return {type: '-ios predicate string', selector: args[1]}
|
429
|
+
when :accessibility_id
|
430
|
+
return {type: 'accessibility id', selector: args[1]}
|
431
|
+
end
|
432
|
+
end
|
433
|
+
if args.first.is_a?(String)
|
434
|
+
return proc { |driver| driver.find_element(name_or_id: args.first) }
|
435
|
+
end
|
436
|
+
if args.first.is_a?(Hash) && args.first.has_key?('selector')
|
437
|
+
if args.first.has_key?('shadow')
|
438
|
+
return {selector: args.first['selector'], shadow: args.first['shadow']}
|
439
|
+
else
|
440
|
+
return {selector: args.first['selector']}
|
441
|
+
end
|
442
|
+
end
|
443
|
+
nil
|
444
|
+
end
|
445
|
+
|
445
446
|
end
|
446
447
|
end
|
447
448
|
end
|