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,370 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Puppetmaster
|
3
|
+
module Driver
|
4
|
+
class Jsdom
|
5
|
+
include Isomorfeus::Puppetmaster::Driver::JsdomDocument
|
6
|
+
include Isomorfeus::Puppetmaster::Driver::JsdomNode
|
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 :default_document
|
36
|
+
|
37
|
+
def initialize(options = {})
|
38
|
+
@app = options.delete(:app)
|
39
|
+
@options = options.dup
|
40
|
+
@ignore_https_errors = !!@options.delete(:ignore_https_errors)
|
41
|
+
@max_width = @options.delete(:max_width) { VIEWPORT_MAX_WIDTH }
|
42
|
+
@max_height = @options.delete(:max_height) { VIEWPORT_MAX_HEIGHT }
|
43
|
+
@width = @options.delete(:width) { VIEWPORT_DEFAULT_WIDTH > @max_width ? @max_width : VIEWPORT_DEFAULT_WIDTH }
|
44
|
+
@height = @options.delete(:height) { VIEWPORT_DEFAULT_HEIGHT > @max_height ? @max_height : VIEWPORT_DEFAULT_HEIGHT }
|
45
|
+
@timeout = @options.delete(:timeout) { TIMEOUT }
|
46
|
+
@max_wait = @options.delete(:max_wait) { @timeout + 1 }
|
47
|
+
@reaction_timeout = @options.delete(:reaction_timeout) { REACTION_TIMEOUT }
|
48
|
+
@jsdom_timeout = @timeout * 1000
|
49
|
+
@jsdom_reaction_timeout = @reaction_timeout * 1000
|
50
|
+
@url_blacklist = @options.delete(:url_blacklist) { [] }
|
51
|
+
@context = ExecJS.permissive_compile(jsdom_launch)
|
52
|
+
page_handle, @browser = await_result
|
53
|
+
@default_document = Isomorfeus::Puppetmaster::Document.new(self, page_handle, Isomorfeus::Puppetmaster::Response.new('status' => 200))
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.document_handle_disposer(driver, handle)
|
57
|
+
cjs = <<~JAVASCRIPT
|
58
|
+
delete AllDomHandles[#{handle}];
|
59
|
+
delete ConsoleMessages[#{handle}];
|
60
|
+
JAVASCRIPT
|
61
|
+
proc { driver.execute_script(cjs) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.node_handle_disposer(driver, handle)
|
65
|
+
cjs = <<~JAVASCRIPT
|
66
|
+
if (AllElementHandles[#{handle}]) { AllElementHandles[#{handle}].dispose(); }
|
67
|
+
delete AllElementHandles[#{handle}];
|
68
|
+
JAVASCRIPT
|
69
|
+
proc { driver.execute_script(cjs) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def browser
|
73
|
+
@browser
|
74
|
+
end
|
75
|
+
|
76
|
+
def document_handles
|
77
|
+
@context.eval 'Object.keys(AllDomHandles)'
|
78
|
+
end
|
79
|
+
|
80
|
+
##### frame, all todo
|
81
|
+
|
82
|
+
def frame_all_text(frame)
|
83
|
+
await <<~JAVASCRIPT
|
84
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
85
|
+
return frame.contentDocument.documentElement.textContent;
|
86
|
+
}, AllElementHandles[#{frame.handle}]);
|
87
|
+
JAVASCRIPT
|
88
|
+
end
|
89
|
+
|
90
|
+
def frame_body(frame)
|
91
|
+
node_data = await <<~JAVASCRIPT
|
92
|
+
var tt = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
93
|
+
node = frame.contentDocument.body;
|
94
|
+
var tag = node.tagName.toLowerCase();
|
95
|
+
var type = null;
|
96
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
97
|
+
return [tag, type];
|
98
|
+
}, AllElementHandles[#{frame.handle}]);
|
99
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1]};
|
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 frame_focus(frame)
|
108
|
+
await <<~JAVASCRIPT
|
109
|
+
await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
110
|
+
frame.contentDocument.documentElement.focus();
|
111
|
+
}, AllElementHandles[#{frame.handle}]);
|
112
|
+
JAVASCRIPT
|
113
|
+
end
|
114
|
+
|
115
|
+
def frame_head(frame)
|
116
|
+
node_data = await <<~JAVASCRIPT
|
117
|
+
var tt = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
118
|
+
node = frame.contentDocument.head;
|
119
|
+
var tag = node.tagName.toLowerCase();
|
120
|
+
var type = null;
|
121
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
122
|
+
return [tag, type];
|
123
|
+
}, AllElementHandles[#{frame.handle}]);
|
124
|
+
LastResult = {handle: node_handle, tag: tt[0], type: tt[1]};
|
125
|
+
JAVASCRIPT
|
126
|
+
if node_data
|
127
|
+
node_data[:css_selector] = 'body'
|
128
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def frame_html(frame)
|
133
|
+
await <<~JAVASCRIPT
|
134
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
135
|
+
return frame.contentDocument.documentElement.outerHTML;
|
136
|
+
}, AllElementHandles[#{frame.handle}]);
|
137
|
+
JAVASCRIPT
|
138
|
+
end
|
139
|
+
|
140
|
+
def frame_title(frame)
|
141
|
+
await <<~JAVASCRIPT
|
142
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
143
|
+
return frame.contentDocument.title;
|
144
|
+
}, AllElementHandles[#{frame.handle}]);
|
145
|
+
JAVASCRIPT
|
146
|
+
end
|
147
|
+
|
148
|
+
def frame_url(frame)
|
149
|
+
await <<~JAVASCRIPT
|
150
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
151
|
+
return frame.contentDocument.location.href;
|
152
|
+
}, AllElementHandles[#{frame.handle}]);
|
153
|
+
JAVASCRIPT
|
154
|
+
end
|
155
|
+
|
156
|
+
def frame_visible_text(frame)
|
157
|
+
# if node is AREA, check visibility of relevant image
|
158
|
+
text = await <<~JAVASCRIPT
|
159
|
+
LastResult = await AllElementHandles[#{frame.handle}].executionContext().evaluate((frame) => {
|
160
|
+
var node = frame.contentDocument.body;
|
161
|
+
var temp_node = node;
|
162
|
+
while (temp_node) {
|
163
|
+
style = window.getComputedStyle(node);
|
164
|
+
if (style.display === "none" || style.visibility === "hidden" || parseFloat(style.opacity) === 0) { return ''; }
|
165
|
+
temp_node = temp_node.parentElement;
|
166
|
+
}
|
167
|
+
if (node.nodeName == "TEXTAREA" || node instanceof SVGElement) { return node.textContent; }
|
168
|
+
else { return node.innerText; }
|
169
|
+
}, AllElementHandles[#{frame.handle}]);
|
170
|
+
JAVASCRIPT
|
171
|
+
text.gsub(/\A[[:space:]&&[^\u00a0]]+/, "").gsub(/[[:space:]&&[^\u00a0]]+\z/, "").gsub(/\n+/, "\n").tr("\u00a0", " ")
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def need_alt?(modifiers)
|
177
|
+
(modifiers & %i[alt alt_left alt_right]).size > 0
|
178
|
+
end
|
179
|
+
|
180
|
+
def need_control?(modifiers)
|
181
|
+
(modifiers & %i[control control_left control_rigth]).size > 0
|
182
|
+
end
|
183
|
+
|
184
|
+
def need_meta?(modifiers)
|
185
|
+
(modifiers & %i[meta meta_left meta_right]).size > 0
|
186
|
+
end
|
187
|
+
|
188
|
+
def need_shift?(modifiers)
|
189
|
+
(modifiers & %i[shift shift_left shift_right]).size > 0
|
190
|
+
end
|
191
|
+
|
192
|
+
def await(script)
|
193
|
+
@context.eval <<~JAVASCRIPT
|
194
|
+
(async () => {
|
195
|
+
try {
|
196
|
+
LastExecutionFinished = false;
|
197
|
+
LastResult = null;
|
198
|
+
LastErr = null;
|
199
|
+
#{script}
|
200
|
+
LastExecutionFinished = true;
|
201
|
+
} catch(err) {
|
202
|
+
LastResult = null;
|
203
|
+
LastErr = err;
|
204
|
+
LastExecutionFinished = true;
|
205
|
+
}
|
206
|
+
})()
|
207
|
+
JAVASCRIPT
|
208
|
+
await_result
|
209
|
+
end
|
210
|
+
|
211
|
+
def await_result
|
212
|
+
start_time = Time.now
|
213
|
+
while !execution_finished? && !timed_out?(start_time)
|
214
|
+
sleep 0.01
|
215
|
+
end
|
216
|
+
get_result
|
217
|
+
end
|
218
|
+
|
219
|
+
def determine_error(message)
|
220
|
+
if message.include?('Error: certificate has expired')
|
221
|
+
Isomorfeus::Puppetmaster::CertificateError.new(message) unless @ignore_https_errors
|
222
|
+
elsif message.include?('Error: getaddrinfo ENOTFOUND')
|
223
|
+
Isomorfeus::Puppetmaster::DNSError.new(message)
|
224
|
+
elsif message.include?('Unknown key: ')
|
225
|
+
Isomorfeus::Puppetmaster::KeyError.new(message)
|
226
|
+
elsif message.include?('Execution context was destroyed, most likely because of a navigation.')
|
227
|
+
Isomorfeus::Puppetmaster::ExecutionContextError.new(message)
|
228
|
+
elsif message.include?('Unable to find ')
|
229
|
+
Isomorfeus::Puppetmaster::ElementNotFound.new(message)
|
230
|
+
elsif (message.include?('SyntaxError:') && (message.include?('unknown pseudo-class selector') || message.include?('is not a valid selector'))) || message.include?('invalid xpath query')
|
231
|
+
Isomorfeus::Puppetmaster::DOMException.new(message)
|
232
|
+
else
|
233
|
+
Isomorfeus::Puppetmaster::JavaScriptError.new(message)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def execution_finished?
|
238
|
+
@context.eval 'LastExecutionFinished'
|
239
|
+
end
|
240
|
+
|
241
|
+
def get_result
|
242
|
+
res, err_msg = @context.eval 'GetLastResult()'
|
243
|
+
raise determine_error(err_msg) if err_msg
|
244
|
+
res
|
245
|
+
end
|
246
|
+
|
247
|
+
def jsdom_launch
|
248
|
+
<<~JAVASCRIPT
|
249
|
+
const canvas = require('canvas')
|
250
|
+
const jsdom = require('jsdom');
|
251
|
+
const Cookie = jsdom.toughCookie.Cookie;
|
252
|
+
const MemoryCookieStore = jsdom.toughCookie.MemoryCookieStore;
|
253
|
+
const { JSDOM } = jsdom;
|
254
|
+
|
255
|
+
const JSDOMOptions = {pretendToBeVisual: true, resources: 'usable', runScripts: 'dangerously'};
|
256
|
+
|
257
|
+
var LastResponse = null;
|
258
|
+
var LastResult = null;
|
259
|
+
var LastErr = null;
|
260
|
+
var LastExecutionFinished = false;
|
261
|
+
var LastHandleId = 0;
|
262
|
+
|
263
|
+
var AllDomHandles = {};
|
264
|
+
var AllElementHandles = {};
|
265
|
+
var AllConsoleHandles = {};
|
266
|
+
var ConsoleMessages = {};
|
267
|
+
|
268
|
+
var ModalText = null;
|
269
|
+
var ModalTextMatched = false;
|
270
|
+
|
271
|
+
const GetLastResult = function() {
|
272
|
+
if (LastExecutionFinished === true) {
|
273
|
+
var err = LastErr;
|
274
|
+
var res = LastResult;
|
275
|
+
|
276
|
+
LastErr = null;
|
277
|
+
LastRes = null;
|
278
|
+
LastExecutionFinished = false;
|
279
|
+
|
280
|
+
if (err) { return [null, err.message]; }
|
281
|
+
else { return [res, null]; }
|
282
|
+
|
283
|
+
} else {
|
284
|
+
return [null, (new Error('Last command did not yet finish execution!')).message];
|
285
|
+
}
|
286
|
+
};
|
287
|
+
|
288
|
+
const DialogAcceptHandler = async (dialog) => {
|
289
|
+
var msg = dialog.message()
|
290
|
+
ModalTextMatched = (ModalText === msg);
|
291
|
+
ModalText = msg;
|
292
|
+
await dialog.accept();
|
293
|
+
}
|
294
|
+
|
295
|
+
const DialogDismissHandler = async (dialog) => {
|
296
|
+
var msg = dialog.message()
|
297
|
+
ModalTextMatched = (ModalText === msg);
|
298
|
+
ModalText = msg;
|
299
|
+
await dialog.dismiss();
|
300
|
+
}
|
301
|
+
|
302
|
+
const RegisterElementHandle = function(element_handle) {
|
303
|
+
var entries = Object.entries(AllElementHandles);
|
304
|
+
for(var i = 0; i < entries.length; i++) {
|
305
|
+
if (entries[i][1] === element_handle) { return entries[i][0]; }
|
306
|
+
}
|
307
|
+
LastHandleId++;
|
308
|
+
AllElementHandles[LastHandleId] = element_handle;
|
309
|
+
return LastHandleId;
|
310
|
+
};
|
311
|
+
|
312
|
+
const RegisterElementHandleArray = function(element_handle_array) {
|
313
|
+
var registered_handles = [];
|
314
|
+
element_handle_array.forEach(function(handle){
|
315
|
+
registered_handles.push(RegisterElementHandle(handle));
|
316
|
+
});
|
317
|
+
return registered_handles;
|
318
|
+
};
|
319
|
+
|
320
|
+
const RegisterCon = function(con) {
|
321
|
+
var entries = Object.entries(ConsoleMessages);
|
322
|
+
for(var i = 0; i < entries.length; i++) {
|
323
|
+
if (entries[i][1] === con) { return entries[i][0]; }
|
324
|
+
}
|
325
|
+
LastHandleId++;
|
326
|
+
AllConsoleHandles[LastHandleId] = con;
|
327
|
+
ConsoleMessages[LastHandleId] = [];
|
328
|
+
return LastHandleId;
|
329
|
+
};
|
330
|
+
const RegisterDom = function(dom, handle_id) {
|
331
|
+
var entries = Object.entries(AllDomHandles);
|
332
|
+
for(var i = 0; i < entries.length; i++) {
|
333
|
+
if (entries[i][1] === dom) { return entries[i][0]; }
|
334
|
+
}
|
335
|
+
AllDomHandles[handle_id] = dom;
|
336
|
+
return handle_id;
|
337
|
+
};
|
338
|
+
|
339
|
+
(async () => {
|
340
|
+
try {
|
341
|
+
var con = new jsdom.VirtualConsole();
|
342
|
+
var jar = new jsdom.CookieJar(new MemoryCookieStore(), {rejectPublicSuffixes: false, looseMode: true});
|
343
|
+
var handle_id = RegisterCon(con);
|
344
|
+
con.on('error', (msg) => { ConsoleMessages[handle_id].push({level: 'error', location: '', text: msg}); });
|
345
|
+
con.on('warn', (msg) => { ConsoleMessages[handle_id].push({level: 'warn', location: '', text: msg}); });
|
346
|
+
con.on('info', (msg) => { ConsoleMessages[handle_id].push({level: 'info', location: '', text: msg}); });
|
347
|
+
con.on('log', (msg) => { ConsoleMessages[handle_id].push({level: 'dir', location: '', text: msg}); });
|
348
|
+
con.on('debug', (msg) => { ConsoleMessages[handle_id].push({level: 'dir', location: '', text: msg}); });
|
349
|
+
var dom = new JSDOM('', Object.assign({}, JSDOMOptions, { virtualConsole: con }));
|
350
|
+
var browser = dom.window.navigator.userAgent;
|
351
|
+
LastResult = [RegisterDom(dom, handle_id), browser];
|
352
|
+
LastExecutionFinished = true;
|
353
|
+
} catch (err) {
|
354
|
+
LastErr = err;
|
355
|
+
LastExecutionFinished = true;
|
356
|
+
}
|
357
|
+
})();
|
358
|
+
JAVASCRIPT
|
359
|
+
end
|
360
|
+
|
361
|
+
def timed_out?(start_time)
|
362
|
+
if (Time.now - start_time) > @timeout
|
363
|
+
raise "Command Execution timed out!"
|
364
|
+
end
|
365
|
+
false
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
@@ -0,0 +1,908 @@
|
|
1
|
+
module Isomorfeus
|
2
|
+
module Puppetmaster
|
3
|
+
module Driver
|
4
|
+
module JsdomDocument
|
5
|
+
def document_accept_alert(document, **options, &block)
|
6
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
7
|
+
# TODO maybe wrap in mutex
|
8
|
+
# text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
9
|
+
# @context.exec <<~JAVASCRIPT
|
10
|
+
# ModalText = #{text};
|
11
|
+
# AllDomHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
12
|
+
# JAVASCRIPT
|
13
|
+
# block.call
|
14
|
+
# sleep @reaction_timeout
|
15
|
+
# @context.eval 'ModalText'
|
16
|
+
# ensure
|
17
|
+
# matched = await <<~JAVASCRIPT
|
18
|
+
# LastResult = ModalTextMatched;
|
19
|
+
# ModalTextMatched = false;
|
20
|
+
# ModalText = null;
|
21
|
+
# AllDomHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
22
|
+
# JAVASCRIPT
|
23
|
+
# raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
24
|
+
end
|
25
|
+
|
26
|
+
def document_accept_confirm(document, **options, &block)
|
27
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
28
|
+
# TODO maybe wrap in mutex
|
29
|
+
# text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
30
|
+
# @context.exec <<~JAVASCRIPT
|
31
|
+
# ModalText = #{text};
|
32
|
+
# AllDomHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
33
|
+
# JAVASCRIPT
|
34
|
+
# block.call
|
35
|
+
# sleep @reaction_timeout
|
36
|
+
# @context.eval 'ModalText'
|
37
|
+
# ensure
|
38
|
+
# matched = await <<~JAVASCRIPT
|
39
|
+
# LastResult = ModalTextMatched;
|
40
|
+
# ModalTextMatched = false;
|
41
|
+
# ModalText = null;
|
42
|
+
# AllDomHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
43
|
+
# JAVASCRIPT
|
44
|
+
# raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
45
|
+
end
|
46
|
+
|
47
|
+
def document_accept_leave_page(document, **options, &block)
|
48
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
49
|
+
# TODO maybe wrap in mutex
|
50
|
+
# text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
51
|
+
# @context.exec <<~JAVASCRIPT
|
52
|
+
# ModalText = #{text};
|
53
|
+
# AllDomHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
54
|
+
# JAVASCRIPT
|
55
|
+
# block.call
|
56
|
+
# sleep @reaction_timeout
|
57
|
+
# @context.eval 'ModalText'
|
58
|
+
# ensure
|
59
|
+
# matched = await <<~JAVASCRIPT
|
60
|
+
# LastResult = ModalTextMatched;
|
61
|
+
# ModalTextMatched = false;
|
62
|
+
# ModalText = null;
|
63
|
+
# AllDomHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
64
|
+
# JAVASCRIPT
|
65
|
+
# raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
66
|
+
end
|
67
|
+
|
68
|
+
def document_accept_prompt(document, **options, &block)
|
69
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
70
|
+
# # TODO maybe wrap in mutex
|
71
|
+
# text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
72
|
+
# @context.exec <<~JAVASCRIPT
|
73
|
+
# ModalText = #{text};
|
74
|
+
# AllDomHandles[#{document.handle}].on('dialog', DialogAcceptHandler);
|
75
|
+
# JAVASCRIPT
|
76
|
+
# block.call
|
77
|
+
# sleep @reaction_timeout
|
78
|
+
# @context.eval 'ModalText'
|
79
|
+
# ensure
|
80
|
+
# matched = await <<~JAVASCRIPT
|
81
|
+
# LastResult = ModalTextMatched;
|
82
|
+
# ModalTextMatched = false;
|
83
|
+
# ModalText = null;
|
84
|
+
# AllDomHandles[#{document.handle}].removeListener('dialog', DialogAcceptHandler);
|
85
|
+
# JAVASCRIPT
|
86
|
+
# raise Isomorfeus::Puppetmaster::NoModalError if options.has_key?(:text) && !matched
|
87
|
+
end
|
88
|
+
|
89
|
+
def document_all_text(document)
|
90
|
+
@context.eval "AllDomHandles[#{document.handle}].window.document.documentElement.textContent"
|
91
|
+
end
|
92
|
+
|
93
|
+
def document_body(document)
|
94
|
+
node_data = @context.exec <<~JAVASCRIPT
|
95
|
+
var node = AllDomHandles[#{document.handle}].window.document.body;
|
96
|
+
var node_handle = RegisterElementHandle(node);
|
97
|
+
var tag = node.tagName.toLowerCase();
|
98
|
+
return {handle: node_handle, tag: tag, type: null, content_editable: node.isContentEditable};
|
99
|
+
JAVASCRIPT
|
100
|
+
if node_data
|
101
|
+
node_data[:css_selector] = 'body'
|
102
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def document_bring_to_front(_document); end
|
107
|
+
|
108
|
+
def document_clear_authentication_credentials(document)
|
109
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
110
|
+
end
|
111
|
+
|
112
|
+
def document_clear_cookies(document)
|
113
|
+
@context.exec "AllDomHandles[#{document.handle}].cookieJar.removeAllCookiesSync()"
|
114
|
+
end
|
115
|
+
|
116
|
+
def document_clear_extra_headers(document)
|
117
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
118
|
+
end
|
119
|
+
|
120
|
+
def document_clear_url_blacklist(document)
|
121
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
122
|
+
end
|
123
|
+
|
124
|
+
def document_click(document, x: nil, y: nil, modifiers: nil)
|
125
|
+
# modifier_keys: :alt, :control, :meta, :shift
|
126
|
+
# raise Isomorfeus::Pupppetmaster::InvalidActionError.new(:click) unless visible?
|
127
|
+
modifiers = [modifiers] if modifiers.is_a?(Symbol)
|
128
|
+
modifiers = [] unless modifiers
|
129
|
+
modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
|
130
|
+
@context.exec <<~JAVASCRIPT
|
131
|
+
var options = {button: 0, bubbles: true, cancelable: true};
|
132
|
+
var window = AllDomHandles[#{document.handle}].window;
|
133
|
+
var modifiers = #{modifiers};
|
134
|
+
if (modifiers.includes('meta')) { options['metaKey'] = true; }
|
135
|
+
if (modifiers.includes('control')) { options['ctrlKey'] = true; }
|
136
|
+
if (modifiers.includes('shift')) { options['shiftKey'] = true; }
|
137
|
+
if (modifiers.includes('alt')) { options['altKey'] = true; }
|
138
|
+
var x = #{x ? x : 'null'};
|
139
|
+
var y = #{y ? y : 'null'};
|
140
|
+
if (x && y) {
|
141
|
+
options['clientX'] = x;
|
142
|
+
options['clientY'] = y;
|
143
|
+
}
|
144
|
+
window.document.dispatchEvent(new window.MouseEvent('mousedown', options));
|
145
|
+
window.document.dispatchEvent(new window.MouseEvent('mouseup', options));
|
146
|
+
window.document.dispatchEvent(new window.MouseEvent('click', options));
|
147
|
+
JAVASCRIPT
|
148
|
+
end
|
149
|
+
|
150
|
+
def document_close(document)
|
151
|
+
await <<~JAVASCRIPT
|
152
|
+
delete AllDomHandles[#{document.handle}];
|
153
|
+
delete AllConsoleHandles[#{document.handle}];
|
154
|
+
delete ConsoleMessages[#{document.handle}];
|
155
|
+
JAVASCRIPT
|
156
|
+
end
|
157
|
+
|
158
|
+
def document_console(document)
|
159
|
+
messages = @context.eval "ConsoleMessages[#{document.handle}]"
|
160
|
+
messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
161
|
+
end
|
162
|
+
|
163
|
+
def document_cookies(document)
|
164
|
+
uri = document_url(document)
|
165
|
+
if uri == 'about:blank'
|
166
|
+
uri = if Isomorfeus::Puppetmaster.server_host
|
167
|
+
u = URI.new
|
168
|
+
u.scheme = Isomorfeus::Puppetmaster.server_scheme if Isomorfeus::Puppetmaster.server_scheme
|
169
|
+
u.host = Isomorfeus::Puppetmaster.server_host
|
170
|
+
u.to_s
|
171
|
+
else
|
172
|
+
'http://127.0.0.1'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
result = @context.eval "AllDomHandles[#{document.handle}].cookieJar.getCookiesSync('#{uri.to_s}')"
|
176
|
+
result_hash = {}
|
177
|
+
result.each do |cookie|
|
178
|
+
cookie['name'] = cookie['key']
|
179
|
+
cookie['expires'] = DateTime.parse(cookie['expires']).to_time if cookie.has_key?('expires')
|
180
|
+
result_hash[cookie['name']] = Isomorfeus::Puppetmaster::Cookie.new(cookie)
|
181
|
+
end
|
182
|
+
result_hash
|
183
|
+
end
|
184
|
+
|
185
|
+
def document_dismiss_confirm(document, **options, &block)
|
186
|
+
# TODO
|
187
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
188
|
+
@context.exec <<~JAVASCRIPT
|
189
|
+
ModalText = #{text};
|
190
|
+
AllDomHandles[#{document.handle}].on('dialog', DialogDismissHandler);
|
191
|
+
JAVASCRIPT
|
192
|
+
block.call
|
193
|
+
sleep @reaction_timeout
|
194
|
+
@context.eval 'ModalText'
|
195
|
+
ensure
|
196
|
+
matched = await <<~JAVASCRIPT
|
197
|
+
LastResult = ModalTextMatched;
|
198
|
+
ModalTextMatched = false;
|
199
|
+
ModalText = null;
|
200
|
+
AllDomHandles[#{document.handle}].removeListener('dialog', DialogDismissHandler);
|
201
|
+
JAVASCRIPT
|
202
|
+
raise Isomorfeus::Puppetmaster::ModalNotFound if options.has_key?(:text) && !matched
|
203
|
+
end
|
204
|
+
|
205
|
+
def document_dismiss_leave_page(document, **options, &block)
|
206
|
+
# TODO
|
207
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
208
|
+
@context.exec <<~JAVASCRIPT
|
209
|
+
ModalText = #{text};
|
210
|
+
AllDomHandles[#{document.handle}].on('dialog', DialogDismissHandler);
|
211
|
+
JAVASCRIPT
|
212
|
+
block.call
|
213
|
+
sleep @reaction_timeout
|
214
|
+
@context.eval 'ModalText'
|
215
|
+
ensure
|
216
|
+
matched = await <<~JAVASCRIPT
|
217
|
+
LastResult = ModalTextMatched;
|
218
|
+
ModalTextMatched = false;
|
219
|
+
ModalText = null;
|
220
|
+
AllDomHandles[#{document.handle}].removeListener('dialog', DialogDismissHandler);
|
221
|
+
JAVASCRIPT
|
222
|
+
raise Isomorfeus::Puppetmaster::ModalNotFound if options.has_key?(:text) && !matched
|
223
|
+
end
|
224
|
+
|
225
|
+
def document_dismiss_prompt(document, **options, &block)
|
226
|
+
# TODO
|
227
|
+
text = options.has_key?(:text) ? "`#{options[:text]}`" : 'null'
|
228
|
+
@context.exec <<~JAVASCRIPT
|
229
|
+
ModalText = #{text};
|
230
|
+
AllDomHandles[#{document.handle}].on('dialog', DialogDismissHandler);
|
231
|
+
JAVASCRIPT
|
232
|
+
block.call
|
233
|
+
sleep @reaction_timeout
|
234
|
+
@context.eval 'ModalText'
|
235
|
+
ensure
|
236
|
+
matched = await <<~JAVASCRIPT
|
237
|
+
LastResult = ModalTextMatched;
|
238
|
+
ModalTextMatched = false;
|
239
|
+
ModalText = null;
|
240
|
+
AllDomHandles[#{document.handle}].removeListener('dialog', DialogDismissHandler);
|
241
|
+
JAVASCRIPT
|
242
|
+
raise Isomorfeus::Puppetmaster::ModalNotFound if options.has_key?(:text) && !matched
|
243
|
+
end
|
244
|
+
|
245
|
+
def document_dispatch_event(document, name, event_type = nil, **options)
|
246
|
+
raise ArgumentError, 'Unknown event' unless EVENTS.key?(name.to_sym) || event_type
|
247
|
+
event_type, opts = *EVENTS[name.to_s.downcase.tr('_', '').to_sym] if event_type.nil?
|
248
|
+
opts.merge!(options)
|
249
|
+
final_options = options.map { |k,v| "#{k}: '#{v}'" }
|
250
|
+
@context.exec <<~JAVASCRIPT
|
251
|
+
var window = AllDomHandles[#{document.handle}].window;
|
252
|
+
var event = new window.#{event_type}('#{name}', { #{final_options.join(', ')} });
|
253
|
+
window.document.dispatchEvent(event);
|
254
|
+
JAVASCRIPT
|
255
|
+
end
|
256
|
+
|
257
|
+
def document_double_click(document, x: nil, y: nil, modifiers: nil)
|
258
|
+
# modifier_keys: :alt, :control, :meta, :shift
|
259
|
+
modifiers = [modifiers] if modifiers.is_a?(Symbol)
|
260
|
+
modifiers = [] unless modifiers
|
261
|
+
modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
|
262
|
+
await <<~JAVASCRIPT
|
263
|
+
var options = {button: 0, bubbles: true, cancelable: true};
|
264
|
+
var window = AllDomHandles[#{document.handle}].window;
|
265
|
+
var modifiers = #{modifiers};
|
266
|
+
if (modifiers.includes('meta')) { options['metaKey'] = true; }
|
267
|
+
if (modifiers.includes('control')) { options['ctrlKey'] = true; }
|
268
|
+
if (modifiers.includes('shift')) { options['shiftKey'] = true; }
|
269
|
+
if (modifiers.includes('alt')) { options['altKey'] = true; }
|
270
|
+
var x = #{x ? x : 'null'};
|
271
|
+
var y = #{y ? y : 'null'};
|
272
|
+
if (x && y) {
|
273
|
+
options['clientX'] = x;
|
274
|
+
options['clientY'] = y;
|
275
|
+
}
|
276
|
+
window.document.dispatchEvent(new window.MouseEvent('mousedown', options));
|
277
|
+
window.document.dispatchEvent(new window.MouseEvent('mouseup', options));
|
278
|
+
window.document.dispatchEvent(new window.MouseEvent('dblclick', options));
|
279
|
+
JAVASCRIPT
|
280
|
+
end
|
281
|
+
|
282
|
+
def document_evaluate_script(document, script, *args)
|
283
|
+
@context.eval <<~JAVASCRIPT
|
284
|
+
AllDomHandles[#{document.handle}].window.eval(
|
285
|
+
`var arguments = #{args};
|
286
|
+
#{script}`
|
287
|
+
)
|
288
|
+
JAVASCRIPT
|
289
|
+
rescue ExecJS::ProgramError => e
|
290
|
+
raise determine_error(e.message)
|
291
|
+
end
|
292
|
+
|
293
|
+
def document_execute_script(document, script, *args)
|
294
|
+
@context.eval <<~JAVASCRIPT
|
295
|
+
AllDomHandles[#{document.handle}].window.eval(
|
296
|
+
`(function() { var arguments = #{args};
|
297
|
+
#{script}
|
298
|
+
})()`
|
299
|
+
)
|
300
|
+
JAVASCRIPT
|
301
|
+
rescue ExecJS::ProgramError => e
|
302
|
+
raise determine_error(e.message)
|
303
|
+
end
|
304
|
+
|
305
|
+
def document_find(document, selector)
|
306
|
+
js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
|
307
|
+
node_data = @context.exec <<~JAVASCRIPT
|
308
|
+
var node = AllDomHandles[#{document.handle}].window.document.querySelector("#{js_escaped_selector}");
|
309
|
+
if (node) {
|
310
|
+
var node_handle = RegisterElementHandle(node);
|
311
|
+
var tag = node.tagName.toLowerCase();
|
312
|
+
var type = null;
|
313
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
314
|
+
return {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
|
315
|
+
}
|
316
|
+
JAVASCRIPT
|
317
|
+
if node_data
|
318
|
+
node_data[:css_selector] = selector
|
319
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
320
|
+
else
|
321
|
+
raise Isomorfeus::Puppetmaster::ElementNotFound.new(selector)
|
322
|
+
end
|
323
|
+
rescue Exception => e
|
324
|
+
raise determine_error(e.message)
|
325
|
+
end
|
326
|
+
|
327
|
+
def document_find_all(document, selector)
|
328
|
+
js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
|
329
|
+
node_data_array = @context.exec <<~JAVASCRIPT
|
330
|
+
var node_array = AllDomHandles[#{document.handle}].window.document.querySelectorAll("#{js_escaped_selector}");
|
331
|
+
var node_data_array = [];
|
332
|
+
if (node_array) {
|
333
|
+
for (var i=0; i<node_array.length; i++) {
|
334
|
+
var node_handle = RegisterElementHandle(node_array[i]);
|
335
|
+
var tag = node.tagName.toLowerCase();
|
336
|
+
var type = null;
|
337
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
338
|
+
node_data_array.push({handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable});
|
339
|
+
}
|
340
|
+
}
|
341
|
+
return node_data_array;
|
342
|
+
JAVASCRIPT
|
343
|
+
node_data_array.map do |node_data|
|
344
|
+
node_data[:css_selector] = selector
|
345
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def document_find_all_xpath(document, query)
|
350
|
+
js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
|
351
|
+
node_data_array = @context.exec <<~JAVASCRIPT
|
352
|
+
var window = AllDomHandles[#{document.handle}].window;
|
353
|
+
var document = window.document;
|
354
|
+
var xpath_result = document.evaluate("#{js_escaped_query}", document, null, window.XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
355
|
+
var node;
|
356
|
+
var node_data_array = [];
|
357
|
+
while (node = xpath_result.iterateNext) {
|
358
|
+
var node_handle = RegisterElementHandle(node);
|
359
|
+
var tag = node.tagName.toLowerCase();
|
360
|
+
var type = null;
|
361
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
362
|
+
node_data_array.push({handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable});
|
363
|
+
}
|
364
|
+
return node_data_array;
|
365
|
+
JAVASCRIPT
|
366
|
+
node_data_array.map do |node_data|
|
367
|
+
node_data[:xpath_query] = query
|
368
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def document_find_xpath(document, query)
|
373
|
+
js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
|
374
|
+
node_data = @context.exec <<~JAVASCRIPT
|
375
|
+
var window = AllDomHandles[#{document.handle}].window;
|
376
|
+
var document = window.document;
|
377
|
+
var xpath_result = document.evaluate("#{js_escaped_query}", document, null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
378
|
+
var node = xpath_result.singleNodeValue;
|
379
|
+
if (node) {
|
380
|
+
var node_handle = RegisterElementHandle(node);
|
381
|
+
var tag = node.tagName.toLowerCase();
|
382
|
+
var type = null;
|
383
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
384
|
+
return {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
|
385
|
+
}
|
386
|
+
JAVASCRIPT
|
387
|
+
if node_data
|
388
|
+
node_data[:xpath_query] = query
|
389
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
390
|
+
else
|
391
|
+
raise Isomorfeus::Puppetmaster::ElementNotFound.new(query)
|
392
|
+
end
|
393
|
+
rescue ExecJS::ProgramError => e
|
394
|
+
raise determine_error('invalid xpath query')
|
395
|
+
end
|
396
|
+
|
397
|
+
def document_go_back(document)
|
398
|
+
raise 'Browser history not supported.'
|
399
|
+
@context.eval "AllDomHandles[#{document.handle}].window.history.back()"
|
400
|
+
end
|
401
|
+
|
402
|
+
def document_go_forward(document)
|
403
|
+
raise 'Browser history not supported.'
|
404
|
+
@context.eval "AllDomHandles[#{document.handle}].window.history.forward()"
|
405
|
+
end
|
406
|
+
|
407
|
+
def document_goto(document, uri)
|
408
|
+
parsed_uri = URI.parse(uri)
|
409
|
+
parsed_uri.host = @app.host unless parsed_uri.host
|
410
|
+
parsed_uri.port = @app.port unless parsed_uri.port
|
411
|
+
parsed_uri.scheme = @app.scheme unless parsed_uri.scheme
|
412
|
+
response_hash, messages = await <<~JAVASCRIPT
|
413
|
+
ConsoleMessages[#{document.handle}] = [];
|
414
|
+
var cookie_jar = AllDomHandles[#{document.handle}].cookieJar.cloneSync(new MemoryCookieStore());
|
415
|
+
cookie_jar.rejectPublicSuffixes = false;
|
416
|
+
var con = new jsdom.VirtualConsole()
|
417
|
+
con.on('error', (msg) => { ConsoleMessages[#{document.handle}].push({level: 'error', location: '', text: msg}); });
|
418
|
+
con.on('warn', (msg) => { ConsoleMessages[#{document.handle}].push({level: 'warn', location: '', text: msg}); });
|
419
|
+
con.on('info', (msg) => { ConsoleMessages[#{document.handle}].push({level: 'info', location: '', text: msg}); });
|
420
|
+
con.on('log', (msg) => { ConsoleMessages[#{document.handle}].push({level: 'dir', location: '', text: msg}); });
|
421
|
+
con.on('debug', (msg) => { ConsoleMessages[#{document.handle}].push({level: 'dir', location: '', text: msg}); });
|
422
|
+
AllConsoleHandles[#{document.handle}] = con;
|
423
|
+
try {
|
424
|
+
var new_dom = await JSDOM.fromURL('#{parsed_uri.to_s}', Object.assign({}, JSDOMOptions, { cookieJar: cookie_jar, virtualConsole: con }));
|
425
|
+
AllDomHandles[#{document.handle}] = new_dom;
|
426
|
+
var formatted_response = {
|
427
|
+
headers: {},
|
428
|
+
ok: true,
|
429
|
+
remote_address: '#{parsed_uri.to_s}',
|
430
|
+
request: {},
|
431
|
+
status: 200,
|
432
|
+
status_text: '',
|
433
|
+
text: '',
|
434
|
+
url: '#{parsed_uri.to_s}'
|
435
|
+
};
|
436
|
+
LastResult = [formatted_response, ConsoleMessages[#{document.handle}]];
|
437
|
+
} catch (err) {
|
438
|
+
var formatted_error_response = {
|
439
|
+
headers: err.options.headers,
|
440
|
+
ok: false,
|
441
|
+
remote_address: '#{parsed_uri.to_s}',
|
442
|
+
request: {},
|
443
|
+
status: err.statusCode ? err.statusCode : 500,
|
444
|
+
status_text: err.response ? err.response.statusMessage : '',
|
445
|
+
text: err.message ? err.message : '',
|
446
|
+
url: '#{parsed_uri.to_s}'
|
447
|
+
};
|
448
|
+
LastResult = [formatted_error_response, ConsoleMessages[#{document.handle}]];
|
449
|
+
}
|
450
|
+
JAVASCRIPT
|
451
|
+
con_messages = messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
452
|
+
# STDERR.puts 'M', con_messages
|
453
|
+
# STDERR.puts 'R', response_hash
|
454
|
+
con_messages.each { |m| raise determine_error(m.text) if m.level == 'error' && !m.text.start_with?('Failed to load resource:') }
|
455
|
+
if response_hash
|
456
|
+
response = Isomorfeus::Puppetmaster::Response.new(response_hash)
|
457
|
+
if response.status == 500 && response.text.start_with?('Error:')
|
458
|
+
error = determine_error(response.text)
|
459
|
+
raise error if error
|
460
|
+
end
|
461
|
+
document.instance_variable_set(:@response, response)
|
462
|
+
end
|
463
|
+
document.response
|
464
|
+
end
|
465
|
+
|
466
|
+
def document_head(document)
|
467
|
+
node_data = @context.exec <<~JAVASCRIPT
|
468
|
+
var node = AllDomHandles[#{document.handle}].window.document.head;
|
469
|
+
var node_handle = RegisterElementHandle(node);
|
470
|
+
var tag = node.tagName.toLowerCase();
|
471
|
+
return {handle: node_handle, tag: tag, type: null, content_editable: node.isContentEditable};
|
472
|
+
JAVASCRIPT
|
473
|
+
if node_data
|
474
|
+
node_data[:css_selector] = 'body'
|
475
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
def document_html(document)
|
480
|
+
@context.eval "AllDomHandles[#{document.handle}].serialize()"
|
481
|
+
end
|
482
|
+
|
483
|
+
def document_open_new_document(_document, uri = nil)
|
484
|
+
if !uri || uri == 'about:blank'
|
485
|
+
parsed_uri = 'about:blank'
|
486
|
+
else
|
487
|
+
parsed_uri = URI.parse(uri)
|
488
|
+
parsed_uri.host = @app.host unless parsed_uri.host
|
489
|
+
parsed_uri.port = @app.port unless parsed_uri.port
|
490
|
+
parsed_uri.scheme = @app.scheme unless parsed_uri.scheme
|
491
|
+
end
|
492
|
+
handle, response_hash, messages = await <<~JAVASCRIPT
|
493
|
+
var con = new jsdom.VirtualConsole();
|
494
|
+
var jar = new jsdom.CookieJar(new MemoryCookieStore(), {rejectPublicSuffixes: false, looseMode: true});
|
495
|
+
var handle_id = RegisterCon(con);
|
496
|
+
con.on('error', (msg) => { ConsoleMessages[handle_id].push({level: 'error', location: '', text: msg}); });
|
497
|
+
con.on('warn', (msg) => { ConsoleMessages[handle_id].push({level: 'warn', location: '', text: msg}); });
|
498
|
+
con.on('info', (msg) => { ConsoleMessages[handle_id].push({level: 'info', location: '', text: msg}); });
|
499
|
+
con.on('log', (msg) => { ConsoleMessages[handle_id].push({level: 'dir', location: '', text: msg}); });
|
500
|
+
con.on('debug', (msg) => { ConsoleMessages[handle_id].push({level: 'dir', location: '', text: msg}); });
|
501
|
+
try {
|
502
|
+
var new_dom;
|
503
|
+
var uri = '#{parsed_uri.to_s}';
|
504
|
+
if (uri === 'about:blank') {
|
505
|
+
new_dom = new JSDOM('', Object.assign({}, JSDOMOptions, { cookieJar: jar, virtualConsole: con }));
|
506
|
+
} else {
|
507
|
+
new_dom = await JSDOM.fromURL(uri, Object.assign({}, JSDOMOptions, { cookieJar: jar, virtualConsole: con }));
|
508
|
+
}
|
509
|
+
AllDomHandles[handle_id] = new_dom;
|
510
|
+
var formatted_response = {
|
511
|
+
headers: {},
|
512
|
+
ok: false,
|
513
|
+
remote_address: '#{parsed_uri.to_s}',
|
514
|
+
request: {},
|
515
|
+
status: 200,
|
516
|
+
status_text: '',
|
517
|
+
text: '',
|
518
|
+
url: '#{parsed_uri.to_s}'
|
519
|
+
};
|
520
|
+
LastResult = [handle_id, formatted_response, ConsoleMessages[handle_id]];
|
521
|
+
} catch (err) {
|
522
|
+
var formatted_response = {
|
523
|
+
headers: err.options.headers,
|
524
|
+
ok: true,
|
525
|
+
remote_address: '#{parsed_uri.to_s}',
|
526
|
+
request: {},
|
527
|
+
status: err.statusCode,
|
528
|
+
status_text: err.response ? err.response.statusMessage : '',
|
529
|
+
text: '',
|
530
|
+
url: '#{parsed_uri.to_s}'
|
531
|
+
};
|
532
|
+
LastResult = [handle_id, formatted_response, ConsoleMessages[handle_id]];
|
533
|
+
}
|
534
|
+
JAVASCRIPT
|
535
|
+
# STDERR.puts 'R', response_hash
|
536
|
+
# STDERR.puts 'C', messages
|
537
|
+
con_messages = messages.map {|m| Isomorfeus::Puppetmaster::ConsoleMessage.new(m)}
|
538
|
+
con_messages.each { |m| raise determine_error(m.text) if m.level == 'error' && !m.text.start_with?('Failed to load resource:') }
|
539
|
+
response = Isomorfeus::Puppetmaster::Response.new(response_hash)
|
540
|
+
if response.status == 500 && response.text.start_with?('Error:')
|
541
|
+
error = determine_error(response.text)
|
542
|
+
raise error if error
|
543
|
+
end
|
544
|
+
Isomorfeus::Puppetmaster::Document.new(self, handle, response)
|
545
|
+
end
|
546
|
+
|
547
|
+
def document_reload(document)
|
548
|
+
document_goto(document, document_url(document))
|
549
|
+
end
|
550
|
+
|
551
|
+
def document_remove_cookie(document, name)
|
552
|
+
uri = document_url(document)
|
553
|
+
if uri == 'about:blank'
|
554
|
+
uri = if Isomorfeus::Puppetmaster.server_host
|
555
|
+
u = URI.new
|
556
|
+
u.scheme = Isomorfeus::Puppetmaster.server_scheme if Isomorfeus::Puppetmaster.server_scheme
|
557
|
+
u.host = Isomorfeus::Puppetmaster.server_host
|
558
|
+
u.to_s
|
559
|
+
else
|
560
|
+
'http://127.0.0.1'
|
561
|
+
end
|
562
|
+
end
|
563
|
+
domain = URI.parse(uri).host
|
564
|
+
await <<~JAVASCRIPT
|
565
|
+
var cookies = AllDomHandles[#{document.handle}].cookieJar.getCookiesSync('#{uri.to_s}')
|
566
|
+
var path = '/';
|
567
|
+
for(i=0; i<cookies.length; i++) {
|
568
|
+
if (cookies[i].key === '#{name}' && cookies[i].domain === '#{domain}') {
|
569
|
+
var path = cookies[i].path;
|
570
|
+
break;
|
571
|
+
}
|
572
|
+
}
|
573
|
+
var promise = new Promise(function(resolve, reject) {
|
574
|
+
AllDomHandles[#{document.handle}].cookieJar.store.removeCookie('#{domain}', path, '#{name}', function(err){ resolve(true); });
|
575
|
+
})
|
576
|
+
await promise;
|
577
|
+
JAVASCRIPT
|
578
|
+
end
|
579
|
+
|
580
|
+
def document_render_base64(document, **options)
|
581
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
582
|
+
end
|
583
|
+
|
584
|
+
def document_reset_user_agent(document)
|
585
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
586
|
+
end
|
587
|
+
|
588
|
+
def document_right_click(document, x: nil, y: nil, modifiers: nil)
|
589
|
+
# modifier_keys: :alt, :control, :meta, :shift
|
590
|
+
modifiers = [modifiers] if modifiers.is_a?(Symbol)
|
591
|
+
modifiers = [] unless modifiers
|
592
|
+
modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
|
593
|
+
await <<~JAVASCRIPT
|
594
|
+
var options = {button: 2, bubbles: true, cancelable: true};
|
595
|
+
var window = AllDomHandles[#{document.handle}].window;
|
596
|
+
var modifiers = #{modifiers};
|
597
|
+
if (modifiers.includes('meta')) { options['metaKey'] = true; }
|
598
|
+
if (modifiers.includes('control')) { options['ctrlKey'] = true; }
|
599
|
+
if (modifiers.includes('shift')) { options['shiftKey'] = true; }
|
600
|
+
if (modifiers.includes('alt')) { options['altKey'] = true; }
|
601
|
+
var x = #{x ? x : 'null'};
|
602
|
+
var y = #{y ? y : 'null'};
|
603
|
+
if (x && y) {
|
604
|
+
options['clientX'] = x;
|
605
|
+
options['clientY'] = y;
|
606
|
+
}
|
607
|
+
window.document.dispatchEvent(new window.MouseEvent('mousedown', options));
|
608
|
+
window.document.dispatchEvent(new window.MouseEvent('mouseup', options));
|
609
|
+
window.document.dispatchEvent(new window.MouseEvent('contextmenu', options));
|
610
|
+
JAVASCRIPT
|
611
|
+
end
|
612
|
+
|
613
|
+
def document_save_pdf(document, **options)
|
614
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
615
|
+
end
|
616
|
+
|
617
|
+
def document_save_screenshot(document, file, **options)
|
618
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
619
|
+
end
|
620
|
+
|
621
|
+
def document_scroll_by(document, x, y)
|
622
|
+
@context.exec <<~JAVASCRIPT
|
623
|
+
AllDomHandles[#{document.handle}].window.scrollX = AllDomHandles[#{document.handle}].window.scrollX + #{x};
|
624
|
+
AllDomHandles[#{document.handle}].window.scrollY = AllDomHandles[#{document.handle}].window.scrollY + #{y};
|
625
|
+
JAVASCRIPT
|
626
|
+
end
|
627
|
+
|
628
|
+
def document_scroll_to(document, x, y)
|
629
|
+
@context.exec <<~JAVASCRIPT
|
630
|
+
AllDomHandles[#{document.handle}].window.scrollX = #{x};
|
631
|
+
AllDomHandles[#{document.handle}].window.scrollY = #{y};
|
632
|
+
JAVASCRIPT
|
633
|
+
end
|
634
|
+
|
635
|
+
def document_set_authentication_credentials(document, username, password)
|
636
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
637
|
+
end
|
638
|
+
|
639
|
+
def document_set_cookie(document, name, value, **options)
|
640
|
+
options[:key] ||= name
|
641
|
+
options[:value] ||= value
|
642
|
+
uri = document_url(document)
|
643
|
+
if uri == 'about:blank'
|
644
|
+
uri = if Isomorfeus::Puppetmaster.server_host
|
645
|
+
u = URI.new
|
646
|
+
u.scheme = Isomorfeus::Puppetmaster.server_scheme if Isomorfeus::Puppetmaster.server_scheme
|
647
|
+
u.host = Isomorfeus::Puppetmaster.server_host
|
648
|
+
u.to_s
|
649
|
+
else
|
650
|
+
'http://127.0.0.1'
|
651
|
+
end
|
652
|
+
end
|
653
|
+
options[:domain] ||= URI.parse(uri).host
|
654
|
+
final_options = []
|
655
|
+
final_options << "expires: new Date('#{options.delete(:expires).to_s}')" if options.has_key?(:expires)
|
656
|
+
final_options << "httpOnly: #{options.delete(:http_only)}" if options.has_key?(:http_only)
|
657
|
+
final_options << "secure: #{options.delete(:secure)}" if options.has_key?(:secure)
|
658
|
+
final_options << "sameSite: '#{options.delete(:same_site)}'" if options.has_key?(:same_site)
|
659
|
+
options.each do |k,v|
|
660
|
+
final_options << "#{k}: '#{v}'"
|
661
|
+
end
|
662
|
+
@context.exec "AllDomHandles[#{document.handle}].cookieJar.setCookieSync(new Cookie({#{final_options.join(', ')}}), '#{uri.to_s}', {ignoreError: true})"
|
663
|
+
end
|
664
|
+
|
665
|
+
def document_set_extra_headers(document, headers_hash)
|
666
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
667
|
+
end
|
668
|
+
|
669
|
+
def document_set_user_agent(document, agent_string)
|
670
|
+
raise Isomorfeus::Puppetmaster::NotSupported
|
671
|
+
end
|
672
|
+
|
673
|
+
def document_title(document)
|
674
|
+
@context.eval "AllDomHandles[#{document.handle}].window.document.title"
|
675
|
+
end
|
676
|
+
|
677
|
+
def document_type_keys(document, *keys)
|
678
|
+
cjs = <<~JAVASCRIPT
|
679
|
+
var window = AllDomHandles[#{document.handle}].window;
|
680
|
+
var events = [];
|
681
|
+
var options = {bubbles: true, cancelable: true};
|
682
|
+
JAVASCRIPT
|
683
|
+
top_modifiers = []
|
684
|
+
keys.each do |key|
|
685
|
+
if key.is_a?(String)
|
686
|
+
key.each_char do |c|
|
687
|
+
shift = !! /[[:upper:]]/.match(c)
|
688
|
+
cjs << <<~JAVASCRIPT
|
689
|
+
events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
|
690
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
691
|
+
shiftKey: #{shift || need_shift?(top_modifiers)}}));
|
692
|
+
JAVASCRIPT
|
693
|
+
cjs << <<~JAVASCRIPT
|
694
|
+
events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
|
695
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
696
|
+
shiftKey: #{shift || need_shift?(top_modifiers)}}));
|
697
|
+
JAVASCRIPT
|
698
|
+
cjs << <<~JAVASCRIPT
|
699
|
+
events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
|
700
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
701
|
+
shiftKey: #{shift || need_shift?(top_modifiers)}}));
|
702
|
+
JAVASCRIPT
|
703
|
+
end
|
704
|
+
elsif key.is_a?(Symbol)
|
705
|
+
if %i[ctrl Ctrl].include?(key)
|
706
|
+
key = :control
|
707
|
+
elsif %i[command Command Meta].include?(key)
|
708
|
+
key = :meta
|
709
|
+
elsif %i[divide Divide].include?(key)
|
710
|
+
key = :numpad_divide
|
711
|
+
elsif %i[decimal Decimal].include?(key)
|
712
|
+
key = :numpad_decimal
|
713
|
+
elsif %i[left right up down].include?(key)
|
714
|
+
key = "arrow_#{key}".to_sym
|
715
|
+
end
|
716
|
+
if %i[alt alt_left alt_right control control_left control_rigth meta meta_left meta_right shift shift_left shift_right].include?(key)
|
717
|
+
top_modifiers << key
|
718
|
+
cjs << <<~JAVASCRIPT
|
719
|
+
events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
|
720
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
721
|
+
shiftKey: #{need_shift?(top_modifiers)}}));
|
722
|
+
JAVASCRIPT
|
723
|
+
else
|
724
|
+
cjs << <<~JAVASCRIPT
|
725
|
+
events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
|
726
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
727
|
+
shiftKey: #{need_shift?(top_modifiers)}}));
|
728
|
+
JAVASCRIPT
|
729
|
+
cjs << <<~JAVASCRIPT
|
730
|
+
events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
|
731
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
732
|
+
shiftKey: #{need_shift?(top_modifiers)}}));
|
733
|
+
JAVASCRIPT
|
734
|
+
cjs << <<~JAVASCRIPT
|
735
|
+
events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
|
736
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
737
|
+
shiftKey: #{need_shift?(top_modifiers)}}));
|
738
|
+
JAVASCRIPT
|
739
|
+
end
|
740
|
+
elsif key.is_a?(Array)
|
741
|
+
modifiers = []
|
742
|
+
key.each do |k|
|
743
|
+
if k.is_a?(Symbol)
|
744
|
+
if %i[ctrl Ctrl].include?(k)
|
745
|
+
k = :control
|
746
|
+
elsif %i[command Command Meta].include?(k)
|
747
|
+
k = :meta
|
748
|
+
elsif %i[divide Divide].include?(k)
|
749
|
+
k = :numpad_divide
|
750
|
+
elsif %i[decimal Decimal].include?(k)
|
751
|
+
k = :numpad_decimal
|
752
|
+
elsif %i[left right up down].include?(key)
|
753
|
+
k = "arrow_#{key}".to_sym
|
754
|
+
end
|
755
|
+
if %i[alt alt_left alt_right control control_left control_rigth meta meta_left meta_right shift shift_left shift_right].include?(k)
|
756
|
+
modifiers << k
|
757
|
+
cjs << <<~JAVASCRIPT
|
758
|
+
events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
|
759
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
760
|
+
shiftKey: #{need_shift?(modifiers)}}));
|
761
|
+
JAVASCRIPT
|
762
|
+
else
|
763
|
+
cjs << <<~JAVASCRIPT
|
764
|
+
events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
|
765
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
766
|
+
shiftKey: #{need_shift?(modifiers)}}));
|
767
|
+
JAVASCRIPT
|
768
|
+
cjs << <<~JAVASCRIPT
|
769
|
+
events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
|
770
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
771
|
+
shiftKey: #{need_shift?(modifiers)}}));
|
772
|
+
JAVASCRIPT
|
773
|
+
cjs << <<~JAVASCRIPT
|
774
|
+
events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
|
775
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
776
|
+
shiftKey: #{need_shift?(modifiers)}}));
|
777
|
+
JAVASCRIPT
|
778
|
+
end
|
779
|
+
elsif k.is_a?(String)
|
780
|
+
k.each_char do |c|
|
781
|
+
shift = !! /[[:upper:]]/.match(c)
|
782
|
+
cjs << <<~JAVASCRIPT
|
783
|
+
events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
|
784
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
785
|
+
shiftKey: #{shift || need_shift?(modifiers)}}));
|
786
|
+
JAVASCRIPT
|
787
|
+
cjs << <<~JAVASCRIPT
|
788
|
+
events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
|
789
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
790
|
+
shiftKey: #{shift || need_shift?(modifiers)}}));
|
791
|
+
JAVASCRIPT
|
792
|
+
cjs << <<~JAVASCRIPT
|
793
|
+
events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
|
794
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
795
|
+
shiftKey: #{shift || need_shift?(modifiers)}}));
|
796
|
+
JAVASCRIPT
|
797
|
+
end
|
798
|
+
end
|
799
|
+
end
|
800
|
+
modifiers.reverse.each do |k|
|
801
|
+
cjs << <<~JAVASCRIPT
|
802
|
+
events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
|
803
|
+
altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
|
804
|
+
shiftKey: #{need_shift?(modifiers)}}));
|
805
|
+
JAVASCRIPT
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
top_modifiers.reverse.each do |key|
|
810
|
+
cjs << <<~JAVASCRIPT
|
811
|
+
events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
|
812
|
+
altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
|
813
|
+
shiftKey: #{need_shift?(top_modifiers)}}));
|
814
|
+
JAVASCRIPT
|
815
|
+
end
|
816
|
+
cjs << <<~JAVASCRIPT
|
817
|
+
for (i=0; i<events.length; i++) {
|
818
|
+
window.document.dispatchEvent(events[i]);
|
819
|
+
}
|
820
|
+
JAVASCRIPT
|
821
|
+
@context.exec cjs
|
822
|
+
end
|
823
|
+
|
824
|
+
def document_url(document)
|
825
|
+
@context.eval "AllDomHandles[#{document.handle}].window.location.href"
|
826
|
+
end
|
827
|
+
|
828
|
+
def document_user_agent(document)
|
829
|
+
@context.eval "AllDomHandles[#{document.handle}].window.navigator.userAgent"
|
830
|
+
end
|
831
|
+
|
832
|
+
def document_viewport_maximize(document)
|
833
|
+
document_viewport_resize(document, @max_width, @max_height)
|
834
|
+
end
|
835
|
+
|
836
|
+
def document_viewport_resize(document, width, height)
|
837
|
+
width = @max_width if width > @max_width
|
838
|
+
height = @max_height if width > @max_height
|
839
|
+
@context.exec <<~JAVASCRIPT
|
840
|
+
AllDomHandles[#{document.handle}].window.innerWidth = #{width};
|
841
|
+
AllDomHandles[#{document.handle}].window.innerHeight = #{height};
|
842
|
+
JAVASCRIPT
|
843
|
+
end
|
844
|
+
|
845
|
+
def document_viewport_size(document)
|
846
|
+
@context.eval "[AllDomHandles[#{document.handle}].window.innerWidth, AllDomHandles[#{document.handle}].window.innerHeight]"
|
847
|
+
end
|
848
|
+
|
849
|
+
def document_wait_for(document, selector)
|
850
|
+
js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
|
851
|
+
node_data = await <<~JAVASCRIPT
|
852
|
+
var node = null;
|
853
|
+
var start_time = new Date();
|
854
|
+
var resolver = function(resolve) {
|
855
|
+
node = AllDomHandles[#{document.handle}].window.document.querySelector("#{js_escaped_selector}");
|
856
|
+
if (node) {
|
857
|
+
var node_handle = RegisterElementHandle(node);
|
858
|
+
var tag = node.tagName.toLowerCase();
|
859
|
+
var type = null;
|
860
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
861
|
+
LastResult = {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
|
862
|
+
resolve(true);
|
863
|
+
}
|
864
|
+
else if ((new Date() - start_time) > #{@jsdom_timeout}) { resolve(true); }
|
865
|
+
else { setTimeout(resolver, #{@jsdom_reaction_timeout}, resolve) }
|
866
|
+
};
|
867
|
+
var promise = new Promise(function(resolve, reject){ resolver(resolve); });
|
868
|
+
await promise;
|
869
|
+
JAVASCRIPT
|
870
|
+
if node_data
|
871
|
+
node_data[:css_selector] = selector
|
872
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
873
|
+
end
|
874
|
+
end
|
875
|
+
|
876
|
+
def document_wait_for_xpath(document, query)
|
877
|
+
js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
|
878
|
+
node_data = await <<~JAVASCRIPT
|
879
|
+
var node = null;
|
880
|
+
var start_time = new Date();
|
881
|
+
var resolver = function(resolve) {
|
882
|
+
var window = AllDomHandles[#{document.handle}].window;
|
883
|
+
var document = window.document;
|
884
|
+
var xpath_result = document.evaluate("#{js_escaped_query}", document, null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
885
|
+
node = xpath_result.singleNodeValue;
|
886
|
+
if (node) {
|
887
|
+
var node_handle = RegisterElementHandle(node);
|
888
|
+
var tag = node.tagName.toLowerCase();
|
889
|
+
var type = null;
|
890
|
+
if (tag === 'input') { type = node.getAttribute('type'); }
|
891
|
+
LastResult = {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
|
892
|
+
resolve(true);
|
893
|
+
}
|
894
|
+
else if ((new Date() - start_time) > #{@jsdom_timeout}) { resolve(true); }
|
895
|
+
else { setTimeout(resolver, #{@jsdom_reaction_timeout}, resolve) }
|
896
|
+
};
|
897
|
+
var promise = new Promise(function(resolve, reject){ resolver(resolve); });
|
898
|
+
await promise;
|
899
|
+
JAVASCRIPT
|
900
|
+
if node_data
|
901
|
+
node_data[:xpath_query] = query
|
902
|
+
Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
|
903
|
+
end
|
904
|
+
end
|
905
|
+
end
|
906
|
+
end
|
907
|
+
end
|
908
|
+
end
|