isomorfeus-puppetmaster 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +191 -0
- data/lib/isomorfeus-puppetmaster.rb +43 -0
- data/lib/isomorfeus/puppetmaster.rb +62 -0
- data/lib/isomorfeus/puppetmaster/console_message.rb +19 -0
- data/lib/isomorfeus/puppetmaster/cookie.rb +46 -0
- data/lib/isomorfeus/puppetmaster/document.rb +160 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom.rb +370 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom_document.rb +908 -0
- data/lib/isomorfeus/puppetmaster/driver/jsdom_node.rb +836 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer.rb +401 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer_document.rb +944 -0
- data/lib/isomorfeus/puppetmaster/driver/puppeteer_node.rb +866 -0
- data/lib/isomorfeus/puppetmaster/driver_registration.rb +19 -0
- data/lib/isomorfeus/puppetmaster/dsl.rb +40 -0
- data/lib/isomorfeus/puppetmaster/errors.rb +90 -0
- data/lib/isomorfeus/puppetmaster/iframe.rb +17 -0
- data/lib/isomorfeus/puppetmaster/node.rb +241 -0
- data/lib/isomorfeus/puppetmaster/node/checkbox.rb +17 -0
- data/lib/isomorfeus/puppetmaster/node/content_editable.rb +18 -0
- data/lib/isomorfeus/puppetmaster/node/filechooser.rb +9 -0
- data/lib/isomorfeus/puppetmaster/node/input.rb +21 -0
- data/lib/isomorfeus/puppetmaster/node/radiobutton.rb +13 -0
- data/lib/isomorfeus/puppetmaster/node/select.rb +36 -0
- data/lib/isomorfeus/puppetmaster/node/textarea.rb +7 -0
- data/lib/isomorfeus/puppetmaster/request.rb +17 -0
- data/lib/isomorfeus/puppetmaster/response.rb +26 -0
- data/lib/isomorfeus/puppetmaster/rspec/features.rb +23 -0
- data/lib/isomorfeus/puppetmaster/rspec/matcher_proxies.rb +80 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers.rb +164 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/base.rb +98 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/become_closed.rb +33 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/compound.rb +88 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_current_path.rb +29 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_selector.rb +69 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_text.rb +33 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/have_title.rb +29 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/match_selector.rb +27 -0
- data/lib/isomorfeus/puppetmaster/rspec/matchers/match_style.rb +38 -0
- data/lib/isomorfeus/puppetmaster/self_forwardable.rb +31 -0
- data/lib/isomorfeus/puppetmaster/server.rb +128 -0
- data/lib/isomorfeus/puppetmaster/server/checker.rb +40 -0
- data/lib/isomorfeus/puppetmaster/server/middleware.rb +60 -0
- data/lib/isomorfeus/puppetmaster/server_registration.rb +37 -0
- data/lib/isomorfeus/puppetmaster/version.rb +3 -0
- metadata +282 -0
@@ -0,0 +1,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
|