isomorfeus-puppetmaster 0.1.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 +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
|