poltergeist 1.8.1 → 1.9.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 +4 -4
- data/README.md +8 -2
- data/lib/capybara/poltergeist/browser.rb +4 -0
- data/lib/capybara/poltergeist/client/agent.coffee +6 -0
- data/lib/capybara/poltergeist/client/browser.coffee +12 -4
- data/lib/capybara/poltergeist/client/compiled/agent.js +8 -1
- data/lib/capybara/poltergeist/client/compiled/browser.js +17 -2
- data/lib/capybara/poltergeist/client/compiled/main.js +3 -2
- data/lib/capybara/poltergeist/client/compiled/node.js +107 -8
- data/lib/capybara/poltergeist/client/compiled/web_page.js +73 -10
- data/lib/capybara/poltergeist/client/main.coffee +2 -2
- data/lib/capybara/poltergeist/client/node.coffee +60 -3
- data/lib/capybara/poltergeist/client/web_page.coffee +42 -3
- data/lib/capybara/poltergeist/command.rb +2 -0
- data/lib/capybara/poltergeist/driver.rb +2 -2
- data/lib/capybara/poltergeist/errors.rb +7 -1
- data/lib/capybara/poltergeist/node.rb +1 -1
- data/lib/capybara/poltergeist/server.rb +6 -1
- data/lib/capybara/poltergeist/version.rb +1 -1
- data/lib/capybara/poltergeist/web_socket_server.rb +6 -5
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30764c7fffa008722381c14b695751179d6f2aa4
|
4
|
+
data.tar.gz: bc9747887101bf79414e69442b39144ca7af0ef4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2cf7f4946e9e46ef9e7cdbd56c4a7e789342799b9b05c2b5e89833c7ef4d706331c9ae29a866efdae4b15311470e4797b2a716044b6cc61f2efda57dd89687d
|
7
|
+
data.tar.gz: 5238c2b847c294019d1b7e7a13393c43aa5d96d1ba7bd7591a1a81ecfdeaea9d90a43a6dc279de095310eef8670b82fe01fd2528bc6ad04b9156cea61ccbc600
|
data/README.md
CHANGED
@@ -22,7 +22,13 @@ to read the bug reporting guidance below).
|
|
22
22
|
|
23
23
|
## Installation ##
|
24
24
|
|
25
|
-
Add
|
25
|
+
Add this line to your Gemfile and run `bundle install`:
|
26
|
+
|
27
|
+
``` ruby
|
28
|
+
gem 'poltergeist'
|
29
|
+
```
|
30
|
+
|
31
|
+
In your test setup add:
|
26
32
|
|
27
33
|
``` ruby
|
28
34
|
require 'capybara/poltergeist'
|
@@ -82,7 +88,7 @@ was 1.0.2, so you should use that if you still need Ruby 1.8 support.
|
|
82
88
|
There are no special steps to take. You don't need Xvfb or any running X
|
83
89
|
server at all.
|
84
90
|
|
85
|
-
[Travis CI](https://travis-ci.org/) has PhantomJS pre-installed.
|
91
|
+
[Travis CI](https://travis-ci.org/) and [Codeship](https://codeship.com/) has PhantomJS pre-installed.
|
86
92
|
|
87
93
|
Depending on your tests, one thing that you may need is some fonts. If
|
88
94
|
you're getting errors on a CI that don't occur during development then
|
@@ -257,6 +257,12 @@ class PoltergeistAgent.Node
|
|
257
257
|
@element.tagName
|
258
258
|
|
259
259
|
isVisible: (element = @element) ->
|
260
|
+
#if an area element, check visibility of relevant image
|
261
|
+
if element.tagName == 'AREA'
|
262
|
+
map_name = document.evaluate('./ancestor::map/@name', element, null, XPathResult.STRING_TYPE, null).stringValue
|
263
|
+
element = document.querySelector("img[usemap='##{map_name}']")
|
264
|
+
return false unless element?
|
265
|
+
|
260
266
|
while (element)
|
261
267
|
style = window.getComputedStyle(element)
|
262
268
|
return false if style.display == 'none' or
|
@@ -82,8 +82,7 @@ class Poltergeist.Browser
|
|
82
82
|
else
|
83
83
|
throw new Poltergeist.ObsoleteNode
|
84
84
|
|
85
|
-
visit: (url) ->
|
86
|
-
|
85
|
+
visit: (url, max_wait=0) ->
|
87
86
|
@currentPage.state = 'loading'
|
88
87
|
#reset modal processing state when changing page
|
89
88
|
@processed_modal_messages = []
|
@@ -109,6 +108,11 @@ class Poltergeist.Browser
|
|
109
108
|
command.sendError(new Poltergeist.StatusFailError(url))
|
110
109
|
else
|
111
110
|
command.sendResponse(status: @currentPage.status)
|
111
|
+
, max_wait, =>
|
112
|
+
resources = @currentPage.openResourceRequests()
|
113
|
+
msg = if resources.length
|
114
|
+
"Timed out with the following resources still waiting #{@currentPage.openResourceRequests().join(',')}"
|
115
|
+
command.sendError(new Poltergeist.StatusFailError(url,msg))
|
112
116
|
return
|
113
117
|
|
114
118
|
current_url: ->
|
@@ -169,8 +173,8 @@ class Poltergeist.Browser
|
|
169
173
|
@currentPage.beforeUpload(node.id)
|
170
174
|
@currentPage.uploadFile('[_poltergeist_selected]', value)
|
171
175
|
@currentPage.afterUpload(node.id)
|
172
|
-
if phantom.version.major == 2
|
173
|
-
# In phantomjs 2 - uploadFile only fully works if executed within a user action
|
176
|
+
if phantom.version.major == 2 && phantom.version.minor == 0
|
177
|
+
# In phantomjs 2.0.x - uploadFile only fully works if executed within a user action
|
174
178
|
# It does however setup the filenames to be uploaded, so if we then click on the
|
175
179
|
# file input element the filenames will get set
|
176
180
|
@click(page_id, id)
|
@@ -481,6 +485,10 @@ class Poltergeist.Browser
|
|
481
485
|
else
|
482
486
|
command.sendResponse(false)
|
483
487
|
|
488
|
+
set_url_whitelist: ->
|
489
|
+
@currentPage.urlWhitelist = Array.prototype.slice.call(arguments)
|
490
|
+
@current_command.sendResponse(true)
|
491
|
+
|
484
492
|
set_url_blacklist: ->
|
485
493
|
@currentPage.urlBlacklist = Array.prototype.slice.call(arguments)
|
486
494
|
@current_command.sendResponse(true)
|
@@ -381,10 +381,17 @@ PoltergeistAgent.Node = (function() {
|
|
381
381
|
};
|
382
382
|
|
383
383
|
Node.prototype.isVisible = function(element) {
|
384
|
-
var style;
|
384
|
+
var map_name, style;
|
385
385
|
if (element == null) {
|
386
386
|
element = this.element;
|
387
387
|
}
|
388
|
+
if (element.tagName === 'AREA') {
|
389
|
+
map_name = document.evaluate('./ancestor::map/@name', element, null, XPathResult.STRING_TYPE, null).stringValue;
|
390
|
+
element = document.querySelector("img[usemap='#" + map_name + "']");
|
391
|
+
if (element == null) {
|
392
|
+
return false;
|
393
|
+
}
|
394
|
+
}
|
388
395
|
while (element) {
|
389
396
|
style = window.getComputedStyle(element);
|
390
397
|
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) {
|
@@ -108,8 +108,11 @@ Poltergeist.Browser = (function() {
|
|
108
108
|
}
|
109
109
|
};
|
110
110
|
|
111
|
-
Browser.prototype.visit = function(url) {
|
111
|
+
Browser.prototype.visit = function(url, max_wait) {
|
112
112
|
var command, prevUrl;
|
113
|
+
if (max_wait == null) {
|
114
|
+
max_wait = 0;
|
115
|
+
}
|
113
116
|
this.currentPage.state = 'loading';
|
114
117
|
this.processed_modal_messages = [];
|
115
118
|
this.confirm_processes = [];
|
@@ -133,6 +136,13 @@ Poltergeist.Browser = (function() {
|
|
133
136
|
});
|
134
137
|
}
|
135
138
|
};
|
139
|
+
})(this), max_wait, (function(_this) {
|
140
|
+
return function() {
|
141
|
+
var msg, resources;
|
142
|
+
resources = _this.currentPage.openResourceRequests();
|
143
|
+
msg = resources.length ? "Timed out with the following resources still waiting " + (_this.currentPage.openResourceRequests().join(',')) : void 0;
|
144
|
+
return command.sendError(new Poltergeist.StatusFailError(url, msg));
|
145
|
+
};
|
136
146
|
})(this));
|
137
147
|
}
|
138
148
|
};
|
@@ -211,7 +221,7 @@ Poltergeist.Browser = (function() {
|
|
211
221
|
this.currentPage.beforeUpload(node.id);
|
212
222
|
this.currentPage.uploadFile('[_poltergeist_selected]', value);
|
213
223
|
this.currentPage.afterUpload(node.id);
|
214
|
-
if (phantom.version.major === 2) {
|
224
|
+
if (phantom.version.major === 2 && phantom.version.minor === 0) {
|
215
225
|
return this.click(page_id, id);
|
216
226
|
} else {
|
217
227
|
return this.current_command.sendResponse(true);
|
@@ -653,6 +663,11 @@ Poltergeist.Browser = (function() {
|
|
653
663
|
}
|
654
664
|
};
|
655
665
|
|
666
|
+
Browser.prototype.set_url_whitelist = function() {
|
667
|
+
this.currentPage.urlWhitelist = Array.prototype.slice.call(arguments);
|
668
|
+
return this.current_command.sendResponse(true);
|
669
|
+
};
|
670
|
+
|
656
671
|
Browser.prototype.set_url_blacklist = function() {
|
657
672
|
this.currentPage.urlBlacklist = Array.prototype.slice.call(arguments);
|
658
673
|
return this.current_command.sendResponse(true);
|
@@ -182,14 +182,15 @@ Poltergeist.BrowserError = (function(superClass) {
|
|
182
182
|
Poltergeist.StatusFailError = (function(superClass) {
|
183
183
|
extend(StatusFailError, superClass);
|
184
184
|
|
185
|
-
function StatusFailError(url) {
|
185
|
+
function StatusFailError(url, details) {
|
186
186
|
this.url = url;
|
187
|
+
this.details = details;
|
187
188
|
}
|
188
189
|
|
189
190
|
StatusFailError.prototype.name = "Poltergeist.StatusFailError";
|
190
191
|
|
191
192
|
StatusFailError.prototype.args = function() {
|
192
|
-
return [this.url];
|
193
|
+
return [this.url, this.details];
|
193
194
|
};
|
194
195
|
|
195
196
|
return StatusFailError;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
var slice = [].slice;
|
2
2
|
|
3
3
|
Poltergeist.Node = (function() {
|
4
|
-
var fn,
|
4
|
+
var fn, j, len, name, ref;
|
5
5
|
|
6
6
|
Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'isInViewport', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection', 'path', 'getProperty'];
|
7
7
|
|
@@ -22,27 +22,41 @@ Poltergeist.Node = (function() {
|
|
22
22
|
return this.page.nodeCall(this.id, name, args);
|
23
23
|
};
|
24
24
|
};
|
25
|
-
for (
|
26
|
-
name = ref[
|
25
|
+
for (j = 0, len = ref.length; j < len; j++) {
|
26
|
+
name = ref[j];
|
27
27
|
fn(name);
|
28
28
|
}
|
29
29
|
|
30
30
|
Node.prototype.mouseEventPosition = function() {
|
31
|
-
var middle, pos, viewport;
|
31
|
+
var area_offset, image, middle, pos, res, viewport;
|
32
32
|
viewport = this.page.viewportSize();
|
33
|
-
|
33
|
+
if (image = this._getAreaImage()) {
|
34
|
+
pos = image.position();
|
35
|
+
if (area_offset = this._getAreaOffsetRect()) {
|
36
|
+
pos.left = pos.left + area_offset.x;
|
37
|
+
pos.right = pos.left + area_offset.width;
|
38
|
+
pos.top = pos.top + area_offset.y;
|
39
|
+
pos.bottom = pos.top + area_offset.height;
|
40
|
+
}
|
41
|
+
} else {
|
42
|
+
pos = this.position();
|
43
|
+
}
|
34
44
|
middle = function(start, end, size) {
|
35
45
|
return start + ((Math.min(end, size) - start) / 2);
|
36
46
|
};
|
37
|
-
return {
|
47
|
+
return res = {
|
38
48
|
x: middle(pos.left, pos.right, viewport.width),
|
39
49
|
y: middle(pos.top, pos.bottom, viewport.height)
|
40
50
|
};
|
41
51
|
};
|
42
52
|
|
43
53
|
Node.prototype.mouseEvent = function(name) {
|
44
|
-
var pos, test;
|
45
|
-
this.
|
54
|
+
var area_image, pos, test;
|
55
|
+
if (area_image = this._getAreaImage()) {
|
56
|
+
area_image.scrollIntoView();
|
57
|
+
} else {
|
58
|
+
this.scrollIntoView();
|
59
|
+
}
|
46
60
|
pos = this.mouseEventPosition();
|
47
61
|
test = this.mouseEventTest(pos.x, pos.y);
|
48
62
|
if (test.status === 'success') {
|
@@ -83,6 +97,91 @@ Poltergeist.Node = (function() {
|
|
83
97
|
return this.page === other.page && this.isDOMEqual(other.id);
|
84
98
|
};
|
85
99
|
|
100
|
+
Node.prototype._getAreaOffsetRect = function() {
|
101
|
+
var centerX, centerY, coord, coords, i, maxX, maxY, minX, minY, radius, rect, shape, x, xs, y, ys;
|
102
|
+
shape = this.getAttribute('shape').toLowerCase();
|
103
|
+
coords = (function() {
|
104
|
+
var k, len1, ref1, results;
|
105
|
+
ref1 = this.getAttribute('coords').split(',');
|
106
|
+
results = [];
|
107
|
+
for (k = 0, len1 = ref1.length; k < len1; k++) {
|
108
|
+
coord = ref1[k];
|
109
|
+
results.push(parseInt(coord, 10));
|
110
|
+
}
|
111
|
+
return results;
|
112
|
+
}).call(this);
|
113
|
+
return rect = (function() {
|
114
|
+
switch (shape) {
|
115
|
+
case 'rect':
|
116
|
+
case 'rectangle':
|
117
|
+
x = coords[0], y = coords[1];
|
118
|
+
return {
|
119
|
+
x: x,
|
120
|
+
y: y,
|
121
|
+
width: coords[2] - x,
|
122
|
+
height: coords[3] - y
|
123
|
+
};
|
124
|
+
case 'circ':
|
125
|
+
case 'circle':
|
126
|
+
centerX = coords[0], centerY = coords[1], radius = coords[2];
|
127
|
+
return {
|
128
|
+
x: centerX - radius,
|
129
|
+
y: centerY - radius,
|
130
|
+
width: 2 * radius,
|
131
|
+
height: 2 * radius
|
132
|
+
};
|
133
|
+
case 'poly':
|
134
|
+
case 'polygon':
|
135
|
+
xs = (function() {
|
136
|
+
var k, ref1, results;
|
137
|
+
results = [];
|
138
|
+
for (i = k = 0, ref1 = coords.length; k < ref1; i = k += 2) {
|
139
|
+
results.push(coords[i]);
|
140
|
+
}
|
141
|
+
return results;
|
142
|
+
})();
|
143
|
+
ys = (function() {
|
144
|
+
var k, ref1, results;
|
145
|
+
results = [];
|
146
|
+
for (i = k = 1, ref1 = coords.length; k < ref1; i = k += 2) {
|
147
|
+
results.push(coords[i]);
|
148
|
+
}
|
149
|
+
return results;
|
150
|
+
})();
|
151
|
+
minX = Math.min.apply(Math, xs);
|
152
|
+
maxX = Math.max.apply(Math, xs);
|
153
|
+
minY = Math.min.apply(Math, ys);
|
154
|
+
maxY = Math.max.apply(Math, ys);
|
155
|
+
return {
|
156
|
+
x: minX,
|
157
|
+
y: minY,
|
158
|
+
width: maxX - minX,
|
159
|
+
height: maxY - minY
|
160
|
+
};
|
161
|
+
}
|
162
|
+
})();
|
163
|
+
};
|
164
|
+
|
165
|
+
Node.prototype._getAreaImage = function() {
|
166
|
+
var image_node_id, map, mapName;
|
167
|
+
if ('area' === this.tagName().toLowerCase()) {
|
168
|
+
map = this.parent();
|
169
|
+
if (map.tagName().toLowerCase() !== 'map') {
|
170
|
+
throw new Error('the area is not within a map');
|
171
|
+
}
|
172
|
+
mapName = map.getAttribute('name');
|
173
|
+
if (mapName == null) {
|
174
|
+
throw new Error("area's parent map must have a name");
|
175
|
+
}
|
176
|
+
mapName = '#' + mapName.toLowerCase();
|
177
|
+
image_node_id = this.page.find('css', "img[usemap='" + mapName + "']")[0];
|
178
|
+
if (image_node_id == null) {
|
179
|
+
throw new Error("no image matches the map");
|
180
|
+
}
|
181
|
+
return this.page.get(image_node_id);
|
182
|
+
}
|
183
|
+
};
|
184
|
+
|
86
185
|
return Node;
|
87
186
|
|
88
187
|
})();
|
@@ -1,5 +1,6 @@
|
|
1
1
|
var slice = [].slice,
|
2
|
-
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }
|
2
|
+
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
|
3
|
+
hasProp = {}.hasOwnProperty;
|
3
4
|
|
4
5
|
Poltergeist.WebPage = (function() {
|
5
6
|
var command, delegate, fn1, fn2, i, j, len, len1, ref, ref1;
|
@@ -20,12 +21,14 @@ Poltergeist.WebPage = (function() {
|
|
20
21
|
this.source = null;
|
21
22
|
this.closed = false;
|
22
23
|
this.state = 'default';
|
24
|
+
this.urlWhitelist = [];
|
23
25
|
this.urlBlacklist = [];
|
24
26
|
this.frames = [];
|
25
27
|
this.errors = [];
|
26
28
|
this._networkTraffic = {};
|
27
29
|
this._tempHeaders = {};
|
28
30
|
this._blockedUrls = [];
|
31
|
+
this._requestedResources = {};
|
29
32
|
ref = WebPage.CALLBACKS;
|
30
33
|
for (i = 0, len = ref.length; i < len; i++) {
|
31
34
|
callback = ref[i];
|
@@ -84,7 +87,8 @@ Poltergeist.WebPage = (function() {
|
|
84
87
|
|
85
88
|
WebPage.prototype.onLoadStartedNative = function() {
|
86
89
|
this.state = 'loading';
|
87
|
-
|
90
|
+
this.requestId = this.lastRequestId;
|
91
|
+
return this._requestedResources = {};
|
88
92
|
};
|
89
93
|
|
90
94
|
WebPage.prototype.onLoadFinishedNative = function(status) {
|
@@ -111,10 +115,21 @@ Poltergeist.WebPage = (function() {
|
|
111
115
|
};
|
112
116
|
|
113
117
|
WebPage.prototype.onResourceRequestedNative = function(request, net) {
|
114
|
-
var abort, ref2;
|
115
|
-
|
118
|
+
var abort, blacklisted, ref2, useWhitelist, whitelisted;
|
119
|
+
useWhitelist = this.urlWhitelist.length > 0;
|
120
|
+
whitelisted = this.urlWhitelist.some(function(whitelisted_url) {
|
121
|
+
return request.url.indexOf(whitelisted_url) !== -1;
|
122
|
+
});
|
123
|
+
blacklisted = this.urlBlacklist.some(function(blacklisted_url) {
|
116
124
|
return request.url.indexOf(blacklisted_url) !== -1;
|
117
125
|
});
|
126
|
+
abort = false;
|
127
|
+
if (useWhitelist && !whitelisted) {
|
128
|
+
abort = true;
|
129
|
+
}
|
130
|
+
if (blacklisted) {
|
131
|
+
abort = true;
|
132
|
+
}
|
118
133
|
if (abort) {
|
119
134
|
if (ref2 = request.url, indexOf.call(this._blockedUrls, ref2) < 0) {
|
120
135
|
this._blockedUrls.push(request.url);
|
@@ -131,6 +146,7 @@ Poltergeist.WebPage = (function() {
|
|
131
146
|
responseParts: [],
|
132
147
|
error: null
|
133
148
|
};
|
149
|
+
this._requestedResources[request.id] = request.url;
|
134
150
|
}
|
135
151
|
return true;
|
136
152
|
};
|
@@ -140,6 +156,9 @@ Poltergeist.WebPage = (function() {
|
|
140
156
|
if ((ref2 = this._networkTraffic[response.id]) != null) {
|
141
157
|
ref2.responseParts.push(response);
|
142
158
|
}
|
159
|
+
if (response.stage === 'end') {
|
160
|
+
delete this._requestedResources[response.id];
|
161
|
+
}
|
143
162
|
if (this.requestId === response.id) {
|
144
163
|
if (response.redirectURL) {
|
145
164
|
this.redirectURL = this.normalizeURL(response.redirectURL);
|
@@ -156,6 +175,7 @@ Poltergeist.WebPage = (function() {
|
|
156
175
|
if ((ref2 = this._networkTraffic[errorResponse.id]) != null) {
|
157
176
|
ref2.error = errorResponse;
|
158
177
|
}
|
178
|
+
delete this._requestedResources[errorResponse.id];
|
159
179
|
return true;
|
160
180
|
};
|
161
181
|
|
@@ -213,15 +233,46 @@ Poltergeist.WebPage = (function() {
|
|
213
233
|
})(this));
|
214
234
|
};
|
215
235
|
|
216
|
-
WebPage.prototype.
|
236
|
+
WebPage.prototype._waitState_until = function(state, callback, timeout, timeout_callback) {
|
237
|
+
var d;
|
238
|
+
if (this.state === state) {
|
239
|
+
return callback.call();
|
240
|
+
} else {
|
241
|
+
d = new Date();
|
242
|
+
if (d.getTime() > timeout) {
|
243
|
+
return timeout_callback.call();
|
244
|
+
} else {
|
245
|
+
return setTimeout(((function(_this) {
|
246
|
+
return function() {
|
247
|
+
return _this._waitState_until(state, callback, timeout, timeout_callback);
|
248
|
+
};
|
249
|
+
})(this)), 100);
|
250
|
+
}
|
251
|
+
}
|
252
|
+
};
|
253
|
+
|
254
|
+
WebPage.prototype.waitState = function(state, callback, max_wait, timeout_callback) {
|
255
|
+
var timeout;
|
256
|
+
if (max_wait == null) {
|
257
|
+
max_wait = 0;
|
258
|
+
}
|
217
259
|
if (this.state === state) {
|
218
260
|
return callback.call();
|
219
261
|
} else {
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
262
|
+
if (max_wait !== 0) {
|
263
|
+
timeout = (new Date).getTime() + (max_wait * 1000);
|
264
|
+
return setTimeout(((function(_this) {
|
265
|
+
return function() {
|
266
|
+
return _this._waitState_until(state, callback, timeout, timeout_callback);
|
267
|
+
};
|
268
|
+
})(this)), 100);
|
269
|
+
} else {
|
270
|
+
return setTimeout(((function(_this) {
|
271
|
+
return function() {
|
272
|
+
return _this.waitState(state, callback);
|
273
|
+
};
|
274
|
+
})(this)), 100);
|
275
|
+
}
|
225
276
|
}
|
226
277
|
};
|
227
278
|
|
@@ -249,6 +300,18 @@ Poltergeist.WebPage = (function() {
|
|
249
300
|
return true;
|
250
301
|
};
|
251
302
|
|
303
|
+
WebPage.prototype.openResourceRequests = function() {
|
304
|
+
var id, ref2, results, url;
|
305
|
+
ref2 = this._requestedResources;
|
306
|
+
results = [];
|
307
|
+
for (id in ref2) {
|
308
|
+
if (!hasProp.call(ref2, id)) continue;
|
309
|
+
url = ref2[id];
|
310
|
+
results.push(url);
|
311
|
+
}
|
312
|
+
return results;
|
313
|
+
};
|
314
|
+
|
252
315
|
WebPage.prototype.content = function() {
|
253
316
|
return this["native"]().frameContent;
|
254
317
|
};
|
@@ -79,9 +79,9 @@ class Poltergeist.BrowserError extends Poltergeist.Error
|
|
79
79
|
args: -> [@message, @stack]
|
80
80
|
|
81
81
|
class Poltergeist.StatusFailError extends Poltergeist.Error
|
82
|
-
constructor: (@url) ->
|
82
|
+
constructor: (@url, @details) ->
|
83
83
|
name: "Poltergeist.StatusFailError"
|
84
|
-
args: -> [@url]
|
84
|
+
args: -> [@url, @details]
|
85
85
|
|
86
86
|
class Poltergeist.NoSuchWindowError extends Poltergeist.Error
|
87
87
|
name: "Poltergeist.NoSuchWindowError"
|
@@ -19,18 +19,32 @@ class Poltergeist.Node
|
|
19
19
|
|
20
20
|
mouseEventPosition: ->
|
21
21
|
viewport = @page.viewportSize()
|
22
|
-
|
22
|
+
|
23
|
+
if image = @_getAreaImage()
|
24
|
+
pos = image.position()
|
25
|
+
|
26
|
+
if area_offset = @_getAreaOffsetRect()
|
27
|
+
pos.left = pos.left + area_offset.x
|
28
|
+
pos.right = pos.left + area_offset.width
|
29
|
+
pos.top = pos.top + area_offset.y
|
30
|
+
pos.bottom = pos.top + area_offset.height
|
31
|
+
else
|
32
|
+
pos = this.position()
|
23
33
|
|
24
34
|
middle = (start, end, size) ->
|
25
35
|
start + ((Math.min(end, size) - start) / 2)
|
26
36
|
|
27
|
-
{
|
37
|
+
res = {
|
28
38
|
x: middle(pos.left, pos.right, viewport.width),
|
29
39
|
y: middle(pos.top, pos.bottom, viewport.height)
|
30
40
|
}
|
31
41
|
|
42
|
+
|
32
43
|
mouseEvent: (name) ->
|
33
|
-
|
44
|
+
if area_image = @_getAreaImage()
|
45
|
+
area_image.scrollIntoView()
|
46
|
+
else
|
47
|
+
@scrollIntoView()
|
34
48
|
pos = this.mouseEventPosition()
|
35
49
|
test = this.mouseEventTest(pos.x, pos.y)
|
36
50
|
if test.status == 'success'
|
@@ -68,3 +82,46 @@ class Poltergeist.Node
|
|
68
82
|
isEqual: (other) ->
|
69
83
|
@page == other.page && this.isDOMEqual(other.id)
|
70
84
|
|
85
|
+
_getAreaOffsetRect: ->
|
86
|
+
# get the offset of the center of selected area
|
87
|
+
shape = @getAttribute('shape').toLowerCase();
|
88
|
+
coords = (parseInt(coord,10) for coord in @getAttribute('coords').split(','))
|
89
|
+
|
90
|
+
rect = switch shape
|
91
|
+
when 'rect', 'rectangle'
|
92
|
+
#coords.length == 4
|
93
|
+
[x,y] = coords
|
94
|
+
{ x: x, y: y, width: coords[2] - x, height: coords[3] - y }
|
95
|
+
when 'circ', 'circle'
|
96
|
+
# coords.length == 3
|
97
|
+
[centerX, centerY, radius] = coords
|
98
|
+
{ x: centerX - radius, y: centerY - radius, width: 2 * radius, height: 2 * radius }
|
99
|
+
when 'poly', 'polygon'
|
100
|
+
# coords.length > 2
|
101
|
+
# This isn't correct for highly concave polygons but is probably good enough for
|
102
|
+
# use in a testing tool
|
103
|
+
xs = (coords[i] for i in [0...coords.length] by 2)
|
104
|
+
ys = (coords[i] for i in [1...coords.length] by 2)
|
105
|
+
minX = Math.min xs...
|
106
|
+
maxX = Math.max xs...
|
107
|
+
minY = Math.min ys...
|
108
|
+
maxY = Math.max ys...
|
109
|
+
{ x: minX, y: minY, width: maxX-minX, height: maxY-minY }
|
110
|
+
|
111
|
+
_getAreaImage: ->
|
112
|
+
if 'area' == @tagName().toLowerCase()
|
113
|
+
map = @parent()
|
114
|
+
if map.tagName().toLowerCase() != 'map'
|
115
|
+
throw new Error('the area is not within a map')
|
116
|
+
|
117
|
+
mapName = map.getAttribute('name')
|
118
|
+
if not mapName?
|
119
|
+
throw new Error ("area's parent map must have a name")
|
120
|
+
mapName = '#' + mapName.toLowerCase()
|
121
|
+
|
122
|
+
image_node_id = @page.find('css', "img[usemap='#{mapName}']")[0]
|
123
|
+
if not image_node_id?
|
124
|
+
throw new Error ("no image matches the map")
|
125
|
+
|
126
|
+
@page.get(image_node_id)
|
127
|
+
|
@@ -20,12 +20,14 @@ class Poltergeist.WebPage
|
|
20
20
|
@source = null
|
21
21
|
@closed = false
|
22
22
|
@state = 'default'
|
23
|
+
@urlWhitelist = []
|
23
24
|
@urlBlacklist = []
|
24
25
|
@frames = []
|
25
26
|
@errors = []
|
26
27
|
@_networkTraffic = {}
|
27
28
|
@_tempHeaders = {}
|
28
29
|
@_blockedUrls = []
|
30
|
+
@_requestedResources = {}
|
29
31
|
|
30
32
|
for callback in WebPage.CALLBACKS
|
31
33
|
this.bindCallback(callback)
|
@@ -61,6 +63,7 @@ class Poltergeist.WebPage
|
|
61
63
|
onLoadStartedNative: ->
|
62
64
|
@state = 'loading'
|
63
65
|
@requestId = @lastRequestId
|
66
|
+
@_requestedResources = {}
|
64
67
|
|
65
68
|
onLoadFinishedNative: (@status) ->
|
66
69
|
@state = 'default'
|
@@ -78,9 +81,22 @@ class Poltergeist.WebPage
|
|
78
81
|
return true
|
79
82
|
|
80
83
|
onResourceRequestedNative: (request, net) ->
|
81
|
-
|
84
|
+
useWhitelist = @urlWhitelist.length > 0
|
85
|
+
|
86
|
+
whitelisted = @urlWhitelist.some (whitelisted_url) ->
|
87
|
+
request.url.indexOf(whitelisted_url) != -1
|
88
|
+
|
89
|
+
blacklisted = @urlBlacklist.some (blacklisted_url) ->
|
82
90
|
request.url.indexOf(blacklisted_url) != -1
|
83
91
|
|
92
|
+
abort = false
|
93
|
+
|
94
|
+
if useWhitelist && !whitelisted
|
95
|
+
abort = true
|
96
|
+
|
97
|
+
if blacklisted
|
98
|
+
abort = true
|
99
|
+
|
84
100
|
if abort
|
85
101
|
@_blockedUrls.push request.url unless request.url in @_blockedUrls
|
86
102
|
net.abort()
|
@@ -96,11 +112,16 @@ class Poltergeist.WebPage
|
|
96
112
|
responseParts: []
|
97
113
|
error: null
|
98
114
|
}
|
115
|
+
|
116
|
+
@_requestedResources[request.id] = request.url
|
99
117
|
return true
|
100
118
|
|
101
119
|
onResourceReceivedNative: (response) ->
|
102
120
|
@_networkTraffic[response.id]?.responseParts.push(response)
|
103
121
|
|
122
|
+
if response.stage == 'end'
|
123
|
+
delete @_requestedResources[response.id]
|
124
|
+
|
104
125
|
if @requestId == response.id
|
105
126
|
if response.redirectURL
|
106
127
|
@redirectURL = @normalizeURL(response.redirectURL)
|
@@ -111,6 +132,7 @@ class Poltergeist.WebPage
|
|
111
132
|
|
112
133
|
onResourceErrorNative: (errorResponse) ->
|
113
134
|
@_networkTraffic[errorResponse.id]?.error = errorResponse
|
135
|
+
delete @_requestedResources[errorResponse.id]
|
114
136
|
return true
|
115
137
|
|
116
138
|
injectAgent: ->
|
@@ -146,11 +168,25 @@ class Poltergeist.WebPage
|
|
146
168
|
names.split(',').map (name) =>
|
147
169
|
this.keyCode(name.charAt(0).toUpperCase() + name.substring(1))
|
148
170
|
|
149
|
-
|
171
|
+
_waitState_until: (state, callback, timeout, timeout_callback) ->
|
172
|
+
if (@state == state)
|
173
|
+
callback.call()
|
174
|
+
else
|
175
|
+
d = new Date()
|
176
|
+
if d.getTime() > timeout
|
177
|
+
timeout_callback.call()
|
178
|
+
else
|
179
|
+
setTimeout (=> @_waitState_until(state, callback, timeout, timeout_callback)), 100
|
180
|
+
|
181
|
+
waitState: (state, callback, max_wait=0, timeout_callback) ->
|
150
182
|
if @state == state
|
151
183
|
callback.call()
|
152
184
|
else
|
153
|
-
|
185
|
+
if max_wait != 0
|
186
|
+
timeout = (new Date).getTime() + (max_wait*1000)
|
187
|
+
setTimeout (=> @_waitState_until(state, callback, timeout, timeout_callback)), 100
|
188
|
+
else
|
189
|
+
setTimeout (=> @waitState(state, callback)), 100
|
154
190
|
|
155
191
|
setHttpAuth: (user, password) ->
|
156
192
|
this.native().settings.userName = user
|
@@ -171,6 +207,9 @@ class Poltergeist.WebPage
|
|
171
207
|
@_blockedUrls = []
|
172
208
|
return true
|
173
209
|
|
210
|
+
openResourceRequests: ->
|
211
|
+
url for own id, url of @_requestedResources
|
212
|
+
|
174
213
|
content: ->
|
175
214
|
this.native().frameContent
|
176
215
|
|
@@ -53,8 +53,8 @@ module Capybara::Poltergeist
|
|
53
53
|
# PhantomJS defaults to only using SSLv3, which since POODLE (Oct 2014)
|
54
54
|
# many sites have dropped from their supported protocols (eg PayPal,
|
55
55
|
# Braintree).
|
56
|
-
list += ["--ssl-
|
57
|
-
|
56
|
+
list += ["--ignore-ssl-errors=yes"] unless list.grep(/ignore-ssl-errors/).any?
|
57
|
+
list += ["--ssl-protocol=TLSv1"] unless list.grep(/ssl-protocol/).any?
|
58
58
|
list += ["--remote-debugger-port=#{inspector.port}", "--remote-debugger-autorun=yes"] if inspector
|
59
59
|
list
|
60
60
|
end
|
@@ -60,8 +60,14 @@ module Capybara
|
|
60
60
|
response['args'].first
|
61
61
|
end
|
62
62
|
|
63
|
+
def details
|
64
|
+
response['args'][1]
|
65
|
+
end
|
66
|
+
|
63
67
|
def message
|
64
|
-
"Request to '#{url}' failed to reach server, check DNS and/or server status"
|
68
|
+
msg = "Request to '#{url}' failed to reach server, check DNS and/or server status"
|
69
|
+
msg += " - #{details}" if details
|
70
|
+
msg
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
@@ -30,7 +30,12 @@ module Capybara::Poltergeist
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def send(command)
|
33
|
-
|
33
|
+
receive_timeout = nil # default
|
34
|
+
if command.name == 'visit'
|
35
|
+
command.args.push(timeout) # set the client set visit timeout parameter
|
36
|
+
receive_timeout = timeout + 5 # Add a couple of seconds to let the client timeout first
|
37
|
+
end
|
38
|
+
@socket.send(command.id, command.message, receive_timeout) or raise DeadClient.new(command.message)
|
34
39
|
end
|
35
40
|
end
|
36
41
|
end
|
@@ -68,14 +68,15 @@ module Capybara::Poltergeist
|
|
68
68
|
|
69
69
|
# Block until the next message is available from the Web Socket.
|
70
70
|
# Raises Errno::EWOULDBLOCK if timeout is reached.
|
71
|
-
def receive(cmd_id)
|
71
|
+
def receive(cmd_id, receive_timeout=nil)
|
72
|
+
receive_timeout ||= timeout
|
72
73
|
start = Time.now
|
73
74
|
|
74
75
|
until @messages.has_key?(cmd_id)
|
75
|
-
raise Errno::EWOULDBLOCK if (Time.now - start) >=
|
76
|
+
raise Errno::EWOULDBLOCK if (Time.now - start) >= receive_timeout
|
76
77
|
if @receive_mutex.try_lock
|
77
78
|
begin
|
78
|
-
IO.select([socket], [], [],
|
79
|
+
IO.select([socket], [], [], receive_timeout) or raise Errno::EWOULDBLOCK
|
79
80
|
data = socket.recv(RECV_SIZE)
|
80
81
|
break if data.empty?
|
81
82
|
driver.parse(data)
|
@@ -90,10 +91,10 @@ module Capybara::Poltergeist
|
|
90
91
|
end
|
91
92
|
|
92
93
|
# Send a message and block until there is a response
|
93
|
-
def send(cmd_id, message)
|
94
|
+
def send(cmd_id, message, accept_timeout=nil)
|
94
95
|
accept unless connected?
|
95
96
|
driver.text(message)
|
96
|
-
receive(cmd_id)
|
97
|
+
receive(cmd_id, accept_timeout)
|
97
98
|
rescue Errno::EWOULDBLOCK
|
98
99
|
raise TimeoutError.new(message)
|
99
100
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: poltergeist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Leighton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -254,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
254
|
version: '0'
|
255
255
|
requirements: []
|
256
256
|
rubyforge_project:
|
257
|
-
rubygems_version: 2.
|
257
|
+
rubygems_version: 2.5.1
|
258
258
|
signing_key:
|
259
259
|
specification_version: 4
|
260
260
|
summary: PhantomJS driver for Capybara
|