poltergeist 1.4.1 → 1.5.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: 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