cuprite 0.13 → 0.14
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 +4 -4
- metadata +39 -55
- data/LICENSE +0 -21
- data/README.md +0 -215
- data/lib/capybara/cuprite/browser.rb +0 -214
- data/lib/capybara/cuprite/cookie.rb +0 -47
- data/lib/capybara/cuprite/driver.rb +0 -465
- data/lib/capybara/cuprite/errors.rb +0 -62
- data/lib/capybara/cuprite/javascripts/index.js +0 -478
- data/lib/capybara/cuprite/node.rb +0 -295
- data/lib/capybara/cuprite/page.rb +0 -200
- data/lib/capybara/cuprite/version.rb +0 -7
- data/lib/capybara/cuprite.rb +0 -13
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Capybara
|
4
|
-
module Cuprite
|
5
|
-
class Error < StandardError; end
|
6
|
-
|
7
|
-
class ClientError < Error
|
8
|
-
attr_reader :response
|
9
|
-
|
10
|
-
def initialize(response)
|
11
|
-
@response = response
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class InvalidSelector < ClientError
|
16
|
-
def initialize(response, method, selector)
|
17
|
-
super(response)
|
18
|
-
@method, @selector = method, selector
|
19
|
-
end
|
20
|
-
|
21
|
-
def message
|
22
|
-
"Browser raised error trying to find #{@method}: #{@selector.inspect}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class MouseEventFailed < ClientError
|
27
|
-
attr_reader :name, :selector, :position
|
28
|
-
|
29
|
-
def initialize(*)
|
30
|
-
super
|
31
|
-
data = /\A\w+: (\w+), (.+?), ([\d\.-]+), ([\d\.-]+)/.match(@response)
|
32
|
-
@name, @selector = data.values_at(1, 2)
|
33
|
-
@position = data.values_at(3, 4).map(&:to_f)
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
def message
|
38
|
-
"Firing a #{name} at coordinates [#{position.join(", ")}] failed. Cuprite detected " \
|
39
|
-
"another element with CSS selector \"#{selector}\" at this position. " \
|
40
|
-
"It may be overlapping the element you are trying to interact with. " \
|
41
|
-
"If you don't care about overlapping elements, try using node.trigger(\"#{name}\")."
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class ObsoleteNode < ClientError
|
46
|
-
attr_reader :node
|
47
|
-
|
48
|
-
def initialize(node, response)
|
49
|
-
@node = node
|
50
|
-
super(response)
|
51
|
-
end
|
52
|
-
|
53
|
-
def message
|
54
|
-
"The element you are trying to interact with is either not part of the DOM, or is " \
|
55
|
-
"not currently visible on the page (perhaps display: none is set). " \
|
56
|
-
"It is possible the element has been replaced by another element and you meant to interact with " \
|
57
|
-
"the new element. If so you need to do a new find in order to get a reference to the " \
|
58
|
-
"new element."
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,478 +0,0 @@
|
|
1
|
-
class InvalidSelector extends Error {}
|
2
|
-
class TimedOutPromise extends Error {}
|
3
|
-
class MouseEventFailed extends Error {}
|
4
|
-
|
5
|
-
const EVENTS = {
|
6
|
-
FOCUS: ["blur", "focus", "focusin", "focusout"],
|
7
|
-
MOUSE: ["click", "dblclick", "mousedown", "mouseenter", "mouseleave",
|
8
|
-
"mousemove", "mouseover", "mouseout", "mouseup", "contextmenu"],
|
9
|
-
FORM: ["submit"]
|
10
|
-
}
|
11
|
-
|
12
|
-
class Cuprite {
|
13
|
-
constructor() {
|
14
|
-
this._json = JSON; // In case someone overrides it like mootools
|
15
|
-
}
|
16
|
-
|
17
|
-
find(method, selector, within = document) {
|
18
|
-
try {
|
19
|
-
let results = [];
|
20
|
-
|
21
|
-
if (method == "xpath") {
|
22
|
-
let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
23
|
-
for (let i = 0; i < xpath.snapshotLength; i++) {
|
24
|
-
results.push(xpath.snapshotItem(i));
|
25
|
-
}
|
26
|
-
} else {
|
27
|
-
results = Array.from(within.querySelectorAll(selector));
|
28
|
-
}
|
29
|
-
|
30
|
-
return results;
|
31
|
-
} catch (error) {
|
32
|
-
// DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code
|
33
|
-
if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {
|
34
|
-
throw new InvalidSelector;
|
35
|
-
} else {
|
36
|
-
throw error;
|
37
|
-
}
|
38
|
-
}
|
39
|
-
}
|
40
|
-
|
41
|
-
parents(node) {
|
42
|
-
let nodes = [];
|
43
|
-
let parent = node.parentNode;
|
44
|
-
while (parent != document && parent !== null) {
|
45
|
-
nodes.push(parent);
|
46
|
-
parent = parent.parentNode;
|
47
|
-
}
|
48
|
-
return nodes;
|
49
|
-
}
|
50
|
-
|
51
|
-
visibleText(node) {
|
52
|
-
if (this.isVisible(node)) {
|
53
|
-
if (node.nodeName == "TEXTAREA") {
|
54
|
-
return node.textContent;
|
55
|
-
} else {
|
56
|
-
if (node instanceof SVGElement) {
|
57
|
-
return node.textContent;
|
58
|
-
} else {
|
59
|
-
return node.innerText;
|
60
|
-
}
|
61
|
-
}
|
62
|
-
}
|
63
|
-
}
|
64
|
-
|
65
|
-
isVisible(node) {
|
66
|
-
let mapName, style;
|
67
|
-
// if node is area, check visibility of relevant image
|
68
|
-
if (node.tagName === "AREA") {
|
69
|
-
mapName = document.evaluate("./ancestor::map/@name", node, null, XPathResult.STRING_TYPE, null).stringValue;
|
70
|
-
node = document.querySelector(`img[usemap="#${mapName}"]`);
|
71
|
-
if (node == null) {
|
72
|
-
return false;
|
73
|
-
}
|
74
|
-
}
|
75
|
-
|
76
|
-
while (node) {
|
77
|
-
style = window.getComputedStyle(node);
|
78
|
-
if (style.display === "none" || style.visibility === "hidden" || parseFloat(style.opacity) === 0) {
|
79
|
-
return false;
|
80
|
-
}
|
81
|
-
node = node.parentElement;
|
82
|
-
}
|
83
|
-
|
84
|
-
return true;
|
85
|
-
}
|
86
|
-
|
87
|
-
|
88
|
-
isDisabled(node) {
|
89
|
-
let xpath = "parent::optgroup[@disabled] | \
|
90
|
-
ancestor::select[@disabled] | \
|
91
|
-
parent::fieldset[@disabled] | \
|
92
|
-
ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
|
93
|
-
|
94
|
-
return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
|
95
|
-
}
|
96
|
-
|
97
|
-
path(node) {
|
98
|
-
let nodes = [node];
|
99
|
-
let parent = node.parentNode;
|
100
|
-
while (parent !== document && parent !== null) {
|
101
|
-
nodes.unshift(parent);
|
102
|
-
parent = parent.parentNode;
|
103
|
-
}
|
104
|
-
|
105
|
-
let selectors = nodes.map(node => {
|
106
|
-
let prevSiblings = [];
|
107
|
-
let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
108
|
-
|
109
|
-
for (let i = 0; i < xpath.snapshotLength; i++) {
|
110
|
-
prevSiblings.push(xpath.snapshotItem(i));
|
111
|
-
}
|
112
|
-
|
113
|
-
return `${node.tagName}[${(prevSiblings.length + 1)}]`;
|
114
|
-
});
|
115
|
-
|
116
|
-
return `//${selectors.join("/")}`;
|
117
|
-
}
|
118
|
-
|
119
|
-
set(node, value) {
|
120
|
-
if (node.readOnly) return;
|
121
|
-
|
122
|
-
if (node.maxLength >= 0) {
|
123
|
-
value = value.substr(0, node.maxLength);
|
124
|
-
}
|
125
|
-
|
126
|
-
let valueBefore = node.value;
|
127
|
-
|
128
|
-
this.trigger(node, "focus");
|
129
|
-
this.setValue(node, "");
|
130
|
-
|
131
|
-
if (node.type == "number" || node.type == "date" || node.type == "range") {
|
132
|
-
this.setValue(node, value);
|
133
|
-
this.input(node);
|
134
|
-
} else if (node.type == "time") {
|
135
|
-
this.setValue(node, new Date(value).toTimeString().split(" ")[0]);
|
136
|
-
this.input(node);
|
137
|
-
} else if (node.type == "datetime-local") {
|
138
|
-
value = new Date(value);
|
139
|
-
let year = value.getFullYear();
|
140
|
-
let month = ("0" + (value.getMonth() + 1)).slice(-2);
|
141
|
-
let date = ("0" + value.getDate()).slice(-2);
|
142
|
-
let hour = ("0" + value.getHours()).slice(-2);
|
143
|
-
let min = ("0" + value.getMinutes()).slice(-2);
|
144
|
-
let sec = ("0" + value.getSeconds()).slice(-2);
|
145
|
-
this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);
|
146
|
-
this.input(node);
|
147
|
-
} else {
|
148
|
-
for (let i = 0; i < value.length; i++) {
|
149
|
-
let char = value[i];
|
150
|
-
let keyCode = this.characterToKeyCode(char);
|
151
|
-
// call the following functions in order, if one returns false (preventDefault),
|
152
|
-
// stop the call chain
|
153
|
-
[
|
154
|
-
() => this.keyupdowned(node, "keydown", keyCode),
|
155
|
-
() => this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)),
|
156
|
-
() => {
|
157
|
-
this.setValue(node, node.value + char)
|
158
|
-
this.input(node)
|
159
|
-
}
|
160
|
-
].some(fn => fn())
|
161
|
-
|
162
|
-
this.keyupdowned(node, "keyup", keyCode);
|
163
|
-
}
|
164
|
-
}
|
165
|
-
|
166
|
-
if (valueBefore !== node.value) {
|
167
|
-
this.changed(node);
|
168
|
-
}
|
169
|
-
this.trigger(node, "blur");
|
170
|
-
}
|
171
|
-
|
172
|
-
setValue(node, value) {
|
173
|
-
let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
|
174
|
-
let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
|
175
|
-
|
176
|
-
if (node.tagName.toLowerCase() === 'input') {
|
177
|
-
return nativeInputValueSetter.call(node, value);
|
178
|
-
}
|
179
|
-
return nativeTextareaValueSetter.call(node, value);
|
180
|
-
}
|
181
|
-
|
182
|
-
input(node) {
|
183
|
-
let event = document.createEvent("HTMLEvents");
|
184
|
-
event.initEvent("input", true, false);
|
185
|
-
node.dispatchEvent(event);
|
186
|
-
}
|
187
|
-
|
188
|
-
/**
|
189
|
-
* @return {boolean} false when an event handler called preventDefault()
|
190
|
-
*/
|
191
|
-
keyupdowned(node, eventName, keyCode) {
|
192
|
-
let event = document.createEvent("UIEvents");
|
193
|
-
event.initEvent(eventName, true, true);
|
194
|
-
event.keyCode = keyCode;
|
195
|
-
event.charCode = 0;
|
196
|
-
return !node.dispatchEvent(event);
|
197
|
-
}
|
198
|
-
|
199
|
-
/**
|
200
|
-
* @return {boolean} false when an event handler called preventDefault()
|
201
|
-
*/
|
202
|
-
keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
|
203
|
-
event = document.createEvent("UIEvents");
|
204
|
-
event.initEvent("keypress", true, true);
|
205
|
-
event.window = window;
|
206
|
-
event.altKey = altKey;
|
207
|
-
event.ctrlKey = ctrlKey;
|
208
|
-
event.shiftKey = shiftKey;
|
209
|
-
event.metaKey = metaKey;
|
210
|
-
event.keyCode = keyCode;
|
211
|
-
event.charCode = charCode;
|
212
|
-
return !node.dispatchEvent(event);
|
213
|
-
}
|
214
|
-
|
215
|
-
characterToKeyCode(char) {
|
216
|
-
const specialKeys = {
|
217
|
-
96: 192, // `
|
218
|
-
45: 189, // -
|
219
|
-
61: 187, // =
|
220
|
-
91: 219, // [
|
221
|
-
93: 221, // ]
|
222
|
-
92: 220, // \
|
223
|
-
59: 186, // ;
|
224
|
-
39: 222, // '
|
225
|
-
44: 188, // ,
|
226
|
-
46: 190, // .
|
227
|
-
47: 191, // /
|
228
|
-
127: 46, // delete
|
229
|
-
126: 192, // ~
|
230
|
-
33: 49, // !
|
231
|
-
64: 50, // @
|
232
|
-
35: 51, // #
|
233
|
-
36: 52, // $
|
234
|
-
37: 53, // %
|
235
|
-
94: 54, // ^
|
236
|
-
38: 55, // &
|
237
|
-
42: 56, // *
|
238
|
-
40: 57, // (
|
239
|
-
41: 48, // )
|
240
|
-
95: 189, // _
|
241
|
-
43: 187, // +
|
242
|
-
123: 219, // {
|
243
|
-
125: 221, // }
|
244
|
-
124: 220, // |
|
245
|
-
58: 186, // :
|
246
|
-
34: 222, // "
|
247
|
-
60: 188, // <
|
248
|
-
62: 190, // >
|
249
|
-
63: 191, // ?
|
250
|
-
}
|
251
|
-
|
252
|
-
let code = char.toUpperCase().charCodeAt(0);
|
253
|
-
return specialKeys[code] || code;
|
254
|
-
}
|
255
|
-
|
256
|
-
scrollIntoViewport(node) {
|
257
|
-
let areaImage = this._getAreaImage(node);
|
258
|
-
|
259
|
-
if (areaImage) {
|
260
|
-
return this.scrollIntoViewport(areaImage);
|
261
|
-
} else {
|
262
|
-
node.scrollIntoViewIfNeeded();
|
263
|
-
|
264
|
-
if (!this._isInViewport(node)) {
|
265
|
-
node.scrollIntoView({block: "center", inline: "center", behavior: "instant"});
|
266
|
-
return this._isInViewport(node);
|
267
|
-
}
|
268
|
-
|
269
|
-
return true;
|
270
|
-
}
|
271
|
-
}
|
272
|
-
|
273
|
-
mouseEventTest(node, name, x, y) {
|
274
|
-
let frameOffset = this._frameOffset();
|
275
|
-
x -= frameOffset.left;
|
276
|
-
y -= frameOffset.top;
|
277
|
-
|
278
|
-
let element = document.elementFromPoint(x, y);
|
279
|
-
|
280
|
-
let el = element;
|
281
|
-
while (el) {
|
282
|
-
if (el == node) {
|
283
|
-
return true;
|
284
|
-
} else {
|
285
|
-
el = el.parentNode;
|
286
|
-
}
|
287
|
-
}
|
288
|
-
|
289
|
-
let selector = element && this._getSelector(element) || "none";
|
290
|
-
throw new MouseEventFailed([name, selector, x, y].join(", "));
|
291
|
-
}
|
292
|
-
|
293
|
-
_getAreaImage(node) {
|
294
|
-
if ("area" == node.tagName.toLowerCase()) {
|
295
|
-
let map = node.parentNode;
|
296
|
-
if (map.tagName.toLowerCase() != "map") {
|
297
|
-
throw new Error("the area is not within a map");
|
298
|
-
}
|
299
|
-
|
300
|
-
let mapName = map.getAttribute("name");
|
301
|
-
if (typeof mapName === "undefined" || mapName === null) {
|
302
|
-
throw new Error("area's parent map must have a name");
|
303
|
-
}
|
304
|
-
|
305
|
-
mapName = `#${mapName.toLowerCase()}`;
|
306
|
-
let imageNode = this.find("css", `img[usemap='${mapName}']`)[0];
|
307
|
-
if (typeof imageNode === "undefined" || imageNode === null) {
|
308
|
-
throw new Error("no image matches the map");
|
309
|
-
}
|
310
|
-
|
311
|
-
return imageNode;
|
312
|
-
}
|
313
|
-
}
|
314
|
-
|
315
|
-
_frameOffset() {
|
316
|
-
let win = window;
|
317
|
-
let offset = { top: 0, left: 0 };
|
318
|
-
|
319
|
-
while (win.frameElement) {
|
320
|
-
let rect = win.frameElement.getClientRects()[0];
|
321
|
-
let style = win.getComputedStyle(win.frameElement);
|
322
|
-
win = win.parent;
|
323
|
-
|
324
|
-
offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10)
|
325
|
-
offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10)
|
326
|
-
}
|
327
|
-
|
328
|
-
return offset;
|
329
|
-
}
|
330
|
-
|
331
|
-
_getSelector(el) {
|
332
|
-
let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + " " : "";
|
333
|
-
selector += el.tagName.toLowerCase();
|
334
|
-
if (el.id) { selector += `#${el.id}` };
|
335
|
-
el.classList.forEach(c => selector += `.${c}`);
|
336
|
-
return selector;
|
337
|
-
}
|
338
|
-
|
339
|
-
_isInViewport(node) {
|
340
|
-
let rect = node.getBoundingClientRect();
|
341
|
-
return rect.top >= 0 &&
|
342
|
-
rect.left >= 0 &&
|
343
|
-
rect.bottom <= window.innerHeight &&
|
344
|
-
rect.right <= window.innerWidth;
|
345
|
-
}
|
346
|
-
|
347
|
-
select(node, value) {
|
348
|
-
if (this.isDisabled(node)) {
|
349
|
-
return false;
|
350
|
-
} else if (value == false && !node.parentNode.multiple) {
|
351
|
-
return false;
|
352
|
-
} else {
|
353
|
-
this.trigger(node.parentNode, "focus");
|
354
|
-
|
355
|
-
node.selected = value;
|
356
|
-
this.changed(node);
|
357
|
-
|
358
|
-
this.trigger(node.parentNode, "blur");
|
359
|
-
return true;
|
360
|
-
}
|
361
|
-
}
|
362
|
-
|
363
|
-
changed(node) {
|
364
|
-
let element;
|
365
|
-
let event = document.createEvent("HTMLEvents");
|
366
|
-
event.initEvent("change", true, false);
|
367
|
-
|
368
|
-
// In the case of an OPTION tag, the change event should come
|
369
|
-
// from the parent SELECT
|
370
|
-
if (node.nodeName == "OPTION") {
|
371
|
-
element = node.parentNode
|
372
|
-
if (element.nodeName == "OPTGROUP") {
|
373
|
-
element = element.parentNode
|
374
|
-
}
|
375
|
-
element
|
376
|
-
} else {
|
377
|
-
element = node
|
378
|
-
}
|
379
|
-
|
380
|
-
element.dispatchEvent(event)
|
381
|
-
}
|
382
|
-
|
383
|
-
trigger(node, name, options = {}) {
|
384
|
-
let event;
|
385
|
-
|
386
|
-
if (EVENTS.MOUSE.indexOf(name) != -1) {
|
387
|
-
event = document.createEvent("MouseEvent");
|
388
|
-
event.initMouseEvent(
|
389
|
-
name, true, true, window, 0,
|
390
|
-
options["screenX"] || 0, options["screenY"] || 0,
|
391
|
-
options["clientX"] || 0, options["clientY"] || 0,
|
392
|
-
options["ctrlKey"] || false,
|
393
|
-
options["altKey"] || false,
|
394
|
-
options["shiftKey"] || false,
|
395
|
-
options["metaKey"] || false,
|
396
|
-
options["button"] || 0, null
|
397
|
-
)
|
398
|
-
} else if (EVENTS.FOCUS.indexOf(name) != -1) {
|
399
|
-
event = this.obtainEvent(name);
|
400
|
-
} else if (EVENTS.FORM.indexOf(name) != -1) {
|
401
|
-
event = this.obtainEvent(name);
|
402
|
-
} else {
|
403
|
-
throw "Unknown event";
|
404
|
-
}
|
405
|
-
|
406
|
-
node.dispatchEvent(event);
|
407
|
-
}
|
408
|
-
|
409
|
-
obtainEvent(name) {
|
410
|
-
let event = document.createEvent("HTMLEvents");
|
411
|
-
event.initEvent(name, true, true);
|
412
|
-
return event;
|
413
|
-
}
|
414
|
-
|
415
|
-
getAttributes(node) {
|
416
|
-
let attrs = {};
|
417
|
-
for (let i = 0, len = node.attributes.length; i < len; i++) {
|
418
|
-
let attr = node.attributes[i];
|
419
|
-
attrs[attr.name] = attr.value.replace("\n", "\\n");
|
420
|
-
}
|
421
|
-
|
422
|
-
return this._json.stringify(attrs);
|
423
|
-
}
|
424
|
-
|
425
|
-
getAttribute(node, name) {
|
426
|
-
if (name == "checked" || name == "selected") {
|
427
|
-
return node[name];
|
428
|
-
} else {
|
429
|
-
return node.getAttribute(name);
|
430
|
-
}
|
431
|
-
}
|
432
|
-
|
433
|
-
value(node) {
|
434
|
-
if (node.tagName == "SELECT" && node.multiple) {
|
435
|
-
let result = []
|
436
|
-
|
437
|
-
for (let i = 0, len = node.children.length; i < len; i++) {
|
438
|
-
let option = node.children[i];
|
439
|
-
if (option.selected) {
|
440
|
-
result.push(option.value);
|
441
|
-
}
|
442
|
-
}
|
443
|
-
|
444
|
-
return result;
|
445
|
-
} else {
|
446
|
-
return node.value;
|
447
|
-
}
|
448
|
-
}
|
449
|
-
|
450
|
-
deleteText(node) {
|
451
|
-
let range = document.createRange();
|
452
|
-
range.selectNodeContents(node);
|
453
|
-
window.getSelection().removeAllRanges();
|
454
|
-
window.getSelection().addRange(range);
|
455
|
-
window.getSelection().deleteFromDocument();
|
456
|
-
}
|
457
|
-
|
458
|
-
containsSelection(node) {
|
459
|
-
let selectedNode = document.getSelection().focusNode;
|
460
|
-
|
461
|
-
if (!selectedNode) {
|
462
|
-
return false;
|
463
|
-
}
|
464
|
-
|
465
|
-
if (selectedNode.nodeType == 3) {
|
466
|
-
selectedNode = selectedNode.parentNode;
|
467
|
-
}
|
468
|
-
|
469
|
-
return node.contains(selectedNode);
|
470
|
-
}
|
471
|
-
|
472
|
-
// This command is purely for testing error handling
|
473
|
-
browserError() {
|
474
|
-
throw new Error("zomg");
|
475
|
-
}
|
476
|
-
}
|
477
|
-
|
478
|
-
window._cuprite = new Cuprite;
|