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