poltergeist 1.8.1 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|