capybara-chrome 0.1.22
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +110 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/capybara-chrome.gemspec +31 -0
- data/lib/capybara-chrome.rb +1 -0
- data/lib/capybara/chrome.rb +56 -0
- data/lib/capybara/chrome/browser.rb +393 -0
- data/lib/capybara/chrome/configuration.rb +77 -0
- data/lib/capybara/chrome/debug.rb +17 -0
- data/lib/capybara/chrome/driver.rb +38 -0
- data/lib/capybara/chrome/errors.rb +15 -0
- data/lib/capybara/chrome/node.rb +343 -0
- data/lib/capybara/chrome/rdp_client.rb +204 -0
- data/lib/capybara/chrome/rdp_socket.rb +29 -0
- data/lib/capybara/chrome/rdp_web_socket_client.rb +51 -0
- data/lib/capybara/chrome/repeat_timeout.rb +15 -0
- data/lib/capybara/chrome/service.rb +109 -0
- data/lib/capybara/chrome/version.rb +5 -0
- data/lib/chrome_remote_helper.js +340 -0
- metadata +154 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module Capybara::Chrome
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
DEFAULT_ALLOWED_URLS = [
|
5
|
+
%r(.*127\.0\.0\.1),
|
6
|
+
%r(.*localhost),
|
7
|
+
"data:*,*"
|
8
|
+
].freeze
|
9
|
+
DEFAULT_MAX_WAIT_TIME = 10
|
10
|
+
DEFAULT_DOWNLOAD_PATH = "/tmp"
|
11
|
+
# set Capybara::Chrome::Configuration.chrome_port = 9222 for easy debugging
|
12
|
+
DEFAULT_CHROME_PORT = nil
|
13
|
+
DEFAULT_TRAP_INTERRUPT = true
|
14
|
+
DEFAULT_DEBUG = false
|
15
|
+
|
16
|
+
attr_accessor :max_wait_time, :download_path, :chrome_port, :trap_interrupt, :debug
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@allowed_urls = DEFAULT_ALLOWED_URLS.dup
|
20
|
+
@blocked_urls = []
|
21
|
+
@max_wait_time = DEFAULT_MAX_WAIT_TIME
|
22
|
+
@download_path = DEFAULT_DOWNLOAD_PATH
|
23
|
+
@chrome_port = DEFAULT_CHROME_PORT
|
24
|
+
@trap_interrupt = DEFAULT_TRAP_INTERRUPT
|
25
|
+
@debug = DEFAULT_DEBUG
|
26
|
+
end
|
27
|
+
|
28
|
+
def block_unknown_urls
|
29
|
+
@block_unknown_urls = true
|
30
|
+
end
|
31
|
+
|
32
|
+
def allow_unknown_urls
|
33
|
+
allow_url(/.*/)
|
34
|
+
end
|
35
|
+
|
36
|
+
def re_url(url)
|
37
|
+
url.is_a?(Regexp) ? url : Regexp.new(Regexp.escape(url))
|
38
|
+
end
|
39
|
+
|
40
|
+
def allow_url(url)
|
41
|
+
@allowed_urls << re_url(url)
|
42
|
+
end
|
43
|
+
|
44
|
+
def block_url(url)
|
45
|
+
@blocked_urls << re_url(url)
|
46
|
+
end
|
47
|
+
|
48
|
+
def url_match?(pattern, url)
|
49
|
+
pattern === url
|
50
|
+
end
|
51
|
+
|
52
|
+
def url_allowed?(url)
|
53
|
+
@allowed_urls.detect {|pattern| url_match?(pattern, url)}
|
54
|
+
end
|
55
|
+
|
56
|
+
def block_url?(url)
|
57
|
+
if url_allowed?(url)
|
58
|
+
false
|
59
|
+
else
|
60
|
+
@block_unknown_urls || @blocked_urls.detect {|pattern| url_match?(pattern, url)}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def skip_image_loading
|
65
|
+
@skip_image_loading = true
|
66
|
+
end
|
67
|
+
|
68
|
+
def skip_image_loading?
|
69
|
+
@skip_image_loading
|
70
|
+
end
|
71
|
+
|
72
|
+
def trap_interrupt?
|
73
|
+
@trap_interrupt
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Chrome
|
3
|
+
module Debug
|
4
|
+
def debug(*args)
|
5
|
+
if Capybara::Chrome.configuration.debug
|
6
|
+
p [caller_locations(1,1)[0].label, *args, Time.now.to_i]
|
7
|
+
end
|
8
|
+
return args[0]
|
9
|
+
end
|
10
|
+
|
11
|
+
def info(*args)
|
12
|
+
p [caller_locations(1,1)[0].label, *args, Time.now.to_i]
|
13
|
+
return args[0]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Capybara::Chrome
|
2
|
+
class Driver < Capybara::Driver::Base
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
def initialize(app, options={})
|
6
|
+
@app = app
|
7
|
+
@options = options
|
8
|
+
@session = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def_delegators :browser, :visit, :find_css, :html, :evaluate_script, :evaluate_async_script, :execute_script, :status_code, :save_screenshot, :render, :current_url, :header, :console_messages, :error_messages, :dismiss_modal, :accept_modal, :title, :unrecognized_scheme_requests, :wait_for_load
|
12
|
+
|
13
|
+
def browser
|
14
|
+
@browser ||= Browser.new(self, port: @options[:port])
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_xpath(query)
|
18
|
+
nodes = browser.find_xpath(query)
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
browser.start
|
23
|
+
end
|
24
|
+
|
25
|
+
def needs_server?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset!
|
34
|
+
browser.reset
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
module Capybara::Chrome
|
2
|
+
class Node < ::Capybara::Driver::Node
|
3
|
+
include Debug
|
4
|
+
attr_reader :browser, :id
|
5
|
+
|
6
|
+
KEY_DATA = Hash.new do |h, k|
|
7
|
+
h[k] = {text: k.to_s}
|
8
|
+
end.merge!({
|
9
|
+
cancel: { key_code: 3, code: "Abort", key: "Cancel"},
|
10
|
+
help: { key_code: 6, code: "Help", key: "Help"},
|
11
|
+
backspace: { key_code: 8, code: "Backspace", key: "Backspace"},
|
12
|
+
tab: { key_code: 9, code: "Tab", key: "Tab"},
|
13
|
+
delete: { key_code: 46, code: "Delete", key: "Delete"},
|
14
|
+
home: { key_code: 36, code: "Home", key: "Home"},
|
15
|
+
end: { key_code: 35, code: "End", key: "End"},
|
16
|
+
left: { key_code: 37, code: "ArrowLeft", key: "ArrowLeft"},
|
17
|
+
right: { key_code: 39, code: "ArrowRight", key: "ArrowRight"},
|
18
|
+
up: { key_code: 38, code: "ArrowUp", key: "ArrowUp"},
|
19
|
+
down: { key_code: 40, code: "ArrowDown", key: "ArrowDown"},
|
20
|
+
return: { key_code: 13, code: "Enter", key: "Enter"},
|
21
|
+
enter: { key_code: 13, code: "Enter", key: "Enter"},
|
22
|
+
"\r" => { key_code: 13, code: "Enter", key: "Enter", text: "\r"},
|
23
|
+
"\n" => { key_code: 13, code: "Enter", key: "Enter", text: "\r"},
|
24
|
+
space: { key_code: 32, code: "Space", key: " ", text: " "},
|
25
|
+
shift: { key_code: 16, code: "Shift", key: "ShiftLeft", location: 1},
|
26
|
+
control: { key_code: 17, code: "ControlLeft", key: "Control", text: "\r"},
|
27
|
+
alt: { key_code: 18, code: "AltLeft", key: "Alt", location: 1},
|
28
|
+
meta: { key_code: 91, code: "MetaLeft", key: "Meta", location: 1},
|
29
|
+
})
|
30
|
+
|
31
|
+
def initialize(driver, browser, id)
|
32
|
+
raise "hell" if id == 0
|
33
|
+
@driver = driver
|
34
|
+
@browser = browser
|
35
|
+
@id = id
|
36
|
+
@mouse_x = 0
|
37
|
+
@mouse_y = 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def html
|
41
|
+
browser.evaluate_script %( ChromeRemoteHelper.waitDOMContentLoaded(); )
|
42
|
+
browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "outerHTML") )
|
43
|
+
end
|
44
|
+
|
45
|
+
def all_text
|
46
|
+
browser.evaluate_script %( ChromeRemoteHelper.waitDOMContentLoaded(); )
|
47
|
+
text = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "textContent") )
|
48
|
+
if Capybara::VERSION.to_f < 3.0
|
49
|
+
Capybara::Helpers.normalize_whitespace(text)
|
50
|
+
else
|
51
|
+
text.gsub(/[\u200b\u200e\u200f]/, '')
|
52
|
+
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
53
|
+
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
54
|
+
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
55
|
+
.tr("\u00a0", ' ')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def visible_text
|
60
|
+
raw_text.to_s.gsub(/\ +/, ' ')
|
61
|
+
.gsub(/[\ \n]*\n[\ \n]*/, "\n")
|
62
|
+
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
63
|
+
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
64
|
+
.tr("\u00a0", ' ')
|
65
|
+
end
|
66
|
+
alias text visible_text
|
67
|
+
|
68
|
+
def raw_text
|
69
|
+
browser.evaluate_script %( ChromeRemoteHelper.nodeText(#{id}) )
|
70
|
+
end
|
71
|
+
|
72
|
+
def visible?
|
73
|
+
browser.evaluate_script %( ChromeRemoteHelper.nodeVisible(#{id}) )
|
74
|
+
end
|
75
|
+
|
76
|
+
def is_connected?
|
77
|
+
# on_self_value %( return this.isConnected )
|
78
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "isConnected") )
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_dimensions
|
82
|
+
browser.wait_for_load
|
83
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.nodeGetDimensions(#{id}) )
|
84
|
+
val = JSON.parse(val) rescue {}
|
85
|
+
val
|
86
|
+
end
|
87
|
+
|
88
|
+
def expect_node_at_position(x,y)
|
89
|
+
in_position = browser.evaluate_script %( ChromeRemoteHelper.nodeIsNodeAtPosition(#{id}, {relativeX: #{x}, relativeY: #{y}}) )
|
90
|
+
if !in_position
|
91
|
+
raise "Element '<#{tag_name}>' not at expected position: #{x},#{y}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def move_mouse(x, y, steps: 1)
|
96
|
+
if steps >= 1
|
97
|
+
(0..x).step(steps).each do |dx|
|
98
|
+
send_cmd! "Input.dispatchMouseEvent", type: "mouseMoved", x: dx, y: 0
|
99
|
+
end
|
100
|
+
(0..y).step(steps).each do |dy|
|
101
|
+
send_cmd! "Input.dispatchMouseEvent", type: "mouseMoved", x: x, y: dy
|
102
|
+
end
|
103
|
+
end
|
104
|
+
send_cmd! "Input.dispatchMouseEvent", type: "mouseMoved", x: x, y: y
|
105
|
+
end
|
106
|
+
|
107
|
+
def click
|
108
|
+
browser.with_retry do
|
109
|
+
on_self("this.scrollIntoViewIfNeeded();");
|
110
|
+
dim = get_dimensions
|
111
|
+
if dim["width"] == 0 && dim["height"] == 0
|
112
|
+
puts "DIM IS 0"
|
113
|
+
puts html
|
114
|
+
raise Capybara::ElementNotFound
|
115
|
+
end
|
116
|
+
xd = [1, dim["width"]/2, dim["width"]/3]
|
117
|
+
yd = [1, dim["height"]/2, dim["height"]/3]
|
118
|
+
strategy = rand(0..xd.size-1)
|
119
|
+
cx = (dim["x"] + xd[strategy]).floor
|
120
|
+
cy = (dim["y"] + yd[strategy]).floor
|
121
|
+
move_mouse(cx, cy, steps: 0)
|
122
|
+
expect_node_at_position(cx, cy)
|
123
|
+
send_cmd! "Input.dispatchMouseEvent", type: "mousePressed", x: cx, y: cy, clickCount: 1, button: "left"
|
124
|
+
send_cmd! "Input.dispatchMouseEvent", type: "mouseReleased", x: cx, y: cy, clickCount: 1, button: "left"
|
125
|
+
vv = browser.wait_for_load
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def find_css(query)
|
130
|
+
browser.query_selector_all(query, id)
|
131
|
+
end
|
132
|
+
|
133
|
+
def find_xpath(query)
|
134
|
+
browser.find_xpath query, id
|
135
|
+
end
|
136
|
+
|
137
|
+
def path
|
138
|
+
browser.evaluate_script %( ChromeRemoteHelper.nodePathForNode(#{id}) )
|
139
|
+
end
|
140
|
+
alias get_xpath path
|
141
|
+
|
142
|
+
def disabled?
|
143
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "disabled") )
|
144
|
+
debug val
|
145
|
+
val
|
146
|
+
end
|
147
|
+
|
148
|
+
TEXT_TYPES = %w(date email number password search tel text textarea url)
|
149
|
+
def set(value, options={})
|
150
|
+
value = value.to_s.gsub('"', '\"')
|
151
|
+
type = browser.evaluate_script %( ChromeRemoteHelper.nodeSetType(#{id}) )
|
152
|
+
|
153
|
+
if type == "file"
|
154
|
+
send_cmd "DOM.setFileInputFiles", files: [value.to_s], nodeId: node_id
|
155
|
+
elsif TEXT_TYPES.include?(type)
|
156
|
+
script = "this.value = ''; this.focus();"
|
157
|
+
if value.blank?
|
158
|
+
script << %(ChromeRemoteHelper.dispatchEvent(this, "change"))
|
159
|
+
end
|
160
|
+
on_self script
|
161
|
+
type_string(value.to_s)
|
162
|
+
else
|
163
|
+
browser.evaluate_script %( ChromeRemoteHelper.nodeSet(#{id}, "#{value}", "#{type}") )
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def node_id
|
168
|
+
browser.get_document
|
169
|
+
val = browser.execute_script %(ChromeRemoteHelper.onSelf(#{id}, "return this;"))
|
170
|
+
send_cmd("DOM.requestNode", objectId: val["result"]["objectId"])["nodeId"]
|
171
|
+
end
|
172
|
+
|
173
|
+
def type_string(string)
|
174
|
+
ary = string.chars
|
175
|
+
ary.each do |char|
|
176
|
+
char.tr!("\n", "\r")
|
177
|
+
send_cmd! "Input.dispatchKeyEvent", {type: "keyDown", text: char}
|
178
|
+
send_cmd! "Input.dispatchKeyEvent", {type: "keyUp"}
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def send_keys(*keys)
|
183
|
+
keys.each do |key|
|
184
|
+
if key.is_a? Array
|
185
|
+
mods, new_keys = get_modifiers(key)
|
186
|
+
new_keys.each do |k|
|
187
|
+
send_key_data(get_key_data(k), modifiers: mods)
|
188
|
+
end
|
189
|
+
else
|
190
|
+
send_key_data(get_key_data(key))
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def get_key_data(key)
|
196
|
+
KEY_DATA[key]
|
197
|
+
end
|
198
|
+
|
199
|
+
def send_key_data(data, modifiers: 0)
|
200
|
+
type = data[:text] ? "keyDown" : "rawKeyDown"
|
201
|
+
send_cmd! "Input.dispatchKeyEvent", {type: type, text: data[:text].to_s, windowsVirtualKeyCode: data[:key_code].to_i, code: data[:code].to_s, key: data[:key].to_s, modifiers: modifiers}
|
202
|
+
send_cmd! "Input.dispatchKeyEvent", {type: "keyUp", modifiers: modifiers}
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_modifiers(ary)
|
206
|
+
mods = []
|
207
|
+
keys = []
|
208
|
+
ary.each do |k|
|
209
|
+
case k
|
210
|
+
when :alt
|
211
|
+
mods << 1
|
212
|
+
when :control
|
213
|
+
mods << 2
|
214
|
+
when :meta, :command
|
215
|
+
mods << 4
|
216
|
+
when :shift
|
217
|
+
mods << 8
|
218
|
+
else
|
219
|
+
keys << k
|
220
|
+
end
|
221
|
+
end
|
222
|
+
[mods.inject(&:|), keys]
|
223
|
+
end
|
224
|
+
|
225
|
+
def focus
|
226
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "focus") )
|
227
|
+
end
|
228
|
+
|
229
|
+
def value(*args)
|
230
|
+
debug args
|
231
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "value") )
|
232
|
+
end
|
233
|
+
|
234
|
+
def checked?
|
235
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "checked") )
|
236
|
+
end
|
237
|
+
|
238
|
+
def selected?
|
239
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "selected") )
|
240
|
+
end
|
241
|
+
|
242
|
+
def disabled?
|
243
|
+
val = browser.evaluate_script %( ChromeRemoteHelper.onSelfValue(#{id}, "disabled") )
|
244
|
+
end
|
245
|
+
|
246
|
+
def select_option(*args)
|
247
|
+
on_self_value %(
|
248
|
+
if (this.disabled)
|
249
|
+
return;
|
250
|
+
|
251
|
+
var selectNode = this.parentNode;
|
252
|
+
if (selectNode.tagName == "OPTGROUP")
|
253
|
+
selectNode = selectNode.parentNode;
|
254
|
+
|
255
|
+
ChromeRemoteHelper.dispatchEvent(selectNode, "mousedown");
|
256
|
+
selectNode.focus();
|
257
|
+
ChromeRemoteHelper.dispatchEvent(selectNode, "input");
|
258
|
+
|
259
|
+
if (!this.selected) {
|
260
|
+
this.selected = true;
|
261
|
+
ChromeRemoteHelper.dispatchEvent(selectNode, "change");
|
262
|
+
}
|
263
|
+
|
264
|
+
ChromeRemoteHelper.dispatchEvent(selectNode, "mouseup");
|
265
|
+
ChromeRemoteHelper.dispatchEvent(selectNode, "click");
|
266
|
+
)
|
267
|
+
end
|
268
|
+
|
269
|
+
def trigger(event_name)
|
270
|
+
on_self %( ChromeRemoteHelper.dispatchEvent(this, '#{event_name}') )
|
271
|
+
end
|
272
|
+
|
273
|
+
def method_missing(method, *args)
|
274
|
+
debug ["method missing", method, args]
|
275
|
+
raise "method missing #{method} #{args.inspect}"
|
276
|
+
end
|
277
|
+
|
278
|
+
def [](attr)
|
279
|
+
on_self_value %(return this.getAttribute("#{attr}") )
|
280
|
+
end
|
281
|
+
|
282
|
+
def node_description
|
283
|
+
# return @node_description if @node_description
|
284
|
+
@node_description = send_cmd("DOM.describeNode", nodeId: id)
|
285
|
+
debug ["node_desc", @node_description, id]
|
286
|
+
if @node_description.nil?
|
287
|
+
raise Capybara::ExpectationNotMet
|
288
|
+
end
|
289
|
+
@node_description
|
290
|
+
end
|
291
|
+
|
292
|
+
def tag_name
|
293
|
+
# node_description["node"]["localName"]
|
294
|
+
on_self_value("return this.tagName.toLowerCase()")
|
295
|
+
end
|
296
|
+
alias local_name tag_name
|
297
|
+
|
298
|
+
def send_cmd(cmd, args={})
|
299
|
+
browser.remote.send_cmd(cmd, args)
|
300
|
+
end
|
301
|
+
def send_cmd!(cmd, args={})
|
302
|
+
browser.remote.send_cmd!(cmd, args)
|
303
|
+
end
|
304
|
+
|
305
|
+
def on_self_value(function_body, options={})
|
306
|
+
function_body = function_body.gsub('"', '\"').gsub(/\s+/, " ")
|
307
|
+
browser.evaluate_script %(ChromeRemoteHelper.onSelf(#{id}, "#{function_body}"))
|
308
|
+
end
|
309
|
+
|
310
|
+
def on_self(function_body, options={})
|
311
|
+
function_body = function_body.gsub('"', '\"').gsub(/\s+/, " ")
|
312
|
+
browser.evaluate_script %( window.ChromeRemoteHelper && ChromeRemoteHelper.waitDOMContentLoaded(); )
|
313
|
+
browser.evaluate_script %(window.ChromeRemoteHelper && ChromeRemoteHelper.onSelf(#{id}, "#{function_body}"))
|
314
|
+
end
|
315
|
+
|
316
|
+
def on_self!(function_body, options={})
|
317
|
+
function_body = function_body.gsub('"', '\"').gsub(/\s+/, " ")
|
318
|
+
|
319
|
+
browser.evaluate_script %( ChromeRemoteHelper.waitDOMContentLoaded(); )
|
320
|
+
|
321
|
+
browser.execute_script! %(ChromeRemoteHelper.onSelf(#{id}, "#{function_body}"))
|
322
|
+
end
|
323
|
+
|
324
|
+
def remote_object_id
|
325
|
+
remote_object["object"]["objectId"]
|
326
|
+
end
|
327
|
+
|
328
|
+
def remote_object
|
329
|
+
return @remote_object if @remote_object
|
330
|
+
@remote_object = send_cmd("DOM.resolveNode", nodeId: id)
|
331
|
+
debug @remote_object, id
|
332
|
+
if @remote_object.nil?
|
333
|
+
raise Capybara::ExpectationNotMet
|
334
|
+
end
|
335
|
+
@remote_object
|
336
|
+
end
|
337
|
+
|
338
|
+
def request_node(remote_object_id)
|
339
|
+
@request_node ||= send_cmd("DOM.requestNode", objectId: remote_object_id)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|