isomorfeus-puppetmaster 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +191 -0
  3. data/lib/isomorfeus-puppetmaster.rb +43 -0
  4. data/lib/isomorfeus/puppetmaster.rb +62 -0
  5. data/lib/isomorfeus/puppetmaster/console_message.rb +19 -0
  6. data/lib/isomorfeus/puppetmaster/cookie.rb +46 -0
  7. data/lib/isomorfeus/puppetmaster/document.rb +160 -0
  8. data/lib/isomorfeus/puppetmaster/driver/jsdom.rb +370 -0
  9. data/lib/isomorfeus/puppetmaster/driver/jsdom_document.rb +908 -0
  10. data/lib/isomorfeus/puppetmaster/driver/jsdom_node.rb +836 -0
  11. data/lib/isomorfeus/puppetmaster/driver/puppeteer.rb +401 -0
  12. data/lib/isomorfeus/puppetmaster/driver/puppeteer_document.rb +944 -0
  13. data/lib/isomorfeus/puppetmaster/driver/puppeteer_node.rb +866 -0
  14. data/lib/isomorfeus/puppetmaster/driver_registration.rb +19 -0
  15. data/lib/isomorfeus/puppetmaster/dsl.rb +40 -0
  16. data/lib/isomorfeus/puppetmaster/errors.rb +90 -0
  17. data/lib/isomorfeus/puppetmaster/iframe.rb +17 -0
  18. data/lib/isomorfeus/puppetmaster/node.rb +241 -0
  19. data/lib/isomorfeus/puppetmaster/node/checkbox.rb +17 -0
  20. data/lib/isomorfeus/puppetmaster/node/content_editable.rb +18 -0
  21. data/lib/isomorfeus/puppetmaster/node/filechooser.rb +9 -0
  22. data/lib/isomorfeus/puppetmaster/node/input.rb +21 -0
  23. data/lib/isomorfeus/puppetmaster/node/radiobutton.rb +13 -0
  24. data/lib/isomorfeus/puppetmaster/node/select.rb +36 -0
  25. data/lib/isomorfeus/puppetmaster/node/textarea.rb +7 -0
  26. data/lib/isomorfeus/puppetmaster/request.rb +17 -0
  27. data/lib/isomorfeus/puppetmaster/response.rb +26 -0
  28. data/lib/isomorfeus/puppetmaster/rspec/features.rb +23 -0
  29. data/lib/isomorfeus/puppetmaster/rspec/matcher_proxies.rb +80 -0
  30. data/lib/isomorfeus/puppetmaster/rspec/matchers.rb +164 -0
  31. data/lib/isomorfeus/puppetmaster/rspec/matchers/base.rb +98 -0
  32. data/lib/isomorfeus/puppetmaster/rspec/matchers/become_closed.rb +33 -0
  33. data/lib/isomorfeus/puppetmaster/rspec/matchers/compound.rb +88 -0
  34. data/lib/isomorfeus/puppetmaster/rspec/matchers/have_current_path.rb +29 -0
  35. data/lib/isomorfeus/puppetmaster/rspec/matchers/have_selector.rb +69 -0
  36. data/lib/isomorfeus/puppetmaster/rspec/matchers/have_text.rb +33 -0
  37. data/lib/isomorfeus/puppetmaster/rspec/matchers/have_title.rb +29 -0
  38. data/lib/isomorfeus/puppetmaster/rspec/matchers/match_selector.rb +27 -0
  39. data/lib/isomorfeus/puppetmaster/rspec/matchers/match_style.rb +38 -0
  40. data/lib/isomorfeus/puppetmaster/self_forwardable.rb +31 -0
  41. data/lib/isomorfeus/puppetmaster/server.rb +128 -0
  42. data/lib/isomorfeus/puppetmaster/server/checker.rb +40 -0
  43. data/lib/isomorfeus/puppetmaster/server/middleware.rb +60 -0
  44. data/lib/isomorfeus/puppetmaster/server_registration.rb +37 -0
  45. data/lib/isomorfeus/puppetmaster/version.rb +3 -0
  46. metadata +282 -0
@@ -0,0 +1,836 @@
1
+ module Isomorfeus
2
+ module Puppetmaster
3
+ module Driver
4
+ module JsdomNode
5
+ def node_all_text(node)
6
+ @context.eval "AllElementHandles[#{node.handle}].textContent"
7
+ end
8
+
9
+ def node_click(node, x: nil, y: nil, modifiers: nil)
10
+ # modifier_keys: :alt, :control, :meta, :shift
11
+ # raise Isomorfeus::Pupppetmaster::InvalidActionError.new(:click) unless visible?
12
+ modifiers = [modifiers] if modifiers.is_a?(Symbol)
13
+ modifiers = [] unless modifiers
14
+ modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
15
+ @context.exec <<~JAVASCRIPT
16
+ var options = {button: 0, bubbles: true, cancelable: true};
17
+ var window = AllDomHandles[#{node.document.handle}].window;
18
+ var modifiers = #{modifiers};
19
+ if (modifiers.includes('meta')) { options['metaKey'] = true; }
20
+ if (modifiers.includes('control')) { options['ctrlKey'] = true; }
21
+ if (modifiers.includes('shift')) { options['shiftKey'] = true; }
22
+ if (modifiers.includes('alt')) { options['altKey'] = true; }
23
+ var x = #{x ? x : 'null'};
24
+ var y = #{y ? y : 'null'};
25
+ if (x && y) {
26
+ options['clientX'] = x;
27
+ options['clientY'] = y;
28
+ }
29
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('mousedown', options));
30
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('mouseup', options));
31
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('click', options));
32
+ JAVASCRIPT
33
+ end
34
+
35
+ def node_disabled?(node)
36
+ @context.exec <<~JAVASCRIPT
37
+ var window = AllDomHandles[#{node.document.handle}].window;
38
+ var node = AllElementHandles[#{node.handle}];
39
+ const xpath = `parent::optgroup[@disabled] | ancestor::select[@disabled] | parent::fieldset[@disabled] |
40
+ ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]`;
41
+ return node.disabled || window.document.evaluate(xpath, node, null, window.XPathResult.BOOLEAN_TYPE, null).booleanValue;
42
+ JAVASCRIPT
43
+ end
44
+
45
+ def node_dispatch_event(node, name, event_type = nil, **options)
46
+ raise ArgumentError, 'Unknown event' unless Isomorfeus::Puppetmaster::Driver::Jsdom::EVENTS.key?(name.to_sym) || event_type
47
+ event_type, opts = *Isomorfeus::Puppetmaster::Driver::Jsdom::EVENTS[name.to_sym] if event_type.nil?
48
+ opts.merge!(options)
49
+ final_options = options.map { |k,v| "#{k}: '#{v}'" }
50
+ @context.exec <<~JAVASCRIPT
51
+ var window = AllDomHandles[#{node.document.handle}].window;
52
+ var event = new window.#{event_type}('#{name}', { #{final_options.join(', ')} });
53
+ AllElementHandles[#{node.handle}].dispatchEvent(event);
54
+ JAVASCRIPT
55
+ end
56
+
57
+ def node_double_click(node, x: nil, y: nil, modifiers: nil)
58
+ # modifier_keys: :alt, :control, :meta, :shift
59
+ # offset: { x: int, y: int }
60
+ modifiers = [modifiers] if modifiers.is_a?(Symbol)
61
+ modifiers = [] unless modifiers
62
+ modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
63
+ @context.exec <<~JAVASCRIPT
64
+ var options = {button: 0, bubbles: true, cancelable: true};
65
+ var window = AllDomHandles[#{node.document.handle}].window;
66
+ var modifiers = #{modifiers};
67
+ if (modifiers.includes('meta')) { options['metaKey'] = true; }
68
+ if (modifiers.includes('control')) { options['ctrlKey'] = true; }
69
+ if (modifiers.includes('shift')) { options['shiftKey'] = true; }
70
+ if (modifiers.includes('alt')) { options['altKey'] = true; }
71
+ var x = #{x ? x : 'null'};
72
+ var y = #{y ? y : 'null'};
73
+ if (x && y) {
74
+ options['clientX'] = x;
75
+ options['clientY'] = y;
76
+ }
77
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('mousedown', options));
78
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('mouseup', options));
79
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('dblclick', options));
80
+ JAVASCRIPT
81
+ end
82
+
83
+ def node_drag_to(node, other_node)
84
+ # TODO
85
+ if node[:draggable]
86
+ await <<~JAVASCRIPT
87
+ var window = AllDomHandles[#{node.document.handle}].window;
88
+ window.document.addEventListener('mousedown', event => {
89
+ window.jsdom_mousedown_prevented = event.defaultPrevented;
90
+ }, { once: true, passive: true });
91
+ JAVASCRIPT
92
+ # TODO use scrollIntoView once chromium bug is fixed
93
+ # https://bugs.chromium.org/p/chromium/issues/detail?id=939740&can=2&start=0&num=100&q=mousemove%20scrollintoview&colspec=ID%20Type%20Status%20Priority%20Milestone%20Owner%20Summary&groupby=&sort=
94
+ await <<~JAVASCRIPT
95
+ var window = AllDomHandles[#{node.document.handle}].window;
96
+ var node = AllElementHandles[#{node.handle}];
97
+ var other_node = AllElementHandles[#{other_node.handle}];
98
+ var n = node;
99
+ var top = n.offsetTop, left = n.offsetLeft, width = n.offsetWidth, height = n.offsetHeight;
100
+ while(n.offsetParent) { n = n.offsetParent; top += n.offsetTop; left += n.offsetLeft; }
101
+ var node_in_view = (top >= window.pageYOffset && left >= window.pageXOffset &&
102
+ (top + height) <= (window.pageYOffset + window.innerHeight) && (left + width) <= (window.pageXOffset + window.innerWidth));
103
+ if (!node_in_view) { node.scrollTo(0,0); };
104
+ setTimeout(function(){
105
+ var client_rect = node.getBoundingClientRect();
106
+ var x = (client_rect.left + (client_rect.width / 2));
107
+ var y = (client_rect.top + (client_rect.height / 2));
108
+ node.dispatchEvent(new window.MouseEvent('mousemove', {clientX: x, clientY: y, bubbles: true, cancelable: true}));
109
+ setTimeout(function(){
110
+ node.dispatchEvent(new window.MouseEvent('mousedown', {clientX: x, clientY: y, bubbles: true, cancelable: true}));
111
+ if (window.jsdom_mousedown_prevented) {
112
+ n = other_node;
113
+ top = n.offsetTop; left = n.offsetLeft; width = n.offsetWidth; height = n.offsetHeight;
114
+ while(n.offsetParent) { n = n.offsetParent; top += n.offsetTop; left += n.offsetLeft; }
115
+ var node_in_view = (top >= window.pageYOffset && left >= window.pageXOffset &&
116
+ (top + height) <= (window.pageYOffset + window.innerHeight) && (left + width) <= (window.pageXOffset + window.innerWidth));
117
+ if (!node_in_view) { other_node.scrollTo(0,0) };
118
+ setTimeout(function(){
119
+ client_rect = other_node.getBoundingClientRect();
120
+ x = (client_rect.left + (client_rect.width / 2));
121
+ y = (client_rect.top + (client_rect.height / 2));
122
+ node.dispatchEvent(new window.MouseEvent('mousemove', {clientX: x, clientY: y, bubbles: true, cancelable: true}));
123
+ setTimeout(function(){
124
+ node.dispatchEvent(new window.MouseEvent('mouseup', {clientX: x, clientY: y, bubbles: true, cancelable: true}));
125
+ }, #{@jsdom_reaction_timeout/2});
126
+ }, #{@jsdom_reaction_timeout});
127
+ } else {
128
+ var dt = new window.DataTransfer();
129
+ if (node.tagName == 'A'){ dt.setData('text/uri-list', node.href); dt.setData('text', node.href); }
130
+ if (node.tagName == 'IMG'){ dt.setData('text/uri-list', node.src); dt.setData('text', node.src); }
131
+ var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
132
+ var dragEvent = new window.DragEvent('dragstart', opts);
133
+ node.dispatchEvent(dragEvent);
134
+ n = other_node;
135
+ top = n.offsetTop; left = n.offsetLeft; width = n.offsetWidth; height = n.offsetHeight;
136
+ while(n.offsetParent) { n = n.offsetParent; top += n.offsetTop; left += n.offsetLeft; }
137
+ var node_in_view = (top >= window.pageYOffset && left >= window.pageXOffset &&
138
+ (top + height) <= (window.pageYOffset + window.innerHeight) && (left + width) <= (window.pageXOffset + window.innerWidth));
139
+ if (!node_in_view) { other_node.scrollTo(0,0); };
140
+ setTimeout(function(){
141
+ var rect = node.getBoundingClientRect()
142
+ var node_center = new window.DOMPoint((rect.left + rect.right)/2, (rect.top + rect.bottom)/2);
143
+ var other_rect = other_node.getBoundingClientRect();
144
+ var other_point = new window.DOMPoint((other_rect.left + other_rect.right)/2, (other_rect.top + other_rect.bottom)/2);
145
+ var entry_point = null;
146
+ var slope = (other_point.y - other_point.y) / (other_point.x - node_center.x);
147
+ if (other_point.x <= other_point.x) { // left side
148
+ var minXy = slope * (other_rect.left - node_center.x) + node_center.y;
149
+ if (other_rect.top <= minXy && minXy <= other_rect.bottom) { entry_point = new window.DOMPoint(other_rect.left, minXy); }
150
+ }
151
+ if (node_center.x >= other_point.x) { // right side
152
+ var maxXy = slope * (other_rect.right - node_center.x) + node_center.y;
153
+ if (other_rect.top <= maxXy && maxXy <= other_rect.bottom) { entry_point = new window.DOMPoint(other_rect.right, maxXy); }
154
+ }
155
+ if (node_center.y <= other_point.y) { // top side
156
+ var minYx = (other_point.top - node_center.y) / slope + node_center.x;
157
+ if (other_rect.left <= minYx && minYx <= other_rect.right) { entry_point = new window.DOMPoint(minYx, other_rect.top); }
158
+ }
159
+ if (node_center.y >= other_point.y) { // bottom side
160
+ var maxYx = (other_rect.bottom - node_center.y) / slope + node_center.x;
161
+ if (other_rect.left <= maxYx && maxYx <= other_rect.right) { entry_point = new window.DOMPoint(maxYx, other_rect.bottom); }
162
+ }
163
+ if (!entry_point) {
164
+ entry_point = new window.DOMPoint(node_center.x, node_center.y);
165
+ }
166
+ var drag_over_event = new window.DragEvent('dragover', {clientX: entry_point.x, clientY: entry_point.y, bubbles: true, cancelable: true});
167
+ other_node.dispatchEvent(drag_over_event);
168
+ var other_center = new window.DOMPoint((other_rect.left + other_rect.right)/2, (other_rect.top + other_rect.bottom)/2);
169
+ drag_over_event = new window.DragEvent('dragover', {clientX: targetCenter.x, clientY: targetCenter.y, bubbles: true, cancelable: true});
170
+ other_node.dispatchEvent(drag_over_event);
171
+ other_node.dispatchEvent(new window.DragEvent('dragleave', {bubbles: true, cancelable: true}));
172
+ if (drag_over_event.defaultPrevented) {
173
+ other_node.dispatchEvent(new window.DragEvent('drop', {bubbles: true, cancelable: true}));
174
+ }
175
+ node.dispatchEvent(new window.DragEvent('dragend', {bubbles: true, cancelable: true}));
176
+ client_rect = other_node.getBoundingClientRect();
177
+ x = (client_rect.left + (client_rect.width / 2));
178
+ y = (client_rect.top + (client_rect.height / 2));
179
+ node.dispatchEvent(new window.MouseEvent('mouseup', {clientX: x, clientY: y, bubbles: true, cancelable: true}));
180
+ }, #{@jsdom_reaction_timeout});
181
+ }
182
+ }, #{@jsdom_reaction_timeout/2});
183
+ }, #{@jsdom_reaction_timeout});
184
+ JAVASCRIPT
185
+ sleep (@reaction_timeout * 3) + 0.2
186
+ else
187
+ await <<~JAVASCRIPT
188
+ var window = AllDomHandles[#{node.document.handle}].window;
189
+ var node = AllElementHandles[#{node.handle}];
190
+ var other_node = AllElementHandles[#{other_node.handle}];
191
+ var n = node;
192
+ var top = n.offsetTop, left = n.offsetLeft, width = n.offsetWidth, height = n.offsetHeight;
193
+ while(n.offsetParent) { n = n.offsetParent; top += n.offsetTop; left += n.offsetLeft; }
194
+ var node_in_view = (top >= window.pageYOffset && left >= window.pageXOffset &&
195
+ (top + height) <= (window.pageYOffset + window.innerHeight) && (left + width) <= (window.pageXOffset + window.innerWidth));
196
+ if (!node_in_view) { res = (n === node); node.scrollTo(0,0); };
197
+ setTimeout(function() {
198
+ var client_rect = node.getBoundingClientRect();
199
+ var x = (client_rect.left + (client_rect.width / 2));
200
+ var y = (client_rect.top + (client_rect.height / 2));
201
+ node.dispatchEvent(new window.MouseEvent('mousemove', {clientX: x, clientY: y, bubbles: true, cancelable: true}));
202
+ setTimeout(function() {
203
+ node.dispatchEvent(new window.MouseEvent('mousedown', {button: 0, buttons: 1, clientX: x, clientY: y, bubbles: true, cancelable: true}));
204
+ var n = other_node;
205
+ var top = n.offsetTop, left = n.offsetLeft, width = n.offsetWidth, height = n.offsetHeight;
206
+ while(n.offsetParent) { n = n.offsetParent; top += n.offsetTop; left += n.offsetLeft; }
207
+ var other_node_in_view = (top >= window.pageYOffset && left >= window.pageXOffset &&
208
+ (top + height) <= (window.pageYOffset + window.innerHeight) && (left + width) <= (window.pageXOffset + window.innerWidth));
209
+ if (!other_node_in_view) { other_node.scrollTo(0,0); };
210
+ setTimeout(function() {
211
+ var other_client_rect = other_node.getBoundingClientRect();
212
+ var x = (other_client_rect.left + (other_client_rect.width / 2));
213
+ var y = (other_client_rect.top + (other_client_rect.height / 2));
214
+ node.dispatchEvent(new window.MouseEvent('mousemove', {button: 0, buttons: 1, clientX: x, clientY: y, bubbles: true, cancelable: true}));
215
+ setTimeout(function() {
216
+ node.dispatchEvent(new window.MouseEvent('mouseup', {button: 0, buttons: 1, clientX: x, clientY: y, bubbles: true, cancelable: true}));
217
+ }, #{@jsdom_reaction_timeout/2});
218
+ }, #{@jsdom_reaction_timeout});
219
+ }, #{@jsdom_reaction_timeout/2});
220
+ }, #{@jsdom_reaction_timeout});
221
+ JAVASCRIPT
222
+ sleep (@reaction_timeout * 3) + 0.2
223
+ end
224
+ end
225
+
226
+ def node_equal(node, other_node)
227
+ @context.eval "AllElementHandles[#{node.handle}] === AllElementHandles[#{other_node.handle}]"
228
+ end
229
+
230
+ def node_execute_script(node, script, *args)
231
+ # TODO
232
+ await <<~JAVASCRIPT
233
+ var node_handle = #{node.handle};
234
+ await AllElementHandles[node_handle].executionContext().evaluateHandle((node, arguments) => {
235
+ arguments.unshift(node);
236
+ #{script}
237
+ }, AllElementHandles[node_handle], #{args[1..-1]});
238
+ JAVASCRIPT
239
+ end
240
+
241
+ def node_evaluate_script(node, script, *args)
242
+ # TODO
243
+ await <<~JAVASCRIPT
244
+ var node_handle = #{node.handle};
245
+ await AllElementHandles[node_handle].executionContext().evaluateHandle((node, arguments) => {
246
+ arguments.unshift(node);
247
+ return #{script};
248
+ }, AllElementHandles[node_handle], #{args[1..-1]});
249
+ LastResult = await handle.jsonValue();
250
+ JAVASCRIPT
251
+ end
252
+
253
+ def node_find(node, selector)
254
+ js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
255
+ node_data = @context.exec <<~JAVASCRIPT
256
+ var node = AllElementHandles[#{node.handle}].querySelector("#{js_escaped_selector}");
257
+ if (node) {
258
+ var node_handle = RegisterElementHandle(node);
259
+ var tag = node.tagName.toLowerCase();
260
+ var type = null;
261
+ if (tag === 'input') { type = node.getAttribute('type'); }
262
+ return {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
263
+ }
264
+ JAVASCRIPT
265
+ if node_data
266
+ node_data[:css_selector] = selector
267
+ Isomorfeus::Puppetmaster::Node.new_by_tag(self, node.document, node_data)
268
+ else
269
+ raise Isomorfeus::Puppetmaster::ElementNotFound.new(selector)
270
+ end
271
+ rescue ExecJS::RuntimeError => e
272
+ raise determine_error(e.message)
273
+ end
274
+
275
+ def node_find_all(node, selector)
276
+ js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
277
+ node_data_array = @context.exec <<~JAVASCRIPT
278
+ var node_array = AllElementHandles[#{node.handle}].querySelectorAll("#{js_escaped_selector}");
279
+ var node_data_array = [];
280
+ if (node_array) {
281
+ for (var i=0; i<node_array.length; i++) {
282
+ var node_handle = RegisterElementHandle(node_array[i]);
283
+ var tag = node_array[i].tagName.toLowerCase();
284
+ var type = null;
285
+ if (tag === 'input') { type = node_array[i].getAttribute('type'); }
286
+ node_data_array.push({handle: node_handle, tag: tag, type: type, content_editable: node_array[i].isContentEditable});
287
+ }
288
+ }
289
+ return node_data_array;
290
+ JAVASCRIPT
291
+ node_data_array.map do |node_data|
292
+ node_data[:css_selector] = selector
293
+ Isomorfeus::Puppetmaster::Node.new_by_tag(self, node.document, node_data)
294
+ end
295
+ end
296
+
297
+ def node_find_all_xpath(node, query)
298
+ js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
299
+ node_data_array = @context.exec <<~JAVASCRIPT
300
+ var window = AllDomHandles[#{node.document.handle}].window;
301
+ var document = window.document;
302
+ var xpath_result = document.evaluate("#{js_escaped_query}", AllElementHandles[#{node.handle}], null, window.XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
303
+ var node;
304
+ var node_data_array = [];
305
+ while (node = xpath_result.iterateNext) {
306
+ var node_handle = RegisterElementHandle(node);
307
+ var tag = node.tagName.toLowerCase();
308
+ var type = null;
309
+ if (tag === 'input') { type = node.getAttribute('type'); }
310
+ node_data_array.push({handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable});
311
+ }
312
+ return node_data_array;
313
+ JAVASCRIPT
314
+ node_data_array.map do |node_data|
315
+ node_data[:xpath_query] = query
316
+ Isomorfeus::Puppetmaster::Node.new_by_tag(self, node.document, node_data)
317
+ end
318
+ end
319
+
320
+ def node_find_xpath(node, query)
321
+ js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
322
+ node_data = @context.exec <<~JAVASCRIPT
323
+ var window = AllDomHandles[#{node.document.handle}].window;
324
+ var document = window.document;
325
+ var xpath_result = document.evaluate("#{js_escaped_query}", AllElementHandles[#{node.handle}], null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
326
+ var node = xpath_result.singleNodeValue;
327
+ if (node) {
328
+ var node_handle = RegisterElementHandle(node);
329
+ var tag = node.tagName.toLowerCase();
330
+ var type = null;
331
+ if (tag === 'input') { type = node.getAttribute('type'); }
332
+ return {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
333
+ }
334
+ JAVASCRIPT
335
+ if node_data
336
+ node_data[:xpath_query] = query
337
+ Isomorfeus::Puppetmaster::Node.new_by_tag(self, node.document, node_data)
338
+ else
339
+ raise Isomorfeus::Puppetmaster::ElementNotFound.new(query)
340
+ end
341
+ rescue ExecJS::ProgramError => e
342
+ raise determine_error('invalid xpath query')
343
+ end
344
+
345
+ def node_focus(node)
346
+ await "await AllElementHandles[#{node.handle}].focus();"
347
+ end
348
+
349
+ def node_get_attribute(node, attribute)
350
+ @context.eval "AllElementHandles[#{node.handle}].getAttribute('#{attribute}')"
351
+ end
352
+
353
+ def node_get_property(node, property)
354
+ @context.eval "AllElementHandles[#{node.handle}]['#{property}']"
355
+ end
356
+
357
+ def node_hover(node)
358
+ @context.exec "AllElementHandles[#{node.handle}].hover()"
359
+ end
360
+
361
+ def node_in_viewport?(node)
362
+ await <<~JAVASCRIPT
363
+ var node_handle = #{node.handle};
364
+ var handle = await AllElementHandles[node_handle].executionContext().evaluateHandle(function(node) {
365
+ var top = node.offsetTop, left = node.offsetLeft, width = node.offsetWidth, height = node.offsetHeight;
366
+ while(node.offsetParent) { node = node.offsetParent; top += node.offsetTop; left += node.offsetLeft; }
367
+ return (top >= window.pageYOffset && left >= window.pageXOffset &&
368
+ (top + height) <= (window.pageYOffset + window.innerHeight) && (left + width) <= (window.pageXOffset + window.innerWidth));
369
+ }, AllElementHandles[node_handle]);
370
+ LastResult = await handle.jsonValue();
371
+ JAVASCRIPT
372
+ end
373
+
374
+ def node_render_base64(_node, **_options)
375
+ raise Isomorfeus::Puppetmaster::NotSupported
376
+ end
377
+
378
+ def node_right_click(node, x: nil, y: nil, modifiers: nil)
379
+ # modifier_keys: :alt, :control, :meta, :shift
380
+ modifiers = [modifiers] if modifiers.is_a?(Symbol)
381
+ modifiers = [] unless modifiers
382
+ modifiers = modifiers.map {|key| key.to_s.camelize(:lower) }
383
+ await <<~JAVASCRIPT
384
+ var options = {button: 2, bubbles: true, cancelable: true};
385
+ var window = AllDomHandles[#{node.document.handle}].window;
386
+ var modifiers = #{modifiers};
387
+ if (modifiers.includes('meta')) { options['metaKey'] = true; }
388
+ if (modifiers.includes('control')) { options['ctrlKey'] = true; }
389
+ if (modifiers.includes('shift')) { options['shiftKey'] = true; }
390
+ if (modifiers.includes('alt')) { options['altKey'] = true; }
391
+ var x = #{x ? x : 'null'};
392
+ var y = #{y ? y : 'null'};
393
+ if (x && y) {
394
+ options['clientX'] = x;
395
+ options['clientY'] = y;
396
+ }
397
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('mousedown', options));
398
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('mouseup', options));
399
+ AllElementHandles[#{node.handle}].dispatchEvent(new window.MouseEvent('contextmenu', options));
400
+ JAVASCRIPT
401
+ end
402
+
403
+ def node_inner_html(node)
404
+ @context.eval("AllElementHandles[#{node.handle}].innerHTML")
405
+ end
406
+
407
+ def node_save_screenshot(_node, _path, **_options)
408
+ raise Isomorfeus::Puppetmaster::NotSupported
409
+ end
410
+
411
+ def node_scroll_by(node, x, y)
412
+ await <<~JAVASCRIPT
413
+ await AllElementHandles[#{node.handle}].executionContext().evaluateHandle(function(node) {
414
+ node.scrollBy(#{x}, #{y});
415
+ }, AllElementHandles[#{node.handle}]);
416
+ JAVASCRIPT
417
+ end
418
+
419
+ def node_scroll_into_view(node)
420
+ await <<~JAVASCRIPT
421
+ await AllElementHandles[#{node.handle}].executionContext().evaluateHandle(function(node) {
422
+ node.scrollIntoView();
423
+ }, AllElementHandles[#{node.handle}]);
424
+ JAVASCRIPT
425
+ end
426
+
427
+ def node_scroll_to(node, x, y)
428
+ await <<~JAVASCRIPT
429
+ await AllElementHandles[#{node.handle}].executionContext().evaluateHandle(function(node) {
430
+ node.scrollTo(#{x}, #{y});
431
+ }, AllElementHandles[#{node.handle}]);
432
+ JAVASCRIPT
433
+ end
434
+
435
+ def node_select(node)
436
+ # In the case of an OPTION tag, the change event should come
437
+ # from the parent SELECT
438
+ await <<~JAVASCRIPT
439
+ await AllElementHandles[#{node.handle}].executionContext().evaluateHandle(function(node){
440
+ var xpath = "parent::optgroup[@disabled] | ancestor::select[@disabled] | parent::fieldset[@disabled] | \
441
+ ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
442
+ if (node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue) { return false; }
443
+ else if (node.value == false && !node.parentNode.multiple) { return false; }
444
+ else {
445
+ node.parentNode.dispatchEvent(new FocusEvent('focus',{bubbles: true, cancelable: true}));
446
+ node.selected = true;
447
+ var element;
448
+ if (node.nodeName == "OPTION") {
449
+ element = node.parentNode;
450
+ if (element.nodeName == "OPTGROUP") { element = element.parentNode; }
451
+ } else { element = node; }
452
+ element.dispatchEvent(new Event('change',{bubbles: true, cancelable: false}));
453
+ node.parentNode.dispatchEvent(new FocusEvent('blur',{bubbles: true, cancelable: true}));
454
+ return true;
455
+ }
456
+ }, AllElementHandles[#{node.handle}]);
457
+ JAVASCRIPT
458
+ end
459
+
460
+ def node_style(node, *styles)
461
+ await <<~JAVASCRIPT
462
+ var handle = await AllElementHandles[#{node.handle}].executionContext().evaluateHandle(function(node, styles){
463
+ var style = window.getComputedStyle(node);
464
+ if (styles.length > 0) {
465
+ return styles.reduce(function(res,name) {
466
+ res[name] = style[name];
467
+ return res;
468
+ }, {});
469
+ } else { return style; }
470
+ }, AllElementHandles[#{node.handle}], #{styles});
471
+ LastResult = await handle.jsonValue();
472
+ JAVASCRIPT
473
+ end
474
+
475
+ def node_type_keys(node, *keys)
476
+ cjs = <<~JAVASCRIPT
477
+ AllElementHandles[#{node.handle}].focus();
478
+ var window = AllDomHandles[#{node.document.handle}].window;
479
+ var events = [];
480
+ var chars = '';
481
+ var tag = AllElementHandles[#{node.handle}].tagName;
482
+ JAVASCRIPT
483
+ # new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: character.charCodeAt(0), char: character, shiftKey: false });
484
+ top_modifiers = []
485
+ keys.each do |key|
486
+ if key.is_a?(String)
487
+ key.each_char do |c|
488
+ shift = !! /[[:upper:]]/.match(c)
489
+ cjs << <<~JAVASCRIPT
490
+ events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
491
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
492
+ shiftKey: #{shift || need_shift?(top_modifiers)}}));
493
+ JAVASCRIPT
494
+ cjs << <<~JAVASCRIPT
495
+ events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
496
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
497
+ shiftKey: #{shift || need_shift?(top_modifiers)}}));
498
+ JAVASCRIPT
499
+ # hack to make input actually happen, sort of
500
+ cjs << <<~JAVASCRIPT
501
+ chars = chars + '#{(shift || need_shift?(top_modifiers)) ? c.upcase : c}';
502
+ JAVASCRIPT
503
+ cjs << <<~JAVASCRIPT
504
+ events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
505
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
506
+ shiftKey: #{shift || need_shift?(top_modifiers)}}));
507
+ JAVASCRIPT
508
+ end
509
+ elsif key.is_a?(Symbol)
510
+ if %i[ctrl Ctrl].include?(key)
511
+ key = :control
512
+ elsif %i[command Command Meta].include?(key)
513
+ key = :meta
514
+ elsif %i[divide Divide].include?(key)
515
+ key = :numpad_divide
516
+ elsif %i[decimal Decimal].include?(key)
517
+ key = :numpad_decimal
518
+ elsif %i[left right up down].include?(key)
519
+ key = "arrow_#{key}".to_sym
520
+ end
521
+ if %i[alt alt_left alt_right control control_left control_rigth meta meta_left meta_right shift shift_left shift_right].include?(key)
522
+ top_modifiers << key
523
+ cjs << <<~JAVASCRIPT
524
+ events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
525
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
526
+ shiftKey: #{need_shift?(top_modifiers)}}));
527
+ JAVASCRIPT
528
+ else
529
+ cjs << <<~JAVASCRIPT
530
+ events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
531
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
532
+ shiftKey: #{need_shift?(top_modifiers)}}));
533
+ JAVASCRIPT
534
+ cjs << <<~JAVASCRIPT
535
+ events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
536
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
537
+ shiftKey: #{need_shift?(top_modifiers)}}));
538
+ JAVASCRIPT
539
+ cjs << <<~JAVASCRIPT
540
+ events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
541
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
542
+ shiftKey: #{need_shift?(top_modifiers)}}));
543
+ JAVASCRIPT
544
+ end
545
+ elsif key.is_a?(Array)
546
+ modifiers = []
547
+ key.each do |k|
548
+ if k.is_a?(Symbol)
549
+ if %i[ctrl Ctrl].include?(k)
550
+ k = :control
551
+ elsif %i[command Command Meta].include?(k)
552
+ k = :meta
553
+ elsif %i[divide Divide].include?(k)
554
+ k = :numpad_divide
555
+ elsif %i[decimal Decimal].include?(k)
556
+ k = :numpad_decimal
557
+ elsif %i[left right up down].include?(key)
558
+ k = "arrow_#{key}".to_sym
559
+ end
560
+ if %i[alt alt_left alt_right control control_left control_rigth meta meta_left meta_right shift shift_left shift_right].include?(k)
561
+ modifiers << k
562
+ cjs << <<~JAVASCRIPT
563
+ events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
564
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
565
+ shiftKey: #{need_shift?(modifiers)}}));
566
+ JAVASCRIPT
567
+ else
568
+ cjs << <<~JAVASCRIPT
569
+ events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
570
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
571
+ shiftKey: #{need_shift?(modifiers)}}));
572
+ JAVASCRIPT
573
+ cjs << <<~JAVASCRIPT
574
+ events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
575
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
576
+ shiftKey: #{need_shift?(modifiers)}}));
577
+ JAVASCRIPT
578
+ cjs << <<~JAVASCRIPT
579
+ events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
580
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
581
+ shiftKey: #{need_shift?(modifiers)}}));
582
+ JAVASCRIPT
583
+ end
584
+ elsif k.is_a?(String)
585
+ k.each_char do |c|
586
+ shift = !! /[[:upper:]]/.match(c)
587
+ cjs << <<~JAVASCRIPT
588
+ events.push(new window.KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
589
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
590
+ shiftKey: #{shift || need_shift?(modifiers)}}));
591
+ JAVASCRIPT
592
+ cjs << <<~JAVASCRIPT
593
+ events.push(new window.KeyboardEvent('keypress', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
594
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
595
+ shiftKey: #{shift || need_shift?(modifiers)}}));
596
+ JAVASCRIPT
597
+ # hack to make input actually happen, sort of
598
+ cjs << <<~JAVASCRIPT
599
+ chars = chars + '#{(shift || need_shift?(modifiers)) ? c.upcase : c}';
600
+ JAVASCRIPT
601
+ cjs << <<~JAVASCRIPT
602
+ events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{c}'.charCodeAt(0), char: '#{c}',
603
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
604
+ shiftKey: #{shift || need_shift?(modifiers)}}));
605
+ JAVASCRIPT
606
+ end
607
+ end
608
+ end
609
+ modifiers.reverse.each do |k|
610
+ cjs << <<~JAVASCRIPT
611
+ events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{k.to_s.camelize}', code: '#{k.to_s.camelize}',
612
+ altKey: #{need_alt?(modifiers)}, ctrlKey: #{need_control?(modifiers)}, metaKey: #{need_meta?(modifiers)},
613
+ shiftKey: #{need_shift?(modifiers)}}));
614
+ JAVASCRIPT
615
+ end
616
+ end
617
+ end
618
+ top_modifiers.reverse.each do |key|
619
+ cjs << <<~JAVASCRIPT
620
+ events.push(new window.KeyboardEvent('keyup', { bubbles: true, cancelable: true, key: '#{key.to_s.camelize}', code: '#{key.to_s.camelize}',
621
+ altKey: #{need_alt?(top_modifiers)}, ctrlKey: #{need_control?(top_modifiers)}, metaKey: #{need_meta?(top_modifiers)},
622
+ shiftKey: #{need_shift?(top_modifiers)}}));
623
+ JAVASCRIPT
624
+ end
625
+ cjs << <<~JAVASCRIPT
626
+ for (i=0; i<events.length; i++) {
627
+ AllElementHandles[#{node.handle}].dispatchEvent(events[i]);
628
+ }
629
+ if (tag === 'INPUT' || tag === 'TEXTAREA') {AllElementHandles[#{node.handle}].value = chars }
630
+ JAVASCRIPT
631
+ @context.exec cjs
632
+ end
633
+
634
+ def node_unselect(node)
635
+ # In the case of an OPTION tag, the change event should come
636
+ # from the parent SELECT
637
+ await <<~JAVASCRIPT
638
+ await AllElementHandles[#{node.handle}].executionContext().evaluateHandle(function(node){
639
+ var xpath = "parent::optgroup[@disabled] | ancestor::select[@disabled] | parent::fieldset[@disabled] | \
640
+ ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
641
+ if (node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue) { return false; }
642
+ else if (node.value == false && !node.parentNode.multiple) { return false; }
643
+ else {
644
+ node.parentNode.dispatchEvent(new FocusEvent('focus',{bubbles: true, cancelable: true}));
645
+ node.selected = false;
646
+ var element;
647
+ if (node.nodeName == "OPTION") {
648
+ element = node.parentNode;
649
+ if (element.nodeName == "OPTGROUP") { element = element.parentNode; }
650
+ } else { element = node; }
651
+ element.dispatchEvent(new Event('change',{bubbles: true, cancelable: false}));
652
+ node.parentNode.dispatchEvent(new FocusEvent('blur',{bubbles: true, cancelable: true}));
653
+ return true;
654
+ }
655
+ }, AllElementHandles[#{node.handle}]);
656
+ JAVASCRIPT
657
+ end
658
+
659
+ def node_value(node)
660
+ @context.exec <<~JAVASCRIPT
661
+ var node = AllElementHandles[#{node.handle}];
662
+ if (node.tagName == "SELECT" && node.multiple) {
663
+ var result = []
664
+ for (let i = 0, len = node.children.length; i < len; i++) {
665
+ var option = node.children[i];
666
+ if (option.selected) { result.push(option.value); }
667
+ }
668
+ return result;
669
+ } else if (node.isContentEditable) { return node.textContent; }
670
+ else { return node.value; }
671
+ JAVASCRIPT
672
+ end
673
+
674
+ def node_value=(node, value)
675
+ raise Isomorfeus::Puppetmaster::ReadOnlyElementError if node.readonly?
676
+ real_value = "`#{value}`"
677
+ @context.exec <<~JAVASCRIPT
678
+ var window = AllDomHandles[#{node.document.handle}].window;
679
+ var node = AllElementHandles[#{node.handle}];
680
+ var value = #{real_value};
681
+ if (node.maxLength >= 0) { value = value.substr(0, node.maxLength); }
682
+ node.dispatchEvent(new window.FocusEvent("focus",{bubbles: true, cancelable: true}));
683
+ var tag_name = node.tagName.toLowerCase();
684
+ if (tag_name === 'input') {
685
+ node.value = '';
686
+ if (node.type === "number" || node.type === "date") {
687
+ for (var i = 0; i < value.length; i++) {
688
+ node.dispatchEvent(new window.KeyboardEvent("keydown", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
689
+ node.dispatchEvent(new window.KeyboardEvent("keyup", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
690
+ node.dispatchEvent(new window.KeyboardEvent("keypress", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
691
+ }
692
+ node.value = value;
693
+ }
694
+ else if (node.type == "time") { node.value = new Date(value).toTimeString().split(" ")[0]; }
695
+ else if (node.type == "datetime-local") {
696
+ value = new Date(value);
697
+ var year = value.getFullYear();
698
+ var month = ("0" + (value.getMonth() + 1)).slice(-2);
699
+ var date = ("0" + value.getDate()).slice(-2);
700
+ var hour = ("0" + value.getHours()).slice(-2);
701
+ var min = ("0" + value.getMinutes()).slice(-2);
702
+ var sec = ("0" + value.getSeconds()).slice(-2);
703
+ value = `${year}-${month}-${date}T${hour}:${min}:${sec}`;
704
+ for (var i = 0; i < value.length; i++) {
705
+ node.dispatchEvent(new window.KeyboardEvent("keydown", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
706
+ node.value = node.value + value[i];
707
+ node.dispatchEvent(new window.KeyboardEvent("keyup", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
708
+ node.dispatchEvent(new window.KeyboardEvent("keypress", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
709
+ }
710
+ } else if (node.type === 'checkbox' || node.type === 'radio') { node.checked = value; }
711
+ else {
712
+ for (var i = 0; i < value.length; i++) {
713
+ node.dispatchEvent(new window.KeyboardEvent("keydown", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
714
+ node.value = node.value + value[i];
715
+ node.dispatchEvent(new window.KeyboardEvent("keyup", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
716
+ node.dispatchEvent(new window.KeyboardEvent("keypress", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
717
+ }
718
+ }
719
+ } else if (tag_name === 'textarea') {
720
+ for (var i = 0; i < value.length; i++) {
721
+ node.dispatchEvent(new window.KeyboardEvent("keydown", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
722
+ node.value = node.value + value[i];
723
+ node.dispatchEvent(new window.KeyboardEvent("keyup", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
724
+ node.dispatchEvent(new window.KeyboardEvent("keypress", {key: value[i], code: value.charCodeAt(0), bubbles: true, cancelable: true}));
725
+ }
726
+ }
727
+ JAVASCRIPT
728
+ real_value
729
+ end
730
+
731
+ def node_visible_text(node)
732
+ # if node is AREA, check visibility of relevant image
733
+ text = @context.exec <<~JAVASCRIPT
734
+ var node = AllElementHandles[#{node.handle}];
735
+ var window = AllDomHandles[#{node.document.handle}].window
736
+ var temp_node = node;
737
+ var mapName, style;
738
+ if (node.tagName === "AREA") {
739
+ mapName = document.evaluate("./ancestor::map/@name", node, null, XPathResult.STRING_TYPE, null).stringValue;
740
+ temp_node = document.querySelector('img[usemap="#${mapName}"]');
741
+ if (temp_node == null) { return ''; }
742
+ } else {
743
+ temp_node = node;
744
+ while (temp_node) {
745
+ style = window.getComputedStyle(node);
746
+ if (style.display === "none" || style.visibility === "hidden" || parseFloat(style.opacity) === 0) { return ''; }
747
+ temp_node = temp_node.parentElement;
748
+ }
749
+ }
750
+ if (node.nodeName == "TEXTAREA" || node instanceof window.SVGElement) { return node.textContent; }
751
+ else { return (node.innerText ? node.innerText : node.textContent); }
752
+ JAVASCRIPT
753
+ text.gsub(/\A[[:space:]&&[^\u00a0]]+/, "").gsub(/[[:space:]&&[^\u00a0]]+\z/, "").gsub(/\n+/, "\n").tr("\u00a0", " ")
754
+ end
755
+
756
+ def node_visible?(node)
757
+ await <<~JAVASCRIPT
758
+ var handle = await AllElementHandles[#{node.handle}].executionContext().evaluateHandle(function(node){
759
+ if (node.tagName == 'AREA'){
760
+ const map_name = document.evaluate('./ancestor::map/@name', node, null, XPathResult.STRING_TYPE, null).stringValue;
761
+ node = document.querySelector(`img[usemap='#${map_name}']`);
762
+ if (!node){ return false; }
763
+ }
764
+ var forced_visible = false;
765
+ while (node) {
766
+ const style = window.getComputedStyle(node);
767
+ if (style.visibility == 'visible') { forced_visible = true; }
768
+ if ((style.display == 'none') || ((style.visibility == 'hidden') && !forced_visible) || (parseFloat(style.opacity) == 0)) {
769
+ return false;
770
+ }
771
+ node = node.parentElement;
772
+ }
773
+ return true;
774
+ }, AllElementHandles[#{node.handle}]);
775
+ LastResult = await handle.jsonValue();
776
+ JAVASCRIPT
777
+ end
778
+
779
+ def node_wait_for(node, selector)
780
+ js_escaped_selector = selector.gsub('\\', '\\\\\\').gsub('"', '\"')
781
+ node_data = await <<~JAVASCRIPT
782
+ var start_time = new Date();
783
+ var resolver = function(resolve) {
784
+ var node = AllElementHandles[#{node.handle}].querySelector("#{js_escaped_selector}");
785
+ if (node) {
786
+ var node_handle = RegisterElementHandle(node);
787
+ var tag = node.tagName.toLowerCase();
788
+ var type = null;
789
+ if (tag === 'input') { type = node.getAttribute('type'); }
790
+ LastResult = {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
791
+ resolve(true);
792
+ }
793
+ else if ((new Date() - start_time) > #{@jsdom_timeout}) { resolve(true); }
794
+ else { setTimeout(resolver, #{@jsdom_reaction_timeout}, resolve) }
795
+ };
796
+ var promise = new Promise(function(resolve, reject){ resolver(resolve); });
797
+ await promise;
798
+ JAVASCRIPT
799
+ if node_data
800
+ node_data[:css_selector] = selector
801
+ Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
802
+ end
803
+ end
804
+
805
+ def node_wait_for_xpath(node, query)
806
+ js_escaped_query = query.gsub('\\', '\\\\\\').gsub('"', '\"')
807
+ node_data = await <<~JAVASCRIPT
808
+ var start_time = new Date();
809
+ var resolver = function(resolve) {
810
+ var window = AllDomHandles[#{document.handle}].window;
811
+ var document = window.document;
812
+ var xpath_result = document.evaluate("#{js_escaped_query}", AllElementHandles[#{node.handle}], null, window.XPathResult.FIRST_ORDERED_NODE_TYPE, null);
813
+ var node = xpath_result.singleNodeValue;
814
+ if (node) {
815
+ var node_handle = RegisterElementHandle(node);
816
+ var tag = node.tagName.toLowerCase();
817
+ var type = null;
818
+ if (tag === 'input') { type = node.getAttribute('type'); }
819
+ LastResult = {handle: node_handle, tag: tag, type: type, content_editable: node.isContentEditable};
820
+ resolve(true);
821
+ }
822
+ else if ((new Date() - start_time) > #{@jsdom_timeout}) { resolve(true); }
823
+ else { setTimeout(resolver, #{@jsdom_reaction_timeout}, resolve) }
824
+ };
825
+ var promise = new Promise(function(resolve, reject){ resolver(resolve); });
826
+ await promise;
827
+ JAVASCRIPT
828
+ if node_data
829
+ node_data[:xpath_query] = query
830
+ Isomorfeus::Puppetmaster::Node.new_by_tag(self, document, node_data)
831
+ end
832
+ end
833
+ end
834
+ end
835
+ end
836
+ end