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 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