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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ba9a64217db44bbe9dcbe55e34cac615640688df
4
- data.tar.gz: 7bd5f0ae85f2565e1b6c07f4030644f59eccbb1f
3
+ metadata.gz: 30764c7fffa008722381c14b695751179d6f2aa4
4
+ data.tar.gz: bc9747887101bf79414e69442b39144ca7af0ef4
5
5
  SHA512:
6
- metadata.gz: 93141f4f1e6438da75f912f4174f5f7f987d9fcff0dbaab0fa7eff384a6501067b6fcbf06eb06f9589e71457a8ee563ab84ff11d0ff9b83828fbd8e531b9eb00
7
- data.tar.gz: 54b08a29b4707dfdb8c3a7ff2a7c16194ca100e87cfc89d1e5f11f17f7f151121260cfdf10584949100f66de17ff57b5ed574273268580e75ca7729533a484f9
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 `poltergeist` to your Gemfile, and in your test setup 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
@@ -324,6 +324,10 @@ module Capybara::Poltergeist
324
324
  end
325
325
  end
326
326
 
327
+ def url_whitelist=(whitelist)
328
+ command 'set_url_whitelist', *whitelist
329
+ end
330
+
327
331
  def url_blacklist=(blacklist)
328
332
  command 'set_url_blacklist', *blacklist
329
333
  end
@@ -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, i, len, name, ref;
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 (i = 0, len = ref.length; i < len; i++) {
26
- name = ref[i];
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
- pos = this.position();
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.scrollIntoView();
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
- return this.requestId = this.lastRequestId;
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
- abort = this.urlBlacklist.some(function(blacklisted_url) {
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.waitState = function(state, callback) {
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
- return setTimeout(((function(_this) {
221
- return function() {
222
- return _this.waitState(state, callback);
223
- };
224
- })(this)), 100);
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
- pos = this.position()
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
- this.scrollIntoView()
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
- abort = @urlBlacklist.some (blacklisted_url) ->
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
- waitState: (state, callback) ->
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
- setTimeout (=> @waitState(state, callback)), 100
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
 
@@ -3,6 +3,8 @@ require 'securerandom'
3
3
  module Capybara::Poltergeist
4
4
  class Command
5
5
  attr_reader :id
6
+ attr_reader :name
7
+ attr_accessor :args
6
8
 
7
9
  def initialize(name, *args)
8
10
  @id = SecureRandom.uuid
@@ -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-protocol=any"] unless list.grep(/ssl-protocol/).any?
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
 
@@ -120,7 +120,7 @@ module Capybara::Poltergeist
120
120
  end
121
121
 
122
122
  def selected?
123
- self[:selected]
123
+ !!self[:selected]
124
124
  end
125
125
 
126
126
  def disabled?
@@ -30,7 +30,12 @@ module Capybara::Poltergeist
30
30
  end
31
31
 
32
32
  def send(command)
33
- @socket.send(command.id, command.message) or raise DeadClient.new(command.message)
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
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
2
  module Poltergeist
3
- VERSION = "1.8.1"
3
+ VERSION = "1.9.0"
4
4
  end
5
5
  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) >= timeout
76
+ raise Errno::EWOULDBLOCK if (Time.now - start) >= receive_timeout
76
77
  if @receive_mutex.try_lock
77
78
  begin
78
- IO.select([socket], [], [], timeout) or raise Errno::EWOULDBLOCK
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.8.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: 2015-11-24 00:00:00.000000000 Z
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.4.5.1
257
+ rubygems_version: 2.5.1
258
258
  signing_key:
259
259
  specification_version: 4
260
260
  summary: PhantomJS driver for Capybara