poltergeist 1.4.1 → 1.5.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: cd4a7dfd79219a924c01ec792ce5baf8e9d78a6e
4
- data.tar.gz: 5c6c3687f0e65c19cd2247b3ce77302ba7c408ea
3
+ metadata.gz: a65b5bdad64852e243d1097f272634b945bf2c09
4
+ data.tar.gz: 3f79fd0d9e6ea7c32667f278941cd6f394078092
5
5
  SHA512:
6
- metadata.gz: 28abc4905c9818ea5b286bdc4472cfacbc7095a73959052c515b5bd7cb663d03545e1036a3285a0a85f1c6be5521b941a0ffeb0c70728e91531b280d6ab1d131
7
- data.tar.gz: 4655a8ec8cf83b6b2dba829fec3f6b429a5729c841e9ee4361304c70586fe2fe704a6d8f6a7f9f1fef92ce214e1749d41467ce3ae83d2b568e372ec814d96e18
6
+ metadata.gz: 416b123a2ab427a59e681dbc1a9c34cc904649f80f5da67dce666a3c27074f42153840471ceadb0d25ecb0cf57429762b6e37244cd0cab4489edc024f86094cb
7
+ data.tar.gz: 02a445954d2ee06aeb89db30ebcfcfa1da0201f0211c4762caab67d17d0ac852a5ccf1a75d1ec109783fc34c79fe9d3e261c24efaa6140d4086d0dfb1711d2d8
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011 Jonathan Leighton
1
+ Copyright (c) 2011-2013 Jonathan Leighton
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -9,7 +9,7 @@ provided by [PhantomJS](http://www.phantomjs.org/).
9
9
  **If you're viewing this at https://github.com/jonleighton/poltergeist,
10
10
  you're reading the documentation for the master branch.
11
11
  [View documentation for the latest release
12
- (1.4.1).](https://github.com/jonleighton/poltergeist/tree/v1.4.1)**
12
+ (1.5.0).](https://github.com/jonleighton/poltergeist/tree/v1.5.0)**
13
13
 
14
14
  ## Getting help ##
15
15
 
@@ -43,26 +43,25 @@ dependencies* (you don't need Qt, or a running X server, etc.)
43
43
 
44
44
  * *Homebrew*: `brew install phantomjs`
45
45
  * *MacPorts*: `sudo port install phantomjs`
46
- * *Manual install*: [Download this](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-macosx.zip&can=2&q=)
46
+ * *Manual install*: [Download this](http://phantomjs.googlecode.com/files/phantomjs-1.9.2-macosx.zip)
47
47
 
48
48
  ### Linux ###
49
49
 
50
- * Download the [32
51
- bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-linux-i686.tar.bz2&can=2&q=)
52
- or [64
53
- bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-linux-x86_64.tar.bz2&can=2&q=)
50
+ * Download the [32 bit](https://phantomjs.googlecode.com/files/phantomjs-1.9.2-linux-i686.tar.bz2)
51
+ or [64 bit](https://phantomjs.googlecode.com/files/phantomjs-1.9.2-linux-x86_64.tar.bz2)
54
52
  binary.
55
53
  * Extract the tarball and copy `bin/phantomjs` into your `PATH`
56
54
 
57
55
  ### Windows ###
58
- * Download the [precompiled binary](http://phantomjs.org/download.html) for Windows
56
+ * Download the [precompiled binary](http://phantomjs.googlecode.com/files/phantomjs-1.9.2-windows.zip)
57
+ for Windows
59
58
 
60
59
  ### Manual compilation ###
61
60
 
62
61
  Do this as a last resort if the binaries don't work for you. It will
63
62
  take quite a long time as it has to build WebKit.
64
63
 
65
- * Download [the source tarball](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-source.zip&can=2&q=)
64
+ * Download [the source tarball](http://phantomjs.googlecode.com/files/phantomjs-1.9.2-source.zip)
66
65
  * Extract and cd in
67
66
  * `./build.sh`
68
67
 
@@ -83,10 +82,7 @@ was 1.0.2, so you should use that if you still need Ruby 1.8 support.
83
82
  There are no special steps to take. You don't need Xvfb or any running X
84
83
  server at all.
85
84
 
86
- [Travis CI](https://travis-ci.org/) has PhantomJS pre-installed, but it
87
- might not be the latest version. If you need to install the latest
88
- version, [check out the .travis.yml that Poltergeist
89
- uses](https://github.com/jonleighton/poltergeist/blob/master/.travis.yml).
85
+ [Travis CI](https://travis-ci.org/) has PhantomJS pre-installed.
90
86
 
91
87
  Depending on your tests, one thing that you may need is some fonts. If
92
88
  you're getting errors on a CI that don't occur during development then
@@ -105,8 +101,10 @@ and the following optional features:
105
101
  * `page.status_code`
106
102
  * `page.response_headers`
107
103
  * `page.save_screenshot`
108
- * `page.render_base64`
109
- * `page.scroll_to`
104
+ * `page.driver.render_base64(format, options)`
105
+ * `page.driver.scroll_to(left, top)`
106
+ * `page.driver.basic_authorize(user, password)`
107
+ * `element.native.send_keys(*keys)`
110
108
  * cookie handling
111
109
  * drag-and-drop
112
110
 
@@ -117,9 +115,12 @@ There are some additional features:
117
115
  You can grab screenshots of the page at any point by calling
118
116
  `save_screenshot('/path/to/file.png')` (this works the same way as the PhantomJS
119
117
  render feature, so you can specify other extensions like `.pdf`, `.gif`, etc.)
118
+ Just in case you render pdf it's might be worth to set `driver.paper_size=` with
119
+ settings provided by PhantomJS in [here](https://github.com/ariya/phantomjs/wiki/API-Reference-WebPage#wiki-webpage-paperSize)
120
120
 
121
- By default, only the viewport will be rendered (the part of the page that is in view). To render
122
- the entire page, use `save_screenshot('/path/to/file.png', :full => true)`.
121
+ By default, only the viewport will be rendered (the part of the page that is in
122
+ view). To render the entire page, use `save_screenshot('/path/to/file.png',
123
+ :full => true)`.
123
124
 
124
125
  You also have an ability to render selected element. Pass option `selector` with
125
126
  any valid element selector to make a screenshot bounded by that element
@@ -198,6 +199,9 @@ You can inspect the network traffic (i.e. what resources have been
198
199
  loaded) on the current page by calling `page.driver.network_traffic`.
199
200
  This returns an array of request objects. A request object has a
200
201
  `response_parts` method containing data about the response chunks.
202
+ Please note that network traffic is not cleared when you visit new page.
203
+ You can manually clear the network traffic by calling `page.driver.clear_network_traffic`
204
+ or `page.driver.reset`
201
205
 
202
206
  ### Manipulating cookies ###
203
207
 
@@ -238,6 +242,25 @@ page.within_window fb_popup do
238
242
  end
239
243
  ```
240
244
 
245
+ ### Sending keys ###
246
+
247
+ There's an ability to send arbitrary keys to the element:
248
+
249
+ ``` ruby
250
+ element = find('input#id')
251
+ element.native.send_key('String')
252
+ ```
253
+
254
+ or even more complicated:
255
+
256
+ ``` ruby
257
+ element.native.send_keys('H', 'elo', :Left, 'l') # => 'Hello'
258
+ element.native.send_key(:Enter) # triggers Enter key
259
+ ```
260
+ Since it's implemented natively in PhantomJS this will exactly imitate user
261
+ behavior.
262
+ See more about [sendEvent](http://phantomjs.org/api/webpage/method/send-event.html) and
263
+ [PhantomJS keys](https://github.com/ariya/phantomjs/commit/cab2635e66d74b7e665c44400b8b20a8f225153a)
241
264
 
242
265
  ## Customization ##
243
266
 
@@ -66,6 +66,10 @@ module Capybara::Poltergeist
66
66
  command 'visible_text', page_id, id
67
67
  end
68
68
 
69
+ def delete_text(page_id, id)
70
+ command 'delete_text', page_id, id
71
+ end
72
+
69
73
  def attribute(page_id, id, name)
70
74
  command 'attribute', page_id, id, name.to_s
71
75
  end
@@ -108,7 +112,7 @@ module Capybara::Poltergeist
108
112
 
109
113
  def within_frame(handle, &block)
110
114
  if handle.is_a?(Capybara::Node::Base)
111
- command 'push_frame', handle['id']
115
+ command 'push_frame', handle[:name] || handle[:id]
112
116
  else
113
117
  command 'push_frame', handle
114
118
  end
@@ -171,10 +175,18 @@ module Capybara::Poltergeist
171
175
  command 'render_base64', format.to_s, !!options[:full], options[:selector]
172
176
  end
173
177
 
178
+ def set_paper_size(size)
179
+ command 'set_paper_size', size
180
+ end
181
+
174
182
  def resize(width, height)
175
183
  command 'resize', width, height
176
184
  end
177
185
 
186
+ def send_keys(page_id, id, keys)
187
+ command 'send_keys', page_id, id, normalize_keys(keys)
188
+ end
189
+
178
190
  def network_traffic
179
191
  command('network_traffic').values.map do |event|
180
192
  NetworkTraffic::Request.new(
@@ -184,6 +196,10 @@ module Capybara::Poltergeist
184
196
  end
185
197
  end
186
198
 
199
+ def clear_network_traffic
200
+ command('clear_network_traffic')
201
+ end
202
+
187
203
  def equals(page_id, id, other_id)
188
204
  command('equals', page_id, id, other_id)
189
205
  end
@@ -228,6 +244,10 @@ module Capybara::Poltergeist
228
244
  command 'cookies_enabled', !!flag
229
245
  end
230
246
 
247
+ def set_http_auth(user, password)
248
+ command 'set_http_auth', user, password
249
+ end
250
+
231
251
  def js_errors=(val)
232
252
  command 'set_js_errors', !!val
233
253
  end
@@ -261,6 +281,14 @@ module Capybara::Poltergeist
261
281
  raise
262
282
  end
263
283
 
284
+ def go_back
285
+ command 'go_back'
286
+ end
287
+
288
+ def go_forward
289
+ command 'go_forward'
290
+ end
291
+
264
292
  private
265
293
 
266
294
  def log(message)
@@ -273,5 +301,20 @@ module Capybara::Poltergeist
273
301
  options.delete(:selector)
274
302
  end
275
303
  end
304
+
305
+ def normalize_keys(keys)
306
+ keys.map do |key|
307
+ case key
308
+ when Array
309
+ # String itself with modifiers like :alt, :shift, etc
310
+ raise Error, 'PhantomJS behaviour for key modifiers is currently ' \
311
+ 'broken, we will add this in later versions'
312
+ when Symbol
313
+ { key: key } # Return a known sequence for PhantomJS
314
+ when String
315
+ key # Plain string, nothing to do
316
+ end
317
+ end
318
+ end
276
319
  end
277
320
  end
@@ -57,32 +57,16 @@ module Capybara::Poltergeist
57
57
 
58
58
  redirect_stdout do
59
59
  @pid = Process.spawn(*command.map(&:to_s), process_options)
60
- ObjectSpace.define_finalizer(self, self.class.process_killer(@pid) )
60
+ ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
61
61
  end
62
62
  end
63
63
 
64
64
  def stop
65
65
  if pid
66
- begin
67
- if Capybara::Poltergeist.windows?
68
- Process.kill('KILL', pid)
69
- else
70
- Process.kill('TERM', pid)
71
- begin
72
- Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
73
- rescue Timeout::Error
74
- Process.kill('KILL', pid)
75
- Process.wait(pid)
76
- end
77
- end
78
- rescue Errno::ESRCH, Errno::ECHILD
79
- # Zed's dead, baby
80
- end
66
+ kill_phantomjs
81
67
  @out_thread.kill
82
- @write_io.close
83
- @read_io.close
68
+ close_io
84
69
  ObjectSpace.undefine_finalizer(self)
85
- @pid = nil
86
70
  end
87
71
  end
88
72
 
@@ -103,7 +87,10 @@ module Capybara::Poltergeist
103
87
  private
104
88
 
105
89
  # This abomination is because JRuby doesn't support the :out option of
106
- # Process.spawn
90
+ # Process.spawn. To be honest it works pretty bad with pipes too, because
91
+ # we ought close writing end in parent process immediately but JRuby will
92
+ # lose all the output from child. Process.popen can be used here and seems
93
+ # it works with JRuby but I've experienced strange mistakes on Rubinius.
107
94
  def redirect_stdout
108
95
  prev = STDOUT.dup
109
96
  prev.autoclose = false
@@ -114,5 +101,45 @@ module Capybara::Poltergeist
114
101
  STDOUT.reopen(prev)
115
102
  $stdout = STDOUT
116
103
  end
104
+
105
+ def kill_phantomjs
106
+ begin
107
+ if Capybara::Poltergeist.windows?
108
+ Process.kill('KILL', pid)
109
+ else
110
+ Process.kill('TERM', pid)
111
+ begin
112
+ Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
113
+ rescue Timeout::Error
114
+ Process.kill('KILL', pid)
115
+ Process.wait(pid)
116
+ end
117
+ end
118
+ rescue Errno::ESRCH, Errno::ECHILD
119
+ # Zed's dead, baby
120
+ end
121
+ @pid = nil
122
+ end
123
+
124
+ # We grab all the output from PhantomJS like console.log in another thread
125
+ # and when PhantomJS crashes we try to restart it. In order to do it we stop
126
+ # server and client and on JRuby see this error `IOError: Stream closed`.
127
+ # It happens because JRuby tries to close pipe and it is blocked on `eof?`
128
+ # or `readpartial` call. The error is raised in the related thread and it's
129
+ # not actually main thread but the thread that listens to the output. That's
130
+ # why if you put some debug code after `rescue IOError` it won't be shown.
131
+ # In fact the main thread will continue working after the error even if we
132
+ # don't use `rescue`. The first attempt to fix it was a try not to block on
133
+ # IO, but looks like similar issue appers after JRuby upgrade. Perhaps the
134
+ # only way to fix it is catching the exception what this method overall does.
135
+ def close_io
136
+ [@write_io, @read_io].each do |io|
137
+ begin
138
+ io.close unless io.closed?
139
+ rescue IOError
140
+ raise unless RUBY_ENGINE == 'jruby'
141
+ end
142
+ end
143
+ end
117
144
  end
118
145
  end
@@ -37,7 +37,8 @@ class PoltergeistAgent
37
37
 
38
38
  this.register(el) for el in results
39
39
  catch error
40
- if error.code == DOMException.SYNTAX_ERR
40
+ # DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code
41
+ if error.code == DOMException.SYNTAX_ERR || error.code == 51
41
42
  throw new PoltergeistAgent.InvalidSelector
42
43
  else
43
44
  throw error
@@ -140,6 +141,12 @@ class PoltergeistAgent.Node
140
141
  else
141
142
  @element.innerText
142
143
 
144
+ deleteText: ->
145
+ range = document.createRange()
146
+ range.selectNodeContents(@element)
147
+ window.getSelection().addRange(range)
148
+ window.getSelection().deleteFromDocument()
149
+
143
150
  getAttribute: (name) ->
144
151
  if name == 'checked' || name == 'selected'
145
152
  @element[name]
@@ -164,13 +171,16 @@ class PoltergeistAgent.Node
164
171
  @element.value = ''
165
172
  this.trigger('focus')
166
173
 
167
- for char in value
168
- keyCode = this.characterToKeyCode(char)
169
- this.keyupdowned('keydown', keyCode)
170
- @element.value += char
174
+ if @element.type == 'number'
175
+ @element.value = value
176
+ else
177
+ for char in value
178
+ keyCode = this.characterToKeyCode(char)
179
+ this.keyupdowned('keydown', keyCode)
180
+ @element.value += char
171
181
 
172
- this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0))
173
- this.keyupdowned('keyup', keyCode)
182
+ this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0))
183
+ this.keyupdowned('keyup', keyCode)
174
184
 
175
185
  this.changed()
176
186
  this.input()
@@ -121,6 +121,9 @@ class Poltergeist.Browser
121
121
  visible_text: (page_id, id) ->
122
122
  this.sendResponse this.node(page_id, id).visibleText()
123
123
 
124
+ delete_text: (page_id, id) ->
125
+ this.sendResponse this.node(page_id, id).deleteText()
126
+
124
127
  attribute: (page_id, id, name) ->
125
128
  this.sendResponse this.node(page_id, id).getAttribute(name)
126
129
 
@@ -250,6 +253,15 @@ class Poltergeist.Browser
250
253
  @page.setScrollPosition(left: left, top: top)
251
254
  this.sendResponse(true)
252
255
 
256
+ send_keys: (page_id, id, keys) ->
257
+ # Programmatically generated focus doesn't work for `sendKeys`.
258
+ # That's why we need something more realistic like user behavior.
259
+ this.node(page_id, id).mouseEvent('click')
260
+ for sequence in keys
261
+ key = if sequence.key? then @page.native.event.key[sequence.key] else sequence
262
+ @page.sendEvent('keypress', key)
263
+ this.sendResponse(true)
264
+
253
265
  render_base64: (format, full, selector = null)->
254
266
  this.set_clip_rect(full, selector)
255
267
  encoded_image = @page.renderBase64(format)
@@ -277,6 +289,10 @@ class Poltergeist.Browser
277
289
  @page.setClipRect(rect)
278
290
  dimensions
279
291
 
292
+ set_paper_size: (size) ->
293
+ @page.setPaperSize(size)
294
+ this.sendResponse(true)
295
+
280
296
  resize: (width, height) ->
281
297
  @page.setViewportSize(width: width, height: height)
282
298
  this.sendResponse(true)
@@ -284,6 +300,10 @@ class Poltergeist.Browser
284
300
  network_traffic: ->
285
301
  this.sendResponse(@page.networkTraffic())
286
302
 
303
+ clear_network_traffic: ->
304
+ @page.clearNetworkTraffic()
305
+ this.sendResponse(true)
306
+
287
307
  get_headers: ->
288
308
  this.sendResponse(@page.getCustomHeaders())
289
309
 
@@ -323,6 +343,10 @@ class Poltergeist.Browser
323
343
  phantom.cookiesEnabled = flag
324
344
  this.sendResponse(true)
325
345
 
346
+ set_http_auth: (user, password) ->
347
+ @page.setHttpAuth(user, password)
348
+ this.sendResponse(true)
349
+
326
350
  set_js_errors: (value) ->
327
351
  @js_errors = value
328
352
  this.sendResponse(true)
@@ -340,3 +364,11 @@ class Poltergeist.Browser
340
364
  # This command is purely for testing error handling
341
365
  browser_error: ->
342
366
  throw new Error('zomg')
367
+
368
+ go_back: ->
369
+ this.page.goBack() if this.page.canGoBack
370
+ this.sendResponse(true)
371
+
372
+ go_forward: ->
373
+ this.page.goForward() if this.page.canGoForward
374
+ this.sendResponse(true)
@@ -74,7 +74,7 @@ PoltergeistAgent = (function() {
74
74
  return _results;
75
75
  } catch (_error) {
76
76
  error = _error;
77
- if (error.code === DOMException.SYNTAX_ERR) {
77
+ if (error.code === DOMException.SYNTAX_ERR || error.code === 51) {
78
78
  throw new PoltergeistAgent.InvalidSelector;
79
79
  } else {
80
80
  throw error;
@@ -233,6 +233,14 @@ PoltergeistAgent.Node = (function() {
233
233
  }
234
234
  };
235
235
 
236
+ Node.prototype.deleteText = function() {
237
+ var range;
238
+ range = document.createRange();
239
+ range.selectNodeContents(this.element);
240
+ window.getSelection().addRange(range);
241
+ return window.getSelection().deleteFromDocument();
242
+ };
243
+
236
244
  Node.prototype.getAttribute = function(name) {
237
245
  if (name === 'checked' || name === 'selected') {
238
246
  return this.element[name];
@@ -272,13 +280,17 @@ PoltergeistAgent.Node = (function() {
272
280
  }
273
281
  this.element.value = '';
274
282
  this.trigger('focus');
275
- for (_i = 0, _len = value.length; _i < _len; _i++) {
276
- char = value[_i];
277
- keyCode = this.characterToKeyCode(char);
278
- this.keyupdowned('keydown', keyCode);
279
- this.element.value += char;
280
- this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
281
- this.keyupdowned('keyup', keyCode);
283
+ if (this.element.type === 'number') {
284
+ this.element.value = value;
285
+ } else {
286
+ for (_i = 0, _len = value.length; _i < _len; _i++) {
287
+ char = value[_i];
288
+ keyCode = this.characterToKeyCode(char);
289
+ this.keyupdowned('keydown', keyCode);
290
+ this.element.value += char;
291
+ this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
292
+ this.keyupdowned('keyup', keyCode);
293
+ }
282
294
  }
283
295
  this.changed();
284
296
  this.input();
@@ -153,6 +153,10 @@ Poltergeist.Browser = (function() {
153
153
  return this.sendResponse(this.node(page_id, id).visibleText());
154
154
  };
155
155
 
156
+ Browser.prototype.delete_text = function(page_id, id) {
157
+ return this.sendResponse(this.node(page_id, id).deleteText());
158
+ };
159
+
156
160
  Browser.prototype.attribute = function(page_id, id, name) {
157
161
  return this.sendResponse(this.node(page_id, id).getAttribute(name));
158
162
  };
@@ -324,6 +328,17 @@ Poltergeist.Browser = (function() {
324
328
  return this.sendResponse(true);
325
329
  };
326
330
 
331
+ Browser.prototype.send_keys = function(page_id, id, keys) {
332
+ var key, sequence, _i, _len;
333
+ this.node(page_id, id).mouseEvent('click');
334
+ for (_i = 0, _len = keys.length; _i < _len; _i++) {
335
+ sequence = keys[_i];
336
+ key = sequence.key != null ? this.page["native"].event.key[sequence.key] : sequence;
337
+ this.page.sendEvent('keypress', key);
338
+ }
339
+ return this.sendResponse(true);
340
+ };
341
+
327
342
  Browser.prototype.render_base64 = function(format, full, selector) {
328
343
  var encoded_image;
329
344
  if (selector == null) {
@@ -371,6 +386,11 @@ Poltergeist.Browser = (function() {
371
386
  return dimensions;
372
387
  };
373
388
 
389
+ Browser.prototype.set_paper_size = function(size) {
390
+ this.page.setPaperSize(size);
391
+ return this.sendResponse(true);
392
+ };
393
+
374
394
  Browser.prototype.resize = function(width, height) {
375
395
  this.page.setViewportSize({
376
396
  width: width,
@@ -383,6 +403,11 @@ Poltergeist.Browser = (function() {
383
403
  return this.sendResponse(this.page.networkTraffic());
384
404
  };
385
405
 
406
+ Browser.prototype.clear_network_traffic = function() {
407
+ this.page.clearNetworkTraffic();
408
+ return this.sendResponse(true);
409
+ };
410
+
386
411
  Browser.prototype.get_headers = function() {
387
412
  return this.sendResponse(this.page.getCustomHeaders());
388
413
  };
@@ -435,6 +460,11 @@ Poltergeist.Browser = (function() {
435
460
  return this.sendResponse(true);
436
461
  };
437
462
 
463
+ Browser.prototype.set_http_auth = function(user, password) {
464
+ this.page.setHttpAuth(user, password);
465
+ return this.sendResponse(true);
466
+ };
467
+
438
468
  Browser.prototype.set_js_errors = function(value) {
439
469
  this.js_errors = value;
440
470
  return this.sendResponse(true);
@@ -455,6 +485,20 @@ Poltergeist.Browser = (function() {
455
485
  throw new Error('zomg');
456
486
  };
457
487
 
488
+ Browser.prototype.go_back = function() {
489
+ if (this.page.canGoBack) {
490
+ this.page.goBack();
491
+ }
492
+ return this.sendResponse(true);
493
+ };
494
+
495
+ Browser.prototype.go_forward = function() {
496
+ if (this.page.canGoForward) {
497
+ this.page.goForward();
498
+ }
499
+ return this.sendResponse(true);
500
+ };
501
+
458
502
  return Browser;
459
503
 
460
504
  })();
@@ -89,14 +89,15 @@ Poltergeist.ObsoleteNode = (function(_super) {
89
89
  Poltergeist.InvalidSelector = (function(_super) {
90
90
  __extends(InvalidSelector, _super);
91
91
 
92
- function InvalidSelector(selector) {
92
+ function InvalidSelector(method, selector) {
93
+ this.method = method;
93
94
  this.selector = selector;
94
95
  }
95
96
 
96
97
  InvalidSelector.prototype.name = "Poltergeist.InvalidSelector";
97
98
 
98
99
  InvalidSelector.prototype.args = function() {
99
- return [this.selector];
100
+ return [this.method, this.selector];
100
101
  };
101
102
 
102
103
  return InvalidSelector;
@@ -4,7 +4,7 @@ Poltergeist.Node = (function() {
4
4
  var name, _fn, _i, _len, _ref,
5
5
  _this = this;
6
6
 
7
- Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled'];
7
+ Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText'];
8
8
 
9
9
  function Node(page, id) {
10
10
  this.page = page;
@@ -6,7 +6,7 @@ Poltergeist.WebPage = (function() {
6
6
 
7
7
  WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated'];
8
8
 
9
- WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64'];
9
+ WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward'];
10
10
 
11
11
  WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload'];
12
12
 
@@ -145,10 +145,19 @@ Poltergeist.WebPage = (function() {
145
145
  }
146
146
  };
147
147
 
148
+ WebPage.prototype.setHttpAuth = function(user, password) {
149
+ this["native"].settings.userName = user;
150
+ return this["native"].settings.password = password;
151
+ };
152
+
148
153
  WebPage.prototype.networkTraffic = function() {
149
154
  return this._networkTraffic;
150
155
  };
151
156
 
157
+ WebPage.prototype.clearNetworkTraffic = function() {
158
+ return this._networkTraffic = {};
159
+ };
160
+
152
161
  WebPage.prototype.content = function() {
153
162
  return this["native"].frameContent;
154
163
  };
@@ -198,6 +207,10 @@ Poltergeist.WebPage = (function() {
198
207
  return this["native"].viewportSize = size;
199
208
  };
200
209
 
210
+ WebPage.prototype.setPaperSize = function(size) {
211
+ return this["native"].paperSize = size;
212
+ };
213
+
201
214
  WebPage.prototype.scrollPosition = function() {
202
215
  return this["native"].scrollPosition;
203
216
  };
@@ -357,7 +370,7 @@ Poltergeist.WebPage = (function() {
357
370
  };
358
371
 
359
372
  WebPage.prototype.runCommand = function(name, args) {
360
- var result;
373
+ var method, result, selector;
361
374
  result = this.evaluate(function(name, args) {
362
375
  return __poltergeist.externalCall(name, args);
363
376
  }, name, args);
@@ -367,7 +380,8 @@ Poltergeist.WebPage = (function() {
367
380
  throw new Poltergeist.ObsoleteNode;
368
381
  break;
369
382
  case 'PoltergeistAgent.InvalidSelector':
370
- throw new Poltergeist.InvalidSelector(args[1]);
383
+ method = args[0], selector = args[1];
384
+ throw new Poltergeist.InvalidSelector(method, selector);
371
385
  break;
372
386
  default:
373
387
  throw new Poltergeist.BrowserError(result.error.message, result.error.stack);
@@ -377,6 +391,14 @@ Poltergeist.WebPage = (function() {
377
391
  }
378
392
  };
379
393
 
394
+ WebPage.prototype.canGoBack = function() {
395
+ return this["native"].canGoBack;
396
+ };
397
+
398
+ WebPage.prototype.canGoForward = function() {
399
+ return this["native"].canGoForward;
400
+ };
401
+
380
402
  return WebPage;
381
403
 
382
404
  }).call(this);
@@ -51,9 +51,9 @@ class Poltergeist.ObsoleteNode extends Poltergeist.Error
51
51
  toString: -> this.name
52
52
 
53
53
  class Poltergeist.InvalidSelector extends Poltergeist.Error
54
- constructor: (@selector) ->
54
+ constructor: (@method, @selector) ->
55
55
  name: "Poltergeist.InvalidSelector"
56
- args: -> [@selector]
56
+ args: -> [@method, @selector]
57
57
 
58
58
  class Poltergeist.FrameNotFound extends Poltergeist.Error
59
59
  constructor: (@frameName) ->
@@ -4,7 +4,7 @@ class Poltergeist.Node
4
4
  @DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete',
5
5
  'removeAttribute', 'isMultiple', 'select', 'tagName', 'find',
6
6
  'isVisible', 'position', 'trigger', 'parentId', 'mouseEventTest',
7
- 'scrollIntoView', 'isDOMEqual', 'isDisabled']
7
+ 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText']
8
8
 
9
9
  constructor: (@page, @id) ->
10
10
 
@@ -3,7 +3,7 @@ class Poltergeist.WebPage
3
3
  'onLoadStarted', 'onResourceRequested', 'onResourceReceived',
4
4
  'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated']
5
5
 
6
- @DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64']
6
+ @DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward']
7
7
 
8
8
  @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload']
9
9
 
@@ -93,9 +93,16 @@ class Poltergeist.WebPage
93
93
  @_statusCode = response.status
94
94
  @_responseHeaders = response.headers
95
95
 
96
+ setHttpAuth: (user, password) ->
97
+ @native.settings.userName = user
98
+ @native.settings.password = password
99
+
96
100
  networkTraffic: ->
97
101
  @_networkTraffic
98
102
 
103
+ clearNetworkTraffic: ->
104
+ @_networkTraffic = {}
105
+
99
106
  content: ->
100
107
  @native.frameContent
101
108
 
@@ -132,6 +139,9 @@ class Poltergeist.WebPage
132
139
  setViewportSize: (size) ->
133
140
  @native.viewportSize = size
134
141
 
142
+ setPaperSize: (size) ->
143
+ @native.paperSize = size
144
+
135
145
  scrollPosition: ->
136
146
  @native.scrollPosition
137
147
 
@@ -262,8 +272,15 @@ class Poltergeist.WebPage
262
272
  when 'PoltergeistAgent.ObsoleteNode'
263
273
  throw new Poltergeist.ObsoleteNode
264
274
  when 'PoltergeistAgent.InvalidSelector'
265
- throw new Poltergeist.InvalidSelector(args[1])
275
+ [method, selector] = args
276
+ throw new Poltergeist.InvalidSelector(method, selector)
266
277
  else
267
278
  throw new Poltergeist.BrowserError(result.error.message, result.error.stack)
268
279
  else
269
280
  result.value
281
+
282
+ canGoBack: ->
283
+ @native.canGoBack
284
+
285
+ canGoForward: ->
286
+ @native.canGoForward
@@ -161,6 +161,10 @@ module Capybara::Poltergeist
161
161
  browser.render_base64(format, options)
162
162
  end
163
163
 
164
+ def paper_size=(size = {})
165
+ browser.set_paper_size(size)
166
+ end
167
+
164
168
  def resize(width, height)
165
169
  browser.resize(width, height)
166
170
  end
@@ -174,6 +178,10 @@ module Capybara::Poltergeist
174
178
  browser.network_traffic
175
179
  end
176
180
 
181
+ def clear_network_traffic
182
+ browser.clear_network_traffic
183
+ end
184
+
177
185
  def headers
178
186
  browser.get_headers
179
187
  end
@@ -221,6 +229,17 @@ module Capybara::Poltergeist
221
229
  browser.cookies_enabled = flag
222
230
  end
223
231
 
232
+ # * PhantomJS with set settings doesn't send `Authorize` on POST request
233
+ # * With manually set header PhantomJS makes next request with
234
+ # `Authorization: Basic Og==` header when settings are empty and the
235
+ # response was `401 Unauthorized` (which means Base64.encode64(':')).
236
+ # Combining both methods to reach proper behavior.
237
+ def basic_authorize(user, password)
238
+ browser.set_http_auth(user, password)
239
+ credentials = ["#{user}:#{password}"].pack('m*').strip
240
+ add_header('Authorization', "Basic #{credentials}")
241
+ end
242
+
224
243
  def debug
225
244
  if @options[:inspector]
226
245
  inspector.open
@@ -243,5 +262,13 @@ module Capybara::Poltergeist
243
262
  def invalid_element_errors
244
263
  [Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::MouseEventFailed]
245
264
  end
265
+
266
+ def go_back
267
+ browser.go_back
268
+ end
269
+
270
+ def go_forward
271
+ browser.go_forward
272
+ end
246
273
  end
247
274
  end
@@ -65,13 +65,17 @@ module Capybara
65
65
  end
66
66
 
67
67
  class InvalidSelector < ClientError
68
+ def method
69
+ response['args'][0]
70
+ end
71
+
68
72
  def selector
69
- response['args'].first
73
+ response['args'][1]
70
74
  end
71
75
 
72
76
  def message
73
77
  "The browser raised a syntax error while trying to evaluate " \
74
- "the selector #{selector.inspect}"
78
+ "#{method} selector #{selector.inspect}"
75
79
  end
76
80
  end
77
81
 
@@ -142,7 +146,7 @@ module Capybara
142
146
  class PhantomJSTooOld < Error
143
147
  def self.===(other)
144
148
  if Cliver::Dependency::VersionMismatch === other
145
- warn "{name} exception has been deprecated in favor of using the " +
149
+ warn "#{name} exception has been deprecated in favor of using the " +
146
150
  "cliver gem for command-line dependency detection. Please " +
147
151
  "handle Cliver::Dependency::VersionMismatch instead."
148
152
  true
@@ -155,7 +159,7 @@ module Capybara
155
159
  class PhantomJSFailed < Error
156
160
  def self.===(other)
157
161
  if Cliver::Dependency::NotMet === other
158
- warn "{name} exception has been deprecated in favor of using the " +
162
+ warn "#{name} exception has been deprecated in favor of using the " +
159
163
  "cliver gem for command-line dependency detection. Please " +
160
164
  "handle Cliver::Dependency::NotMet instead."
161
165
  true
@@ -1,8 +1,5 @@
1
1
  module Capybara::Poltergeist
2
2
  class Node < Capybara::Driver::Node
3
- NBSP = "\xC2\xA0"
4
- NBSP.force_encoding("UTF-8") if NBSP.respond_to?(:force_encoding)
5
-
6
3
  attr_reader :page_id, :id
7
4
 
8
5
  def initialize(driver, page_id, id)
@@ -72,6 +69,9 @@ module Capybara::Poltergeist
72
69
  end
73
70
  elsif tag_name == 'textarea'
74
71
  command :set, value.to_s
72
+ elsif self[:contenteditable] == 'true'
73
+ command :delete_text
74
+ send_keys(value.to_s)
75
75
  end
76
76
  end
77
77
 
@@ -128,10 +128,15 @@ module Capybara::Poltergeist
128
128
  command :equals, other.id
129
129
  end
130
130
 
131
+ def send_keys(*keys)
132
+ command :send_keys, keys
133
+ end
134
+ alias_method :send_key, :send_keys
135
+
131
136
  private
132
137
 
133
138
  def filter_text(text)
134
- text.to_s.gsub(NBSP, ' ').gsub(/\s+/u, ' ').strip
139
+ Capybara::Helpers.normalize_whitespace(text.to_s)
135
140
  end
136
141
  end
137
142
  end
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
2
  module Poltergeist
3
- VERSION = "1.4.1"
3
+ VERSION = "1.5.0"
4
4
  end
5
5
  end
@@ -26,15 +26,13 @@ module Capybara::Poltergeist
26
26
  @server = start_server(port)
27
27
  end
28
28
 
29
- def port
30
- server.addr[1]
31
- end
32
-
33
29
  def start_server(port)
34
30
  time = Time.now
35
31
 
36
32
  begin
37
- TCPServer.open(HOST, port || 0)
33
+ TCPServer.open(HOST, port || 0).tap do |server|
34
+ @port = server.addr[1]
35
+ end
38
36
  rescue Errno::EADDRINUSE
39
37
  if (Time.now - time) < BIND_TIMEOUT
40
38
  sleep(0.01)
@@ -89,11 +87,10 @@ module Capybara::Poltergeist
89
87
  raise TimeoutError.new(message)
90
88
  end
91
89
 
90
+ # Closing sockets separately as `close_read`, `close_write`
91
+ # causes IO mistakes on JRuby, using just `close` fixes that.
92
92
  def close
93
- [server, socket].compact.each do |s|
94
- s.close_read
95
- s.close_write
96
- end
93
+ [server, socket].compact.each(&:close)
97
94
  end
98
95
  end
99
96
  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.1
4
+ version: 1.5.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: 2013-09-05 00:00:00.000000000 Z
11
+ date: 2013-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 2.1.0
19
+ version: '2.1'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
- version: 2.1.0
26
+ version: '2.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: websocket-driver
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: 0.2.1
61
+ version: 0.3.1
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ~>
67
67
  - !ruby/object:Gem::Version
68
- version: 0.2.1
68
+ version: 0.3.1
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ~>
123
123
  - !ruby/object:Gem::Version
124
124
  version: '1.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pdf-reader
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 1.3.3
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: 1.3.3
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: coffee-script
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +178,8 @@ dependencies:
164
178
  - - ~>
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0.1'
167
- description: PhantomJS driver for Capybara
181
+ description: Poltergeist is a driver for Capybara that allows you to run your tests
182
+ on a headless WebKit browser, provided by PhantomJS.
168
183
  email:
169
184
  - j@jonathanleighton.com
170
185
  executables: []
@@ -202,7 +217,8 @@ files:
202
217
  - LICENSE
203
218
  - README.md
204
219
  homepage: http://github.com/jonleighton/poltergeist
205
- licenses: []
220
+ licenses:
221
+ - MIT
206
222
  metadata: {}
207
223
  post_install_message:
208
224
  rdoc_options: []
@@ -212,7 +228,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
212
228
  requirements:
213
229
  - - '>='
214
230
  - !ruby/object:Gem::Version
215
- version: 1.9.2
231
+ version: 1.9.3
216
232
  required_rubygems_version: !ruby/object:Gem::Requirement
217
233
  requirements:
218
234
  - - '>='
@@ -220,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
220
236
  version: '0'
221
237
  requirements: []
222
238
  rubyforge_project:
223
- rubygems_version: 2.0.3
239
+ rubygems_version: 2.1.11
224
240
  signing_key:
225
241
  specification_version: 4
226
242
  summary: PhantomJS driver for Capybara