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,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
|