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