isomorfeus-puppetmaster 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +191 -0
- data/lib/isomorfeus-puppetmaster.rb +43 -0
- data/lib/isomorfeus/puppetmaster.rb +62 -0
- data/lib/isomorfeus/puppetmaster/console_message.rb +19 -0
- data/lib/isomorfeus/puppetmaster/cookie.rb +46 -0
- data/lib/isomorfeus/puppetmaster/document.rb +160 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom.rb +370 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom_document.rb +908 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom_node.rb +836 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer.rb +401 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer_document.rb +944 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer_node.rb +866 -0
- data/lib/isomorfeus/puppetmaster/driver_registration.rb +19 -0
- data/lib/isomorfeus/puppetmaster/dsl.rb +40 -0
- data/lib/isomorfeus/puppetmaster/errors.rb +90 -0
- data/lib/isomorfeus/puppetmaster/iframe.rb +17 -0
- data/lib/isomorfeus/puppetmaster/node.rb +241 -0
- data/lib/isomorfeus/puppetmaster/node/checkbox.rb +17 -0
- data/lib/isomorfeus/puppetmaster/node/content_editable.rb +18 -0
- data/lib/isomorfeus/puppetmaster/node/filechooser.rb +9 -0
- data/lib/isomorfeus/puppetmaster/node/input.rb +21 -0
- data/lib/isomorfeus/puppetmaster/node/radiobutton.rb +13 -0
- data/lib/isomorfeus/puppetmaster/node/select.rb +36 -0
- data/lib/isomorfeus/puppetmaster/node/textarea.rb +7 -0
- data/lib/isomorfeus/puppetmaster/request.rb +17 -0
- data/lib/isomorfeus/puppetmaster/response.rb +26 -0
- data/lib/isomorfeus/puppetmaster/rspec/features.rb +23 -0
- data/lib/isomorfeus/puppetmaster/rspec/matcher_proxies.rb +80 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers.rb +164 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/base.rb +98 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/become_closed.rb +33 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/compound.rb +88 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_current_path.rb +29 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_selector.rb +69 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_text.rb +33 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_title.rb +29 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/match_selector.rb +27 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/match_style.rb +38 -0
- data/lib/isomorfeus/puppetmaster/self_forwardable.rb +31 -0
- data/lib/isomorfeus/puppetmaster/server.rb +128 -0
- data/lib/isomorfeus/puppetmaster/server/checker.rb +40 -0
- data/lib/isomorfeus/puppetmaster/server/middleware.rb +60 -0
- data/lib/isomorfeus/puppetmaster/server_registration.rb +37 -0
- data/lib/isomorfeus/puppetmaster/version.rb +3 -0
- metadata +282 -0
@@ -0,0 +1,401 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Puppetmaster
|
3
|
+
module Driver
|
4
|
+
class Puppeteer
|
5
|
+
include Isomorfeus::Puppetmaster::Driver::PuppeteerDocument
|
6
|
+
include Isomorfeus::Puppetmaster::Driver::PuppeteerNode
|
7
|
+
|
8
|
+
VIEWPORT_DEFAULT_WIDTH = 1024
|
9
|
+
VIEWPORT_DEFAULT_HEIGHT = 768
|
10
|
+
VIEWPORT_MAX_WIDTH = 1366
|
11
|
+
VIEWPORT_MAX_HEIGHT = 768
|
12
|
+
TIMEOUT = 30 # seconds
|
13
|
+
REACTION_TIMEOUT = 0.5
|
14
|
+
EVENTS = {
|
15
|
+
blur: ['FocusEvent', {}],
|
16
|
+
focus: ['FocusEvent', {}],
|
17
|
+
focusin: ['FocusEvent', { bubbles: true }],
|
18
|
+
focusout: ['FocusEvent', { bubbles: true }],
|
19
|
+
click: ['MouseEvent', { bubbles: true, cancelable: true }],
|
20
|
+
dblckick: ['MouseEvent', { bubbles: true, cancelable: true }],
|
21
|
+
mousedown: ['MouseEvent', { bubbles: true, cancelable: true }],
|
22
|
+
mouseup: ['MouseEvent', { bubbles: true, cancelable: true }],
|
23
|
+
mouseenter: ['MouseEvent', {}],
|
24
|
+
mouseleave: ['MouseEvent', {}],
|
25
|
+
mousemove: ['MouseEvent', { bubbles: true, cancelable: true }],
|
26
|
+
mouseover: ['MouseEvent', { bubbles: true, cancelable: true }],
|
27
|
+
mouseout: ['MouseEvent', { bubbles: true, cancelable: true }],
|
28
|
+
context_menu: ['MouseEvent', { bubble: true, cancelable: true }],
|
29
|
+
submit: ['Event', { bubbles: true, cancelable: true }],
|
30
|
+
change: ['Event', { bubbles: true, cacnelable: false }],
|
31
|
+
input: ['InputEvent', { bubbles: true, cacnelable: false }],
|
32
|
+
wheel: ['WheelEvent', { bubbles: true, cancelable: true }]
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
attr_accessor :app, :default_document, :url_blacklist
|
36
|
+
|
37
|
+
def initialize(options = {})
|
38
|
+
# https://pptr.dev/#?product=Puppeteer&version=v1.12.2&show=api-puppeteerlaunchoptions
|
39
|
+
# init ExecJs context
|
40
|
+
@app = options.delete(:app)
|
41
|
+
@options = options.dup
|
42
|
+
@browser_type = @options.delete(:browser_type) { :chromium }
|
43
|
+
@max_width = @options.delete(:max_width) { VIEWPORT_MAX_WIDTH }
|
44
|
+
@max_height = @options.delete(:max_height) { VIEWPORT_MAX_HEIGHT }
|
45
|
+
@width = @options.delete(:width) { VIEWPORT_DEFAULT_WIDTH > @max_width ? @max_width : VIEWPORT_DEFAULT_WIDTH }
|
46
|
+
@height = @options.delete(:height) { VIEWPORT_DEFAULT_HEIGHT > @max_height ? @max_height : VIEWPORT_DEFAULT_HEIGHT }
|
47
|
+
@timeout = @options.delete(:timeout) { TIMEOUT }
|
48
|
+
@max_wait = @options.delete(:max_wait) { @timeout + 1 }
|
49
|
+
@reaction_timeout = @options.delete(:reaction_timeout) { REACTION_TIMEOUT }
|
50
|
+
@puppeteer_timeout = @timeout * 1000
|
51
|
+
@puppeteer_reaction_timeout = @reaction_timeout * 1000
|
52
|
+
@url_blacklist = @options.delete(:url_blacklist) { [] }
|
53
|
+
@context = ExecJS.permissive_compile(puppeteer_launch)
|
54
|
+
page_handle = await_result
|
55
|
+
@default_document = Isomorfeus::Puppetmaster::Document.new(self, page_handle, Isomorfeus::Puppetmaster::Response.new('status' => 200))
|
56
|
+
ObjectSpace.define_finalizer(self, self.class.close_browser(self))
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.document_handle_disposer(driver, handle)
|
60
|
+
cjs = <<~JAVASCRIPT
|
61
|
+
if (AllPageHandles[#{handle}]) { AllPageHandles[#{handle}].close(); }
|
62
|
+
delete AllPageHandles[#{handle}];
|
63
|
+
delete ConsoleMessages[#{handle}];
|
64
|
+
JAVASCRIPT
|
65
|
+
proc { driver.execute_script(cjs) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.node_handle_disposer(driver, handle)
|
69
|
+
cjs = <<~JAVASCRIPT
|
70
|
+
if (AllElementHandles[#{handle}]) { AllElementHandles[#{handle}].dispose(); }
|
71
|
+
delete AllElementHandles[#{handle}];
|
72
|
+
JAVASCRIPT
|
73
|
+
proc { driver.execute_script(cjs) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def browser
|
77
|
+
await('LastResult = await CurrentBrowser.userAgent();')
|
78
|
+
end
|
79
|
+
|
80
|
+
def document_handles
|
81
|
+
await <<~JAVASCRIPT
|
82
|
+
var pages = await CurrentBrowser.pages();
|
83
|
+
var handles = [];
|
84
|
+
for (i=0; i< pages.length; i++) {
|
85
|
+
handles.push(RegisterPage(pages[i]));
|
86
|
+
}
|
87
|
+
LastResult = handles;
|
88
|
+
JAVASCRIPT
|
89
|
+
end
|
90
|
+
|
91
|
+
##### frame, all todo
|
92
|
+
|
93
|
+
def frame_all_text(frame)
|
94
|
+
await <<~JAVASCRIPT
|
95
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
96
|
+
return frame.contentDocument.documentElement.textContent;
|
97
|
+
}, AllElementHandles[#{frame.handle}]);
|
98
|
+
JAVASCRIPT
|
99
|
+
end
|
100
|
+
|
101
|
+
def frame_body(frame)
|
102
|
+
node_data = await <<~JAVASCRIPT
|
103
|
+
var tt = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
104
|
+
node = frame.contentDocument.body;
|
105
|
+
var tag = node.tagName.toLowerCase();
|
106
|
+
var type = null;
|
107
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
108
|
+
return [tag, type];
|
109
|
+
}, AllElementHandles[#{frame.handle}]);
|
110
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1]};
|
111
|
+
JAVASCRIPT
|
112
|
+
if node_data
|
113
|
+
node_data[:css_selector] = 'body'
|
114
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def frame_focus(frame)
|
119
|
+
await <<~JAVASCRIPT
|
120
|
+
await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
121
|
+
frame.contentDocument.documentElement.focus();
|
122
|
+
}, AllElementHandles[#{frame.handle}]);
|
123
|
+
JAVASCRIPT
|
124
|
+
end
|
125
|
+
|
126
|
+
def frame_head(frame)
|
127
|
+
node_data = await <<~JAVASCRIPT
|
128
|
+
var tt = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
129
|
+
node = frame.contentDocument.head;
|
130
|
+
var tag = node.tagName.toLowerCase();
|
131
|
+
var type = null;
|
132
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
133
|
+
return [tag, type];
|
134
|
+
}, AllElementHandles[#{frame.handle}]);
|
135
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1]};
|
136
|
+
JAVASCRIPT
|
137
|
+
if node_data
|
138
|
+
node_data[:css_selector] = 'body'
|
139
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def frame_html(frame)
|
144
|
+
await <<~JAVASCRIPT
|
145
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
146
|
+
return frame.contentDocument.documentElement.outerHTML;
|
147
|
+
}, AllElementHandles[#{frame.handle}]);
|
148
|
+
JAVASCRIPT
|
149
|
+
end
|
150
|
+
|
151
|
+
def frame_title(frame)
|
152
|
+
await <<~JAVASCRIPT
|
153
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
154
|
+
return frame.contentDocument.title;
|
155
|
+
}, AllElementHandles[#{frame.handle}]);
|
156
|
+
JAVASCRIPT
|
157
|
+
end
|
158
|
+
|
159
|
+
def frame_url(frame)
|
160
|
+
await <<~JAVASCRIPT
|
161
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
162
|
+
return frame.contentDocument.location.href;
|
163
|
+
}, AllElementHandles[#{frame.handle}]);
|
164
|
+
JAVASCRIPT
|
165
|
+
end
|
166
|
+
|
167
|
+
def frame_visible_text(frame)
|
168
|
+
# if node is AREA, check visibility of relevant image
|
169
|
+
text = await <<~JAVASCRIPT
|
170
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
171
|
+
var node = frame.contentDocument.body;
|
172
|
+
var temp_node = node;
|
173
|
+
while (temp_node) {
|
174
|
+
style = window.getComputedStyle(node);
|
175
|
+
if (style.display === "none" || style.visibility === "hidden" || parseFloat(style.opacity) === 0) { return ''; }
|
176
|
+
temp_node = temp_node.parentElement;
|
177
|
+
}
|
178
|
+
if (node.nodeName == "TEXTAREA" || node instanceof SVGElement) { return node.textContent; }
|
179
|
+
else { return node.innerText; }
|
180
|
+
}, AllElementHandles[#{frame.handle}]);
|
181
|
+
JAVASCRIPT
|
182
|
+
text.gsub(/\A[[:space:]&&[^\u00a0]]+/, "").gsub(/[[:space:]&&[^\u00a0]]+\z/, "").gsub(/\n+/, "\n").tr("\u00a0", " ")
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def self.close_browser(driver)
|
188
|
+
cjs = <<~JAVASCRIPT
|
189
|
+
CurrentBrowser.close()
|
190
|
+
JAVASCRIPT
|
191
|
+
proc { driver.await(cjs) }
|
192
|
+
end
|
193
|
+
|
194
|
+
def await(script)
|
195
|
+
@context.eval <<~JAVASCRIPT
|
196
|
+
(async () => {
|
197
|
+
try {
|
198
|
+
LastExecutionFinished = false;
|
199
|
+
LastResult = null;
|
200
|
+
LastErr = null;
|
201
|
+
#{script}
|
202
|
+
LastExecutionFinished = true;
|
203
|
+
} catch(err) {
|
204
|
+
LastResult = null;
|
205
|
+
LastErr = err;
|
206
|
+
LastExecutionFinished = true;
|
207
|
+
}
|
208
|
+
})()
|
209
|
+
JAVASCRIPT
|
210
|
+
await_result
|
211
|
+
end
|
212
|
+
|
213
|
+
def await_result
|
214
|
+
start_time = Time.now
|
215
|
+
while !execution_finished? && !timed_out?(start_time)
|
216
|
+
sleep 0.01
|
217
|
+
end
|
218
|
+
get_result
|
219
|
+
end
|
220
|
+
|
221
|
+
def chromium_require
|
222
|
+
<<~JAVASCRIPT
|
223
|
+
const MasterPuppeteer = require('puppeteer');
|
224
|
+
JAVASCRIPT
|
225
|
+
end
|
226
|
+
|
227
|
+
def determine_error(message)
|
228
|
+
if message.include?('net::ERR_CERT_') || message.include?('SEC_ERROR_EXPIRED_CERTIFICATE')
|
229
|
+
Isomorfeus::Puppetmaster::CertificateError.new(message)
|
230
|
+
elsif message.include?('net::ERR_NAME_') || message.include?('NS_ERROR_UNKNOWN_HOST')
|
231
|
+
Isomorfeus::Puppetmaster::DNSError.new(message)
|
232
|
+
elsif message.include?('Unknown key: ')
|
233
|
+
Isomorfeus::Puppetmaster::KeyError.new(message)
|
234
|
+
elsif message.include?('Execution context was destroyed, most likely because of a navigation.')
|
235
|
+
Isomorfeus::Puppetmaster::ExecutionContextError.new(message)
|
236
|
+
elsif message.include?('Evaluation failed: DOMException:') || (message.include?('Evaluation failed:') && (message.include?('is not a valid selector') || message.include?('is not a legal expression')))
|
237
|
+
Isomorfeus::Puppetmaster::DOMException.new(message)
|
238
|
+
else
|
239
|
+
Isomorfeus::Puppetmaster::JavaScriptError.new(message)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def execution_finished?
|
244
|
+
@context.eval 'LastExecutionFinished'
|
245
|
+
end
|
246
|
+
|
247
|
+
def firefox_require
|
248
|
+
<<~JAVASCRIPT
|
249
|
+
const MasterPuppeteer = require('puppeteer-firefox');
|
250
|
+
JAVASCRIPT
|
251
|
+
end
|
252
|
+
|
253
|
+
def get_result
|
254
|
+
res, err_msg = @context.eval 'GetLastResult()'
|
255
|
+
raise determine_error(err_msg) if err_msg
|
256
|
+
res
|
257
|
+
end
|
258
|
+
|
259
|
+
def launch_line
|
260
|
+
string_options = []
|
261
|
+
options = @options.dup
|
262
|
+
string_options << "ignoreHTTPSErrors: #{options.delete(:ignore_https_errors)}" if options.has_key?(:ignore_https_errors)
|
263
|
+
string_options << "executablePath: '#{options.delete(:executable_path)}'" if options.has_key?(:executable_path)
|
264
|
+
options.each do |option, value|
|
265
|
+
string_options << "#{option.to_s.camelize(:lower)}: #{value}"
|
266
|
+
end
|
267
|
+
string_options << "userDataDir: '#{Dir.mktmpdir}'" unless @options.has_key?(:user_data_dir)
|
268
|
+
string_options << "defaultViewport: { width: #{@width}, height: #{@height} }"
|
269
|
+
string_options << "pipe: true"
|
270
|
+
# string_options << "args: ['--disable-popup-blocking']"
|
271
|
+
line = 'await MasterPuppeteer.launch('
|
272
|
+
unless string_options.empty?
|
273
|
+
line << '{'
|
274
|
+
line << string_options.join(', ') if string_options.size > 1
|
275
|
+
line << '}'
|
276
|
+
end
|
277
|
+
line << ')'
|
278
|
+
end
|
279
|
+
|
280
|
+
def puppeteer_launch
|
281
|
+
# todo target_handle, puppeteer save path
|
282
|
+
puppeteer_require = case @browser_type
|
283
|
+
when :firefox then firefox_require
|
284
|
+
when :chrome then chromium_require
|
285
|
+
when :chromium then chromium_require
|
286
|
+
else
|
287
|
+
raise "Browser type #{@browser_type} not supported! Browser type must be one of: chrome, firefox."
|
288
|
+
end
|
289
|
+
<<~JAVASCRIPT
|
290
|
+
#{puppeteer_require}
|
291
|
+
var BrowserType = '#{@browser_type.to_s}';
|
292
|
+
var LastResult = null;
|
293
|
+
var LastErr = null;
|
294
|
+
var LastExecutionFinished = false;
|
295
|
+
var LastHandleId = 0;
|
296
|
+
|
297
|
+
var AllPageHandles = {};
|
298
|
+
var AllElementHandles = {};
|
299
|
+
|
300
|
+
var CurrentBrowser = null;
|
301
|
+
var ConsoleMessages = {};
|
302
|
+
|
303
|
+
var ModalText = null;
|
304
|
+
var ModalTextMatched = false;
|
305
|
+
|
306
|
+
const GetLastResult = function() {
|
307
|
+
if (LastExecutionFinished === true) {
|
308
|
+
var err = LastErr;
|
309
|
+
var res = LastResult;
|
310
|
+
|
311
|
+
LastErr = null;
|
312
|
+
LastRes = null;
|
313
|
+
LastExecutionFinished = false;
|
314
|
+
|
315
|
+
if (err) { return [null, err.message]; }
|
316
|
+
else { return [res, null]; }
|
317
|
+
|
318
|
+
} else {
|
319
|
+
return [null, (new Error('Last command did not yet finish execution!')).message];
|
320
|
+
}
|
321
|
+
};
|
322
|
+
|
323
|
+
const DialogAcceptHandler = async (dialog) => {
|
324
|
+
var msg = dialog.message()
|
325
|
+
ModalTextMatched = (ModalText === msg);
|
326
|
+
ModalText = msg;
|
327
|
+
await dialog.accept();
|
328
|
+
}
|
329
|
+
|
330
|
+
const DialogDismissHandler = async (dialog) => {
|
331
|
+
var msg = dialog.message()
|
332
|
+
ModalTextMatched = (ModalText === msg);
|
333
|
+
ModalText = msg;
|
334
|
+
await dialog.dismiss();
|
335
|
+
}
|
336
|
+
|
337
|
+
const RegisterElementHandle = function(element_handle) {
|
338
|
+
var entries = Object.entries(AllElementHandles);
|
339
|
+
for(var i = 0; i < entries.length; i++) {
|
340
|
+
if (entries[i][1] === element_handle) { return entries[i][0]; }
|
341
|
+
}
|
342
|
+
LastHandleId++;
|
343
|
+
var handle_id = LastHandleId;
|
344
|
+
AllElementHandles[handle_id] = element_handle;
|
345
|
+
return handle_id;
|
346
|
+
};
|
347
|
+
|
348
|
+
const RegisterPage = function(page) {
|
349
|
+
var entries = Object.entries(AllPageHandles);
|
350
|
+
for(var i = 0; i < entries.length; i++) {
|
351
|
+
if (entries[i][1] === page) { return entries[i][0]; }
|
352
|
+
}
|
353
|
+
LastHandleId++;
|
354
|
+
var handle_id = LastHandleId;
|
355
|
+
AllPageHandles[handle_id] = page;
|
356
|
+
ConsoleMessages[handle_id] = [];
|
357
|
+
AllPageHandles[handle_id].on('console', (msg) => {
|
358
|
+
ConsoleMessages[handle_id].push({level: msg.type(), location: msg.location(), text: msg.text()});
|
359
|
+
});
|
360
|
+
AllPageHandles[handle_id].on('pageerror', (error) => {
|
361
|
+
ConsoleMessages[handle_id].push({level: 'error', location: '', text: error.message});
|
362
|
+
});
|
363
|
+
return handle_id;
|
364
|
+
};
|
365
|
+
|
366
|
+
(async () => {
|
367
|
+
try {
|
368
|
+
CurrentBrowser = #{launch_line}
|
369
|
+
var page = (await CurrentBrowser.pages())[0];
|
370
|
+
page.setDefaultTimeout(#{@puppeteer_timeout});
|
371
|
+
if (!(BrowserType === 'firefox')) {
|
372
|
+
var target = page.target();
|
373
|
+
var cdp_session = await target.createCDPSession();
|
374
|
+
await cdp_session.send('Page.setDownloadBehavior', {behavior: 'allow', downloadPath: '#{Isomorfeus::Puppetmaster.save_path}'});
|
375
|
+
if (#{@url_blacklist}.length > 0) { await cdp_session.send('Network.setBlockedURLs', {urls: #{@url_blacklist}}); }
|
376
|
+
await cdp_session.detach();
|
377
|
+
}
|
378
|
+
LastResult = RegisterPage(page);
|
379
|
+
LastExecutionFinished = true;
|
380
|
+
} catch (err) {
|
381
|
+
LastErr = err;
|
382
|
+
LastExecutionFinished = true;
|
383
|
+
}
|
384
|
+
})();
|
385
|
+
JAVASCRIPT
|
386
|
+
end
|
387
|
+
|
388
|
+
def session
|
389
|
+
@session
|
390
|
+
end
|
391
|
+
|
392
|
+
def timed_out?(start_time)
|
393
|
+
if (Time.now - start_time) > @timeout
|
394
|
+
raise "Command Execution timed out!"
|
395
|
+
end
|
396
|
+
false
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
@@ -0,0 +1,944 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Puppetmaster
|
3
|
+
module Driver
|
4
|
+
module PuppeteerDocument
|
5
|
+
def document_accept_alert(document, **options, &block)
|
6
|
+
# TODO maybe wrap in mutex
|
7
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
8
|
+
@context.exec <<~JAVASCRIPT
|
9
|
+
ModalText = #{text};
|
10
|
+
AllPageHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
11
|
+
JAVASCRIPT
|
12
|
+
block.call
|
13
|
+
sleep @reaction_timeout
|
14
|
+
@context.eval 'ModalText'
|
15
|
+
ensure
|
16
|
+
matched = await <<~JAVASCRIPT
|
17
|
+
LastResult = ModalTextMatched;
|
18
|
+
ModalTextMatched = false;
|
19
|
+
ModalText = null;
|
20
|
+
AllPageHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
21
|
+
JAVASCRIPT
|
22
|
+
raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
23
|
+
end
|
24
|
+
|
25
|
+
def document_accept_confirm(document, **options, &block)
|
26
|
+
# TODO maybe wrap in mutex
|
27
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
28
|
+
@context.exec <<~JAVASCRIPT
|
29
|
+
ModalText = #{text};
|
30
|
+
AllPageHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
31
|
+
JAVASCRIPT
|
32
|
+
block.call
|
33
|
+
sleep @reaction_timeout
|
34
|
+
@context.eval 'ModalText'
|
35
|
+
ensure
|
36
|
+
matched = await <<~JAVASCRIPT
|
37
|
+
LastResult = ModalTextMatched;
|
38
|
+
ModalTextMatched = false;
|
39
|
+
ModalText = null;
|
40
|
+
AllPageHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
41
|
+
JAVASCRIPT
|
42
|
+
raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
43
|
+
end
|
44
|
+
|
45
|
+
def document_accept_leave_page(document, **options, &block)
|
46
|
+
# TODO maybe wrap in mutex
|
47
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
48
|
+
@context.exec <<~JAVASCRIPT
|
49
|
+
ModalText = #{text};
|
50
|
+
AllPageHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
51
|
+
JAVASCRIPT
|
52
|
+
block.call
|
53
|
+
sleep @reaction_timeout
|
54
|
+
@context.eval 'ModalText'
|
55
|
+
ensure
|
56
|
+
matched = await <<~JAVASCRIPT
|
57
|
+
LastResult = ModalTextMatched;
|
58
|
+
ModalTextMatched = false;
|
59
|
+
ModalText = null;
|
60
|
+
AllPageHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
61
|
+
JAVASCRIPT
|
62
|
+
raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
63
|
+
end
|
64
|
+
|
65
|
+
def document_accept_prompt(document, **options, &block)
|
66
|
+
# TODO maybe wrap in mutex
|
67
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
68
|
+
@context.exec <<~JAVASCRIPT
|
69
|
+
ModalText = #{text};
|
70
|
+
AllPageHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
71
|
+
JAVASCRIPT
|
72
|
+
block.call
|
73
|
+
sleep @reaction_timeout
|
74
|
+
@context.eval 'ModalText'
|
75
|
+
ensure
|
76
|
+
matched = await <<~JAVASCRIPT
|
77
|
+
LastResult = ModalTextMatched;
|
78
|
+
ModalTextMatched = false;
|
79
|
+
ModalText = null;
|
80
|
+
AllPageHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
81
|
+
JAVASCRIPT
|
82
|
+
raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
83
|
+
end
|
84
|
+
|
85
|
+
def document_all_text(document)
|
86
|
+
await("LastResult = AllPageHandles[#{document.handle}].evaluate(function(){ return document.documentElement.textContent; });")
|
87
|
+
end
|
88
|
+
|
89
|
+
def document_body(document)
|
90
|
+
node_data = await <<~JAVASCRIPT
|
91
|
+
var element_handle = await AllPageHandles[#{document.handle}].$('body');
|
92
|
+
if (element_handle) {
|
93
|
+
var node_handle = RegisterElementHandle(element_handle);
|
94
|
+
var tt = await AllElementHandles[node_handle].executionContext().evaluate((node) => {
|
95
|
+
var tag = node.tagName.toLowerCase();
|
96
|
+
return [tag, null, node.isContentEditable];
|
97
|
+
}, AllElementHandles[node_handle]);
|
98
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]};
|
99
|
+
}
|
100
|
+
JAVASCRIPT
|
101
|
+
if node_data
|
102
|
+
node_data[:css_selector] = 'body'
|
103
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def document_bring_to_front(document)
|
108
|
+
await("await AllPageHandles[#{document.handle}].bringToFront();")
|
109
|
+
end
|
110
|
+
|
111
|
+
def document_clear_authentication_credentials(document)
|
112
|
+
await("AllPageHandles[#{document.handle}].authenticate(null);")
|
113
|
+
end
|
114
|
+
|
115
|
+
def document_clear_cookies(document)
|
116
|
+
await <<~JAVASCRIPT
|
117
|
+
var cookies = await AllPageHandles[#{document.handle}].cookies();
|
118
|
+
cookies.forEach(async(cookie) => {await AllPageHandles[#{document.handle}].deleteCookie(cookie);});
|
119
|
+
JAVASCRIPT
|
120
|
+
end
|
121
|
+
|
122
|
+
def document_clear_extra_headers(document)
|
123
|
+
await ("AllPageHandles[#{document.handle}].setExtraHTTPHeaders({});")
|
124
|
+
end
|
125
|
+
|
126
|
+
def document_clear_url_blacklist(document)
|
127
|
+
await <<~JAVASCRIPT
|
128
|
+
if (!(BrowserType === 'firefox')) {
|
129
|
+
var cdp_session = await AllPageHandles[#{document.handle}].target().createCDPSession();
|
130
|
+
await cdp_session.send('Network.setBlockedURLs', {urls: []});
|
131
|
+
await cdp_session.detach();
|
132
|
+
}
|
133
|
+
JAVASCRIPT
|
134
|
+
end
|
135
|
+
|
136
|
+
def document_click(document, x: nil, y: nil, modifiers: nil)
|
137
|
+
# modifier_keys: :alt, :control, :meta, :shift
|
138
|
+
# await "await AllPageHandles[#{document.handle}].mouse.click(#{x},#{y},{button: 'left'});"
|
139
|
+
# raise Isomorfeus::Pupppetmaster::InvalidActionError.new(:click) unless visible?
|
140
|
+
modifiers = [modifiers] if modifiers.is_a?(Symbol)
|
141
|
+
modifiers = [] unless modifiers
|
142
|
+
modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
|
143
|
+
await <<~JAVASCRIPT
|
144
|
+
var response_event_occurred = false;
|
145
|
+
var response_handler = function(event){ response_event_occurred = true; };
|
146
|
+
var response_watcher = new Promise(function(resolve, reject){
|
147
|
+
setTimeout(function(){
|
148
|
+
if (!response_event_occurred) { resolve(true); }
|
149
|
+
else { setTimeout(function(){ resolve(true); }, #{@puppeteer_timeout}); }
|
150
|
+
AllPageHandles[#{document.handle}].removeListener('response', response_handler);
|
151
|
+
}, #{@puppeteer_reaction_timeout});
|
152
|
+
});
|
153
|
+
AllPageHandles[#{document.handle}].on('response', response_handler);
|
154
|
+
var navigation_watcher;
|
155
|
+
if (BrowserType === 'firefox') {
|
156
|
+
navigation_watcher = AllPageHandles[#{document.handle}].waitFor(1000);
|
157
|
+
} else {
|
158
|
+
navigation_watcher = AllPageHandles[#{document.handle}].waitForNavigation();
|
159
|
+
}
|
160
|
+
await AllPageHandles[#{document.handle}].evaluate(function(){
|
161
|
+
var options = {button: 0, bubbles: true, cancelable: true};
|
162
|
+
var node = window;
|
163
|
+
var x = #{x ? x : 'null'};
|
164
|
+
var y = #{y ? y : 'null'};
|
165
|
+
var modifiers = #{modifiers};
|
166
|
+
if (x && y) {
|
167
|
+
options['clientX'] = x;
|
168
|
+
options['clientY'] = y;
|
169
|
+
var n = document.elementFromPoint(x, y);
|
170
|
+
if (n) { node = n };
|
171
|
+
}
|
172
|
+
if (modifiers.includes('meta')) { options['metaKey'] = true; }
|
173
|
+
if (modifiers.includes('control')) { options['ctrlKey'] = true; }
|
174
|
+
if (modifiers.includes('shift')) { options['shiftKey'] = true; }
|
175
|
+
if (modifiers.includes('alt')) { options['altKey'] = true; }
|
176
|
+
node.dispatchEvent(new MouseEvent('mousedown', options));
|
177
|
+
node.dispatchEvent(new MouseEvent('mouseup', options));
|
178
|
+
node.dispatchEvent(new MouseEvent('click', options));
|
179
|
+
});
|
180
|
+
await Promise.race([response_watcher, navigation_watcher]);
|
181
|
+
JAVASCRIPT
|
182
|
+
end
|
183
|
+
|
184
|
+
def document_close(document)
|
185
|
+
await <<~JAVASCRIPT
|
186
|
+
await AllPageHandles[#{document.handle}].close();
|
187
|
+
delete AllPageHandles[#{document.handle}];
|
188
|
+
delete ConsoleMessages[#{document.handle}];
|
189
|
+
JAVASCRIPT
|
190
|
+
end
|
191
|
+
|
192
|
+
def document_console(document)
|
193
|
+
messages = @context.exec "return ConsoleMessages[#{document.handle}]"
|
194
|
+
messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
195
|
+
end
|
196
|
+
|
197
|
+
def document_cookies(document)
|
198
|
+
result = await("LastResult = await AllPageHandles[#{document.handle}].cookies();")
|
199
|
+
result_hash = {}
|
200
|
+
result.each { |cookie| result_hash[cookie['name']] = Isomorfeus::Puppetmaster::Cookie.new(cookie) }
|
201
|
+
result_hash
|
202
|
+
end
|
203
|
+
|
204
|
+
def document_dismiss_confirm(document, **options, &block)
|
205
|
+
# TODO
|
206
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
207
|
+
@context.exec <<~JAVASCRIPT
|
208
|
+
ModalText = #{text};
|
209
|
+
AllPageHandles[#{document.handle}].on('dialog', DialogDismissHandler);
|
210
|
+
JAVASCRIPT
|
211
|
+
block.call
|
212
|
+
sleep @reaction_timeout
|
213
|
+
@context.eval 'ModalText'
|
214
|
+
ensure
|
215
|
+
matched = await <<~JAVASCRIPT
|
216
|
+
LastResult = ModalTextMatched;
|
217
|
+
ModalTextMatched = false;
|
218
|
+
ModalText = null;
|
219
|
+
AllPageHandles[#{document.handle}].removeListener('dialog', DialogDismissHandler);
|
220
|
+
JAVASCRIPT
|
221
|
+
raise Isomorfeus::Puppetmaster::ModalNotFound if options.has_key?(:text) && !matched
|
222
|
+
end
|
223
|
+
|
224
|
+
def document_dismiss_leave_page(document, **options, &block)
|
225
|
+
# TODO
|
226
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
227
|
+
@context.exec <<~JAVASCRIPT
|
228
|
+
ModalText = #{text};
|
229
|
+
AllPageHandles[#{document.handle}].on('dialog', DialogDismissHandler);
|
230
|
+
JAVASCRIPT
|
231
|
+
block.call
|
232
|
+
sleep @reaction_timeout
|
233
|
+
@context.eval 'ModalText'
|
234
|
+
ensure
|
235
|
+
matched = await <<~JAVASCRIPT
|
236
|
+
LastResult = ModalTextMatched;
|
237
|
+
ModalTextMatched = false;
|
238
|
+
ModalText = null;
|
239
|
+
AllPageHandles[#{document.handle}].removeListener('dialog', DialogDismissHandler);
|
240
|
+
JAVASCRIPT
|
241
|
+
raise Isomorfeus::Puppetmaster::ModalNotFound if options.has_key?(:text) && !matched
|
242
|
+
end
|
243
|
+
|
244
|
+
def document_dismiss_prompt(document, **options, &block)
|
245
|
+
# TODO
|
246
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
247
|
+
@context.exec <<~JAVASCRIPT
|
248
|
+
ModalText = #{text};
|
249
|
+
AllPageHandles[#{document.handle}].on('dialog', DialogDismissHandler);
|
250
|
+
JAVASCRIPT
|
251
|
+
block.call
|
252
|
+
sleep @reaction_timeout
|
253
|
+
@context.eval 'ModalText'
|
254
|
+
ensure
|
255
|
+
matched = await <<~JAVASCRIPT
|
256
|
+
LastResult = ModalTextMatched;
|
257
|
+
ModalTextMatched = false;
|
258
|
+
ModalText = null;
|
259
|
+
AllPageHandles[#{document.handle}].removeListener('dialog', DialogDismissHandler);
|
260
|
+
JAVASCRIPT
|
261
|
+
raise Isomorfeus::Puppetmaster::ModalNotFound if options.has_key?(:text) && !matched
|
262
|
+
end
|
263
|
+
|
264
|
+
def document_dispatch_event(document, name, event_type = nil, **options)
|
265
|
+
raise ArgumentError, 'Unknown event' unless EVENTS.key?(name.to_sym) || event_type
|
266
|
+
event_type, opts = *EVENTS[name.to_s.downcase.tr('_', '').to_sym] if event_type.nil?
|
267
|
+
opts.merge!(options)
|
268
|
+
await <<~JAVASCRIPT
|
269
|
+
handle = await AllPageHandles[#{document.handle}].evaluate(function(node){
|
270
|
+
var event = new #{event_type}('#{name}'#{opts.empty? ? '' : opts});
|
271
|
+
document.dispatchEvent(event);
|
272
|
+
});
|
273
|
+
JAVASCRIPT
|
274
|
+
end
|
275
|
+
|
276
|
+
def document_double_click(document, x: nil, y: nil, modifiers: nil)
|
277
|
+
# modifier_keys: :alt, :control, :meta, :shift
|
278
|
+
modifiers = [modifiers] if modifiers.is_a?(Symbol)
|
279
|
+
modifiers = [] unless modifiers
|
280
|
+
modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
|
281
|
+
await <<~JAVASCRIPT
|
282
|
+
var response_event_occurred = false;
|
283
|
+
var response_handler = function(event){ response_event_occurred = true; };
|
284
|
+
var response_watcher = new Promise(function(resolve, reject){
|
285
|
+
setTimeout(function(){
|
286
|
+
if (!response_event_occurred) { resolve(true); }
|
287
|
+
else { setTimeout(function(){ resolve(true); }, #{@puppeteer_timeout}); }
|
288
|
+
AllPageHandles[#{document.handle}].removeListener('response', response_handler);
|
289
|
+
}, #{@puppeteer_reaction_timeout});
|
290
|
+
});
|
291
|
+
AllPageHandlers[#{document.handle}].on('response', response_handler);
|
292
|
+
var navigation_watcher;
|
293
|
+
if (BrowserType === 'firefox') {
|
294
|
+
navigation_watcher = AllPageHandles[#{document.handle}].waitFor(1000);
|
295
|
+
} else {
|
296
|
+
navigation_watcher = AllPageHandles[#{document.handle}].waitForNavigation();
|
297
|
+
}
|
298
|
+
await AllPageHandles[#{document.handle}].evaluate(function(){
|
299
|
+
var options = {button: 0, bubbles: true, cancelable: true};
|
300
|
+
var node = window;
|
301
|
+
var x = #{x ? x : 'null'};
|
302
|
+
var y = #{y ? y : 'null'};
|
303
|
+
var modifiers = #{modifiers};
|
304
|
+
if (x && y) {
|
305
|
+
options['clientX'] = x;
|
306
|
+
options['clientY'] = y;
|
307
|
+
var n = document.elementFromPoint(x, y);
|
308
|
+
if (n) { node = n };
|
309
|
+
}
|
310
|
+
if (modifiers.includes('meta')) { options['metaKey'] = true; }
|
311
|
+
if (modifiers.includes('control')) { options['ctrlKey'] = true; }
|
312
|
+
if (modifiers.includes('shift')) { options['shiftKey'] = true; }
|
313
|
+
if (modifiers.includes('alt')) { options['altKey'] = true; }
|
314
|
+
node.dispatchEvent(new MouseEvent('mousedown', options));
|
315
|
+
node.dispatchEvent(new MouseEvent('mouseup', options));
|
316
|
+
node.dispatchEvent(new MouseEvent('dblclick', options));
|
317
|
+
});
|
318
|
+
await Promise.race([response_watcher, navigation_watcher]);
|
319
|
+
JAVASCRIPT
|
320
|
+
end
|
321
|
+
|
322
|
+
def document_evaluate_script(document, script, *args)
|
323
|
+
await <<~JAVASCRIPT
|
324
|
+
LastResult = await AllPageHandles[#{document.handle}].evaluate((arguments) => {
|
325
|
+
return #{script}
|
326
|
+
}, #{args});
|
327
|
+
JAVASCRIPT
|
328
|
+
end
|
329
|
+
|
330
|
+
def document_execute_script(document, script, *args)
|
331
|
+
await <<~JAVASCRIPT
|
332
|
+
LastResult = await AllPageHandles[#{document.handle}].evaluate((arguments) => {
|
333
|
+
#{script}
|
334
|
+
}, #{args});
|
335
|
+
JAVASCRIPT
|
336
|
+
end
|
337
|
+
|
338
|
+
def document_find(document, selector)
|
339
|
+
js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
|
340
|
+
node_data = await <<~JAVASCRIPT
|
341
|
+
var element_handle = await AllPageHandles[#{document.handle}].$("#{js_escaped_selector}");
|
342
|
+
if (element_handle) {
|
343
|
+
var node_handle = RegisterElementHandle(element_handle);
|
344
|
+
var tt = await AllPageHandles[#{document.handle}].evaluate((node) => {
|
345
|
+
var tag = node.tagName.toLowerCase();
|
346
|
+
var type = null;
|
347
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
348
|
+
return [tag, type, node.isContentEditable];
|
349
|
+
}, AllElementHandles[node_handle]);
|
350
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]};
|
351
|
+
}
|
352
|
+
JAVASCRIPT
|
353
|
+
if node_data
|
354
|
+
node_data[:css_selector] = selector
|
355
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
356
|
+
else
|
357
|
+
raise Isomorfeus::Puppetmaster::ElementNotFound.new(selector)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def document_find_all(document, selector)
|
362
|
+
js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
|
363
|
+
node_data_array = await <<~JAVASCRIPT
|
364
|
+
var node_data_array = [];
|
365
|
+
var element_handle_array = await AllPageHandles[#{document.handle}].$$("#{js_escaped_selector}");
|
366
|
+
if (element_handle_array) {
|
367
|
+
for (var i=0; i<element_handle_array.length; i++) {
|
368
|
+
var node_handle = RegisterElementHandle(element_handle_array[i]);
|
369
|
+
var tt = await AllPageHandles[#{document.handle}].evaluate((node) => {
|
370
|
+
var tag = node.tagName.toLowerCase();
|
371
|
+
var type = null;
|
372
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
373
|
+
return [tag, type, node.isContentEditable];
|
374
|
+
}, AllElementHandles[node_handle]);
|
375
|
+
node_data_array.push({handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]});
|
376
|
+
}
|
377
|
+
}
|
378
|
+
LastResult = node_data_array;
|
379
|
+
JAVASCRIPT
|
380
|
+
node_data_array.map do |node_data|
|
381
|
+
node_data[:css_selector] = selector
|
382
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def document_find_all_xpath(document, query)
|
387
|
+
js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
|
388
|
+
node_data_array = await <<~JAVASCRIPT
|
389
|
+
var node_data_array = [];
|
390
|
+
var element_handle_array = await AllPageHandles[#{document.handle}].$x("#{js_escaped_query}");
|
391
|
+
if (element_handle_array) {
|
392
|
+
for (var i=0; i<element_handle_array.length; i++) {
|
393
|
+
var node_handle = RegisterElementHandle(element_handle_array[i]);
|
394
|
+
var tt = await AllPageHandles[#{document.handle}].evaluate((node) => {
|
395
|
+
var tag = node.tagName.toLowerCase();
|
396
|
+
var type = null;
|
397
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
398
|
+
return [tag, type, node.isContentEditable];
|
399
|
+
}, AllElementHandles[node_handle]);
|
400
|
+
node_data_array.push({handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]});
|
401
|
+
}
|
402
|
+
}
|
403
|
+
LastResult = node_data_array;
|
404
|
+
JAVASCRIPT
|
405
|
+
node_data_array.map do |node_data|
|
406
|
+
node_data[:xpath_query] = query
|
407
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def document_find_xpath(document, query)
|
412
|
+
js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
|
413
|
+
node_data = await <<~JAVASCRIPT
|
414
|
+
var element_handle_array = await AllPageHandles[#{document.handle}].$x("#{js_escaped_query}");
|
415
|
+
var element_handle = (element_handle_array) ? element_handle_array[0] : null;
|
416
|
+
if (element_handle) {
|
417
|
+
var node_handle = RegisterElementHandle(element_handle);
|
418
|
+
var tt = await AllPageHandles[#{document.handle}].evaluate((node) => {
|
419
|
+
var tag = node.tagName.toLowerCase();
|
420
|
+
var type = null;
|
421
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
422
|
+
return [tag, type, node.isContentEditable];
|
423
|
+
}, AllElementHandles[node_handle]);
|
424
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]};
|
425
|
+
}
|
426
|
+
JAVASCRIPT
|
427
|
+
if node_data
|
428
|
+
node_data[:xpath_query] = query
|
429
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
430
|
+
else
|
431
|
+
raise Isomorfeus::Puppetmaster::ElementNotFound.new(query)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
def document_go_back(document)
|
436
|
+
response_hash, messages = await <<~JAVASCRIPT
|
437
|
+
ConsoleMessages[#{document.handle}] = [];
|
438
|
+
var response = await AllPageHandles[#{document.handle}].goBack();
|
439
|
+
if (response) {
|
440
|
+
var request = response.request();
|
441
|
+
var formatted_response = {
|
442
|
+
headers: response.headers(),
|
443
|
+
ok: response.ok(),
|
444
|
+
remote_address: response.remoteAddress(),
|
445
|
+
request: {
|
446
|
+
failure: request.failure(),
|
447
|
+
headers: request.headers(),
|
448
|
+
method: request.method(),
|
449
|
+
post_data: request.postData(),
|
450
|
+
resource_type: request.resourceType(),
|
451
|
+
url: request.url()
|
452
|
+
},
|
453
|
+
status: response.status(),
|
454
|
+
status_text: response.statusText(),
|
455
|
+
text: response.text(),
|
456
|
+
url: response.url()
|
457
|
+
};
|
458
|
+
LastResult = [formatted_response, ConsoleMessages[#{document.handle}]];
|
459
|
+
} else {
|
460
|
+
LastResult = [null, ConsoleMessages[#{document.handle}]];
|
461
|
+
}
|
462
|
+
JAVASCRIPT
|
463
|
+
con_messages = messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
464
|
+
con_messages.each { |m| raise determine_error(m.text) if m.level == 'error' && !m.text.start_with?('Failed to load resource:') }
|
465
|
+
if response_hash
|
466
|
+
response = Isomorfeus::Puppetmaster::Response.new(response_hash)
|
467
|
+
document.instance_variable_set(:@response, response)
|
468
|
+
end
|
469
|
+
document.response
|
470
|
+
end
|
471
|
+
|
472
|
+
def document_go_forward(document)
|
473
|
+
response_hash, messages = await <<~JAVASCRIPT
|
474
|
+
ConsoleMessages[#{document.handle}] = [];
|
475
|
+
var response = await AllPageHandles[#{document.handle}].goForward();
|
476
|
+
if (response) {
|
477
|
+
var request = response.request();
|
478
|
+
var formatted_response = {
|
479
|
+
headers: response.headers(),
|
480
|
+
ok: response.ok(),
|
481
|
+
remote_address: response.remoteAddress(),
|
482
|
+
request: {
|
483
|
+
failure: request.failure(),
|
484
|
+
headers: request.headers(),
|
485
|
+
method: request.method(),
|
486
|
+
post_data: request.postData(),
|
487
|
+
resource_type: request.resourceType(),
|
488
|
+
url: request.url()
|
489
|
+
},
|
490
|
+
status: response.status(),
|
491
|
+
status_text: response.statusText(),
|
492
|
+
text: response.text(),
|
493
|
+
url: response.url()
|
494
|
+
};
|
495
|
+
LastResult = [formatted_response, ConsoleMessages[#{document.handle}]];
|
496
|
+
} else {
|
497
|
+
LastResult = [null, ConsoleMessages[#{document.handle}]];
|
498
|
+
}
|
499
|
+
JAVASCRIPT
|
500
|
+
con_messages = messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
501
|
+
con_messages.each { |m| raise determine_error(m.text) if m.level == 'error' && !m.text.start_with?('Failed to load resource:') }
|
502
|
+
if response_hash
|
503
|
+
response = Isomorfeus::Puppetmaster::Response.new(response_hash)
|
504
|
+
document.instance_variable_set(:@response, response)
|
505
|
+
end
|
506
|
+
document.response
|
507
|
+
end
|
508
|
+
|
509
|
+
def document_goto(document, uri)
|
510
|
+
parsed_uri = URI.parse(uri)
|
511
|
+
parsed_uri.host = @app.host unless parsed_uri.host
|
512
|
+
parsed_uri.port = @app.port unless parsed_uri.port
|
513
|
+
parsed_uri.scheme = @app.scheme unless parsed_uri.scheme
|
514
|
+
response_hash, messages = await <<~JAVASCRIPT
|
515
|
+
ConsoleMessages[#{document.handle}] = [];
|
516
|
+
var response = await AllPageHandles[#{document.handle}].goto('#{parsed_uri.to_s}');
|
517
|
+
if (response) {
|
518
|
+
var request = response.request();
|
519
|
+
var formatted_response = {
|
520
|
+
headers: response.headers(),
|
521
|
+
ok: response.ok(),
|
522
|
+
remote_address: response.remoteAddress(),
|
523
|
+
request: {
|
524
|
+
failure: request.failure(),
|
525
|
+
headers: request.headers(),
|
526
|
+
method: request.method(),
|
527
|
+
post_data: request.postData(),
|
528
|
+
resource_type: request.resourceType(),
|
529
|
+
url: request.url()
|
530
|
+
},
|
531
|
+
status: response.status(),
|
532
|
+
status_text: response.statusText(),
|
533
|
+
text: response.text(),
|
534
|
+
url: response.url()
|
535
|
+
};
|
536
|
+
LastResult = [formatted_response, ConsoleMessages[#{document.handle}]];
|
537
|
+
} else {
|
538
|
+
LastResult = [null, ConsoleMessages[#{document.handle}]];
|
539
|
+
}
|
540
|
+
JAVASCRIPT
|
541
|
+
con_messages = messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
542
|
+
con_messages.each { |m| raise determine_error(m.text) if m.level == 'error' && !m.text.start_with?('Failed to load resource:') }
|
543
|
+
if response_hash
|
544
|
+
response = Isomorfeus::Puppetmaster::Response.new(response_hash)
|
545
|
+
document.instance_variable_set(:@response, response)
|
546
|
+
end
|
547
|
+
document.response
|
548
|
+
end
|
549
|
+
|
550
|
+
def document_head(document)
|
551
|
+
node_data = await <<~JAVASCRIPT
|
552
|
+
var element_handle = await AllPageHandles[#{document.handle}].$('head');
|
553
|
+
if (element_handle) {
|
554
|
+
var node_handle = RegisterElementHandle(element_handle);
|
555
|
+
var tt = await AllElementHandles[node_handle].executionContext().evaluate((node) => {
|
556
|
+
var tag = node.tagName.toLower();
|
557
|
+
return [tag, null, node.isContentEditable];
|
558
|
+
}, AllElementHandles[node_handle]);
|
559
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]};
|
560
|
+
}
|
561
|
+
JAVASCRIPT
|
562
|
+
if node_data
|
563
|
+
node_data[:css_selector] = selector
|
564
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
def document_html(document)
|
569
|
+
await "LastResult = await AllPageHandles[#{document.handle}].content();"
|
570
|
+
end
|
571
|
+
|
572
|
+
def document_open_new_document(_document, uri = nil)
|
573
|
+
if !uri || uri == 'about:blank'
|
574
|
+
parsed_uri = 'about:blank'
|
575
|
+
else
|
576
|
+
parsed_uri = URI.parse(uri)
|
577
|
+
parsed_uri.host = @app.host unless parsed_uri.host
|
578
|
+
parsed_uri.port = @app.port unless parsed_uri.port
|
579
|
+
parsed_uri.scheme = @app.scheme unless parsed_uri.scheme
|
580
|
+
end
|
581
|
+
handle, response_hash, messages = await <<~JAVASCRIPT
|
582
|
+
var new_page = await CurrentBrowser.newPage();
|
583
|
+
var url = '#{parsed_uri.to_s}';
|
584
|
+
new_page.setDefaultTimeout(#{@puppeteer_timeout});
|
585
|
+
await new_page.setViewport({width: #{@width}, height: #{@height}});
|
586
|
+
if (!(BrowserType === 'firefox')) {
|
587
|
+
var new_target = new_page.target();
|
588
|
+
var cdp_session = await new_target.createCDPSession();
|
589
|
+
await cdp_session.send('Page.setDownloadBehavior', {behavior: 'allow', downloadPath: '#{Isomorfeus::Puppetmaster.save_path}'});
|
590
|
+
if (#{@url_blacklist}.length > 0) { await cdp_session.send('Network.setBlockedURLs', {urls: #{@url_blacklist}}); }
|
591
|
+
await cdp_session.detach();
|
592
|
+
}
|
593
|
+
var page_handle = RegisterPage(new_page);
|
594
|
+
var result_response = null;
|
595
|
+
if (url && url !== '') {
|
596
|
+
var response = await new_page.goto(url);
|
597
|
+
if (response) {
|
598
|
+
var request = response.request();
|
599
|
+
result_response = {
|
600
|
+
headers: response.headers(),
|
601
|
+
ok: response.ok(),
|
602
|
+
remote_address: response.remoteAddress(),
|
603
|
+
request: {
|
604
|
+
failure: request.failure(),
|
605
|
+
headers: request.headers(),
|
606
|
+
method: request.method(),
|
607
|
+
post_data: request.postData(),
|
608
|
+
resource_type: request.resourceType(),
|
609
|
+
url: request.url()
|
610
|
+
},
|
611
|
+
status: response.status(),
|
612
|
+
status_text: response.statusText(),
|
613
|
+
text: response.text(),
|
614
|
+
url: response.url()
|
615
|
+
}
|
616
|
+
}
|
617
|
+
};
|
618
|
+
LastResult = [page_handle, result_response, ConsoleMessages[page_handle]];
|
619
|
+
JAVASCRIPT
|
620
|
+
con_messages = messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
621
|
+
con_messages.each { |m| raise determine_error(m.text) if m.level == 'error' && !m.text.start_with?('Failed to load resource:') }
|
622
|
+
Isomorfeus::Puppetmaster::Document.new(self, handle, Isomorfeus::Puppetmaster::Response.new(response_hash))
|
623
|
+
end
|
624
|
+
|
625
|
+
def document_reload(document)
|
626
|
+
response_hash = await"LastResult = await AllPageHandles[#{document.handle}].reload();"
|
627
|
+
Isomorfeus::Puppetmaster::Response.new(response_hash)
|
628
|
+
end
|
629
|
+
|
630
|
+
def document_remove_cookie(document, name)
|
631
|
+
await "await AllPageHandles[#{document.handle}].deleteCookie({name: '#{name}'})"
|
632
|
+
end
|
633
|
+
|
634
|
+
def document_render_base64(document, **options)
|
635
|
+
# todo
|
636
|
+
# https://pptr.dev/#?product=Puppeteer&version=v1.12.2&show=api-pagescreenshotoptions
|
637
|
+
final_options = ["encoding: 'base64'"]
|
638
|
+
if options.has_key?(:format)
|
639
|
+
options[:format] = 'jpeg' if options[:format].to_s.downcase == 'jpg'
|
640
|
+
final_options << "type: '#{options.delete(:format).to_s.downcase}'"
|
641
|
+
end
|
642
|
+
final_options << "quality: #{options.delete(:quality)}" if options.has_key?(:quality)
|
643
|
+
final_options << "fullPage: #{options.delete(:full)}" if options.has_key?(:full)
|
644
|
+
options.each do |k,v|
|
645
|
+
final_options << "#{k.to_s.camelize(:lower)}: #{v}"
|
646
|
+
end
|
647
|
+
await "LastResult = await AllPageHandles[#{document.handle}].screenshot({#{final_options.join(', ')}});"
|
648
|
+
end
|
649
|
+
|
650
|
+
def document_reset_user_agent(document)
|
651
|
+
await <<~JAVASCRIPT
|
652
|
+
var original_user_agent = await CurrentBrowser.userAgent();
|
653
|
+
await AllPageHandles[#{document.handle}].setUserAgent(original_user_agent);
|
654
|
+
JAVASCRIPT
|
655
|
+
end
|
656
|
+
|
657
|
+
def document_right_click(document, x: nil, y: nil, modifiers: nil)
|
658
|
+
# modifier_keys: :alt, :control, :meta, :shift
|
659
|
+
# offset: { x: int, y: int }
|
660
|
+
modifiers = [modifiers] if modifiers.is_a?(Symbol)
|
661
|
+
modifiers = [] unless modifiers
|
662
|
+
modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
|
663
|
+
await <<~JAVASCRIPT
|
664
|
+
var response_event_occurred = false;
|
665
|
+
var response_handler = function(event){ response_event_occurred = true; };
|
666
|
+
var response_watcher = new Promise(function(resolve, reject){
|
667
|
+
setTimeout(function(){
|
668
|
+
if (!response_event_occurred) { resolve(true); }
|
669
|
+
else { setTimeout(function(){ resolve(true); }, #{@puppeteer_timeout}); }
|
670
|
+
AllPageHandles[#{document.handle}].removeListener('response', response_handler);
|
671
|
+
}, #{@puppeteer_reaction_timeout});
|
672
|
+
});
|
673
|
+
AllPageHandles[#{document.handle}].on('response', response_handler);
|
674
|
+
var navigation_watcher;
|
675
|
+
if (BrowserType === 'firefox') {
|
676
|
+
navigation_watcher = AllPageHandles[#{document.handle}].waitFor(1000);
|
677
|
+
} else {
|
678
|
+
navigation_watcher = AllPageHandles[#{document.handle}].waitForNavigation();
|
679
|
+
}
|
680
|
+
await AllPageHandles[#{document.handle}].evaluate(function(){
|
681
|
+
var options = {button: 2, bubbles: true, cancelable: true};
|
682
|
+
var node = window;
|
683
|
+
var x = #{x ? x : 'null'};
|
684
|
+
var y = #{y ? y : 'null'};
|
685
|
+
var modifiers = #{modifiers};
|
686
|
+
if (x && y) {
|
687
|
+
options['clientX'] = x;
|
688
|
+
options['clientY'] = y;
|
689
|
+
var n = document.elementFromPoint(x, y);
|
690
|
+
if (n) { node = n };
|
691
|
+
}
|
692
|
+
if (modifiers.includes('meta')) { options['metaKey'] = true; }
|
693
|
+
if (modifiers.includes('control')) { options['ctrlKey'] = true; }
|
694
|
+
if (modifiers.includes('shift')) { options['shiftKey'] = true; }
|
695
|
+
if (modifiers.includes('alt')) { options['altKey'] = true; }
|
696
|
+
node.dispatchEvent(new MouseEvent('mousedown', options));
|
697
|
+
node.dispatchEvent(new MouseEvent('mouseup', options));
|
698
|
+
node.dispatchEvent(new MouseEvent('contextmenu', options));
|
699
|
+
});
|
700
|
+
await Promise.race([response_watcher, navigation_watcher]);
|
701
|
+
JAVASCRIPT
|
702
|
+
end
|
703
|
+
|
704
|
+
def document_save_pdf(document, path, **options)
|
705
|
+
# todo
|
706
|
+
# https://pptr.dev/#?product=Puppeteer&version=v1.12.2&show=api-pagepdfoptions
|
707
|
+
absolute_path = File.absolute_path(path)
|
708
|
+
final_options = ["path: '#{absolute_path}'"]
|
709
|
+
final_options << "format: '#{options.delete(:format)}'" if options.has_key?(:format)
|
710
|
+
final_options << "headerTemplate: `#{options.delete(:header_template)}`" if options.has_key?(:header_template)
|
711
|
+
final_options << "footerTemplate: `#{options.delete(:footer_template)}`" if options.has_key?(:footer_template)
|
712
|
+
final_options << "pageRanges: '#{options.delete(:page_ranges)}'" if options.has_key?(:page_ranges)
|
713
|
+
final_options << "width: '#{options.delete(:width)}'" if options.has_key?(:width)
|
714
|
+
final_options << "height: '#{options.delete(:height)}'" if options.has_key?(:height)
|
715
|
+
options.each do |k,v|
|
716
|
+
final_options << "#{k.to_s.camelize(:lower)}: #{v}"
|
717
|
+
end
|
718
|
+
await "await AllPageHandles[#{document.handle}].pdf({#{final_options.join(', ')}});"
|
719
|
+
end
|
720
|
+
|
721
|
+
def document_save_screenshot(document, path, **options)
|
722
|
+
# todo
|
723
|
+
# https://pptr.dev/#?product=Puppeteer&version=v1.12.2&show=api-pagescreenshotoptions
|
724
|
+
absolute_path = File.absolute_path(path)
|
725
|
+
final_options = ["path: '#{absolute_path}'"]
|
726
|
+
if options.has_key?(:format)
|
727
|
+
options[:format] = 'jpeg' if options[:format].to_s.downcase == 'jpg'
|
728
|
+
final_options << "type: '#{options.delete(:format).to_s.downcase}'"
|
729
|
+
end
|
730
|
+
final_options << "quality: #{options.delete(:quality)}" if options.has_key?(:quality)
|
731
|
+
final_options << "fullPage: #{options.delete(:full)}" if options.has_key?(:full)
|
732
|
+
options.each do |k,v|
|
733
|
+
final_options << "#{k.to_s.camelize(:lower)}: #{v}"
|
734
|
+
end
|
735
|
+
await "await AllPageHandles[#{document.handle}].screenshot({#{final_options.join(', ')}});"
|
736
|
+
end
|
737
|
+
|
738
|
+
def document_scroll_by(document, x, y)
|
739
|
+
await "await AllPageHandles[#{document.handle}].evaluate('window.scrollBy(#{x}, #{y})');"
|
740
|
+
end
|
741
|
+
|
742
|
+
def document_scroll_to(document, x, y)
|
743
|
+
await "await AllPageHandles[#{document.handle}].evaluate('window.scrollTo(#{x}, #{y})');"
|
744
|
+
end
|
745
|
+
|
746
|
+
def document_set_authentication_credentials(document, username, password)
|
747
|
+
await "await AllPageHandles[#{document.handle}].authenticate({username: '#{username}', password: '#{password}'});"
|
748
|
+
end
|
749
|
+
|
750
|
+
def document_set_cookie(document, name, value, **options)
|
751
|
+
options[:name] ||= name
|
752
|
+
options[:value] ||= value
|
753
|
+
uri = document_url(document)
|
754
|
+
if uri == 'about:blank'
|
755
|
+
uri = if Isomorfeus::Puppetmaster.server_host
|
756
|
+
u = URI.new
|
757
|
+
u.scheme = Isomorfeus::Puppetmaster.server_scheme if Isomorfeus::Puppetmaster.server_scheme
|
758
|
+
u.host = Isomorfeus::Puppetmaster.server_host
|
759
|
+
u.to_s
|
760
|
+
else
|
761
|
+
'http://127.0.0.1'
|
762
|
+
end
|
763
|
+
end
|
764
|
+
options[:domain] ||= URI.parse(uri).host
|
765
|
+
final_options = []
|
766
|
+
final_options << "expires: #{options.delete(:expires).to_i}" if options.has_key?(:expires)
|
767
|
+
final_options << "httpOnly: #{options.delete(:http_only)}" if options.has_key?(:http_only)
|
768
|
+
final_options << "secure: #{options.delete(:secure)}" if options.has_key?(:secure)
|
769
|
+
final_options << "sameSite: '#{options.delete(:same_site)}'" if options.has_key?(:same_site)
|
770
|
+
options.each do |k,v|
|
771
|
+
final_options << "#{k}: '#{v}'"
|
772
|
+
end
|
773
|
+
await "await AllPageHandles[#{document.handle}].setCookie({#{final_options.join(', ')}});"
|
774
|
+
end
|
775
|
+
|
776
|
+
def document_set_extra_headers(document, headers_hash)
|
777
|
+
await "await AllPageHandles[#{document.handle}].setExtraHTTPHeaders({#{headers_hash.map { |k, v| "'#{k}': '#{v}'" }.join(', ')}});"
|
778
|
+
end
|
779
|
+
|
780
|
+
def document_set_url_blacklist(document, url_array)
|
781
|
+
# https://www.chromium.org/administrators/url-blacklist-filter-format
|
782
|
+
@url_blacklist = url_array
|
783
|
+
await <<~JAVASCRIPT
|
784
|
+
if (!(BrowserType === 'firefox')) {
|
785
|
+
var cdp_session = await AllPageHandles[#{document.handle}].target().createCDPSession();
|
786
|
+
await cdp_session.send('Network.setBlockedURLs', {urls: #{url_array}});
|
787
|
+
await cdp_session.detach();
|
788
|
+
}
|
789
|
+
JAVASCRIPT
|
790
|
+
end
|
791
|
+
|
792
|
+
def document_set_user_agent(document, agent_string)
|
793
|
+
await "await AllPageHandles[#{document.handle}].setUserAgent('#{agent_string}');"
|
794
|
+
end
|
795
|
+
|
796
|
+
def document_title(document)
|
797
|
+
await "LastResult = await AllPageHandles[#{document.handle}].title();"
|
798
|
+
end
|
799
|
+
|
800
|
+
def document_type_keys(document, *keys)
|
801
|
+
cjs = "await AllPageHandles[#{document.handle}].bringToFront();"
|
802
|
+
top_modifiers = []
|
803
|
+
keys.each do |key|
|
804
|
+
if key.is_a?(String)
|
805
|
+
key.each_char do |c|
|
806
|
+
need_shift = /[[:upper:]]/.match(c)
|
807
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.down('Shift');\n" if need_shift
|
808
|
+
c = "Key#{c.upcase}" if /[[:alpha:]]/.match(c)
|
809
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.down('#{c}');\n"
|
810
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.up('#{c}');\n"
|
811
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.up('Shift');\n" if need_shift
|
812
|
+
end
|
813
|
+
elsif key.is_a?(Symbol)
|
814
|
+
if %i[ctrl Ctrl].include?(key)
|
815
|
+
key = :control
|
816
|
+
elsif %i[command Command Meta].include?(key)
|
817
|
+
key = :meta
|
818
|
+
elsif %i[divide Divide].include?(key)
|
819
|
+
key = :numpad_divide
|
820
|
+
elsif %i[decimal Decimal].include?(key)
|
821
|
+
key = :numpad_decimal
|
822
|
+
elsif %i[left right up down].include?(key)
|
823
|
+
key = "arrow_#{key}".to_sym
|
824
|
+
end
|
825
|
+
if %i[alt alt_left alt_right control control_left control_rigth meta meta_left meta_right shift shift_left shift_right].include?(key)
|
826
|
+
top_modifiers << key
|
827
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.down('#{key.to_s.camelize}');\n"
|
828
|
+
else
|
829
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.press('#{key.to_s.camelize}');\n"
|
830
|
+
end
|
831
|
+
elsif key.is_a?(Array)
|
832
|
+
modifiers = []
|
833
|
+
key.each do |k|
|
834
|
+
if k.is_a?(Symbol)
|
835
|
+
if %i[ctrl Ctrl].include?(k)
|
836
|
+
k = :control
|
837
|
+
elsif %i[command Command Meta].include?(k)
|
838
|
+
k = :meta
|
839
|
+
elsif %i[divide Divide].include?(k)
|
840
|
+
k = :numpad_divide
|
841
|
+
elsif %i[decimal Decimal].include?(k)
|
842
|
+
k = :numpad_decimal
|
843
|
+
elsif %i[left right up down].include?(key)
|
844
|
+
k = "arrow_#{key}".to_sym
|
845
|
+
end
|
846
|
+
if %i[alt alt_left alt_right control control_left control_rigth meta meta_left meta_right shift shift_left shift_right].include?(k)
|
847
|
+
modifiers << k
|
848
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.down('#{k.to_s.camelize}');\n"
|
849
|
+
else
|
850
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.press('#{k.to_s.camelize}');\n"
|
851
|
+
end
|
852
|
+
elsif k.is_a?(String)
|
853
|
+
k.each_char do |c|
|
854
|
+
need_shift = /[[:upper:]]/.match(c)
|
855
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.down('Shift');\n" if need_shift
|
856
|
+
c = "Key#{c.upcase}" if /[[:alpha:]]/.match(c)
|
857
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.press('#{c}');\n"
|
858
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.up('Shift');\n" if need_shift
|
859
|
+
end
|
860
|
+
end
|
861
|
+
end
|
862
|
+
modifiers.reverse.each do |k|
|
863
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.up('#{k.to_s.camelize}');\n"
|
864
|
+
end
|
865
|
+
end
|
866
|
+
end
|
867
|
+
top_modifiers.reverse.each do |key|
|
868
|
+
cjs << "await AllPageHandles[#{document.handle}].keyboard.up('#{key.to_s.camelize}');\n"
|
869
|
+
end
|
870
|
+
await(cjs)
|
871
|
+
end
|
872
|
+
|
873
|
+
def document_url(document)
|
874
|
+
await "LastResult = await AllPageHandles[#{document.handle}].evaluate('window.location.href');"
|
875
|
+
end
|
876
|
+
|
877
|
+
def document_user_agent(document)
|
878
|
+
await "LastResult = await AllPageHandles[#{document.handle}].evaluate('window.navigator.userAgent');"
|
879
|
+
end
|
880
|
+
|
881
|
+
def document_viewport_maximize(document)
|
882
|
+
document_viewport_resize(document, @max_width, @max_height)
|
883
|
+
end
|
884
|
+
|
885
|
+
def document_viewport_resize(document, width, height)
|
886
|
+
width = @max_width if width > @max_width
|
887
|
+
height = @max_height if width > @max_height
|
888
|
+
await "await AllPageHandles[#{document.handle}].setViewport({width: #{width}, height: #{height}});"
|
889
|
+
end
|
890
|
+
|
891
|
+
def document_viewport_size(document)
|
892
|
+
viewport = @context.eval "AllPageHandles[#{document.handle}].viewport()"
|
893
|
+
[viewport['width'], viewport['height']]
|
894
|
+
end
|
895
|
+
|
896
|
+
def document_wait_for(document, selector)
|
897
|
+
js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
|
898
|
+
node_data = await <<~JAVASCRIPT
|
899
|
+
var element_handle = await AllPageHandles[#{document.handle}].waitForSelector("#{js_escaped_selector}");
|
900
|
+
if (element_handle) {
|
901
|
+
var node_handle = RegisterElementHandle(element_handle);
|
902
|
+
var tt = await AllElementHandles[node_handle].executionContext().evaluate((node) => {
|
903
|
+
var tag = node.tagName.toLowerCase();
|
904
|
+
var type = null;
|
905
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
906
|
+
return [tag, type, node.isContentEditable];
|
907
|
+
}, AllElementHandles[node_handle]);
|
908
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]};
|
909
|
+
}
|
910
|
+
JAVASCRIPT
|
911
|
+
if node_data
|
912
|
+
node_data[:css_selector] = selector
|
913
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
914
|
+
else
|
915
|
+
raise Isomorfeus::Puppetmaster::ElementNotFound.new(selector)
|
916
|
+
end
|
917
|
+
end
|
918
|
+
|
919
|
+
def document_wait_for_xpath(document, query)
|
920
|
+
js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
|
921
|
+
node_data = await <<~JAVASCRIPT
|
922
|
+
var element_handle = await AllPageHandles[#{document.handle}].waitForXPath("#{js_escaped_query}");
|
923
|
+
if (element_handle) {
|
924
|
+
var node_handle = RegisterElementHandle(element_handle);
|
925
|
+
var tt = await AllElementHandles[node_handle].executionContext().evaluate((node) => {
|
926
|
+
var tag = node.tagName.toLowerCase();
|
927
|
+
var type = null;
|
928
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
929
|
+
return [tag, type, node.isContentEditable];
|
930
|
+
}, AllElementHandles[node_handle]);
|
931
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1], content_editable: tt[2]};
|
932
|
+
}
|
933
|
+
JAVASCRIPT
|
934
|
+
if node_data
|
935
|
+
node_data[:xpath_query] = query
|
936
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
937
|
+
else
|
938
|
+
raise Isomorfeus::Puppetmaster::ElementNotFound.new(selector)
|
939
|
+
end
|
940
|
+
end
|
941
|
+
end
|
942
|
+
end
|
943
|
+
end
|
944
|
+
end
|