poltergeist 0.6.0 → 0.7.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.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Poltergeist - A PhantomJS driver for Capybara #
2
2
 
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
 
5
5
  [![Build Status](https://secure.travis-ci.org/jonleighton/poltergeist.png)](http://travis-ci.org/jonleighton/poltergeist)
6
6
  [![Dependency Status](https://gemnasium.com/jonleighton/poltergeist.png)](https://gemnasium.com/jonleighton/poltergeist)
@@ -18,47 +18,31 @@ require 'capybara/poltergeist'
18
18
  Capybara.javascript_driver = :poltergeist
19
19
  ```
20
20
 
21
- ## Important note about Rack versions < 1.3.0 ##
22
-
23
- Prior to version 1.3.0, the Rack handlers for Mongrel and Thin wrap your
24
- app in the `Rack::Chunked` middleware so that it uses
25
- `Transfer-Encoding: chunked`
26
- ([commit](https://github.com/rack/rack/commit/50cdd0bf000a9ffb3eb3760fda2ff3e1ad18f3a7)).
27
- This has been observed to cause problems,
28
- probably due to race conditions in Qt's HTTP handling code, so you are
29
- recommended to avoid this by specifying your own server setup for
30
- Capybara:
31
-
32
- ``` ruby
33
- Capybara.server do |app, port|
34
- require 'rack/handler/thin'
35
- Thin::Logging.silent = true
36
- Thin::Server.new('0.0.0.0', port, app).start
37
- end
38
- ```
39
-
40
- If you're using Rails 3.0, this affects you. If you're using Rails 3.1+,
41
- this doesn't affect you.
21
+ If you were previously using the `:rack_test` driver, be aware that
22
+ your app will now run in a separate thread and this can have
23
+ consequences for transactional tests. [See the Capybara README for more
24
+ detail](https://github.com/jnicklas/capybara/blob/master/README.md#transactions-and-database-setup).
42
25
 
43
26
  ## Installing PhantomJS ##
44
27
 
45
- You need PhantomJS 1.5.0. There are no other dependencies (you don't
46
- need Qt, or Xvfb, etc.)
28
+ You need at least PhantomJS 1.6.0, but 1.6.1 is recommended as there some issues with the former.
29
+ There are *no other external dependencies* (you don't need Qt, or a running X
30
+ server, etc.)
47
31
 
48
32
  ### Mac ###
49
33
 
50
- * *With homebrew*: `brew install phantomjs`
51
- * *Without homebrew*: [Download this](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-macosx-static.zip&can=2&q=)
34
+ * *Manual install*: [Download this](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.6.1-macosx-static.zip&can=2&q=)
35
+ * *Homebrew*: `brew install phantomjs`
52
36
 
53
37
  ### Linux ###
54
38
 
55
39
  * Download the [32
56
- bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-linux-x86-dynamic.tar.gz&can=2&q=)
40
+ bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.6.1-linux-i686-dynamic.tar.bz2&can=2&q=)
57
41
  or [64
58
- bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-linux-x86_64-dynamic.tar.gz&can=2&q=)
42
+ bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.6.1-linux-x86_64-dynamic.tar.bz2&can=2&q=)
59
43
  binary.
60
- * Extract it: `sudo tar xvzf phantomjs-1.5.0-linux-*-dynamic.tar.gz -C /usr/local`
61
- * Link it: `sudo ln -s /usr/local/phantomjs/bin/phantomjs /usr/local/bin/phantomjs`
44
+ * Extract it: `sudo tar xvjf phantomjs-1.6.1-linux-*-dynamic.tar.gz -C /usr/local`
45
+ * Link it: `sudo ln -s /usr/local/phantomjs-1.6.1-linux*/bin/phantomjs /usr/local/bin/phantomjs`
62
46
 
63
47
  (Note that you cannot copy the `/usr/local/phantomjs/bin/phantomjs`
64
48
  binary elsewhere on its own as it dynamically links with other files in
@@ -69,7 +53,7 @@ binary elsewhere on its own as it dynamically links with other files in
69
53
  Do this as a last resort if the binaries don't work for you. It will
70
54
  take quite a long time as it has to build WebKit.
71
55
 
72
- * Download [the source tarball](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-source.tar.gz&can=2&q=)
56
+ * Download [the source tarball](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.6.1-source.zip&can=2&q=)
73
57
  * Extract and cd in
74
58
  * `./build.sh`
75
59
 
@@ -79,7 +63,7 @@ Supported: MRI 1.8.7, MRI 1.9.2, MRI 1.9.3, JRuby 1.8, JRuby 1.9.
79
63
 
80
64
  Not supported:
81
65
 
82
- * Rubinius (due to some unknown socket related issues)
66
+ * Rubinius
83
67
  * Windows
84
68
 
85
69
  Contributions are welcome in order to move 'unsupported'
@@ -90,11 +74,11 @@ items into the 'supported' list.
90
74
  There are no special steps to take. You don't need Xvfb or any running X
91
75
  server at all.
92
76
 
93
- [Travis CI](http://travis-ci.org/) has PhantomJS 1.5.0 installed.
94
-
95
- You may like to use their [chef
96
- cookbook](https://github.com/travis-ci/travis-cookbooks/tree/master/ci_environment/phantomjs)
97
- on your own servers.
77
+ Depending on your tests, one thing that you may need is some fonts. If
78
+ you're getting errors on a CI that don't occur during development then
79
+ try taking some screenshots - it may well be missing fonts throwing
80
+ things off kilter. Your distro will have various font packages available
81
+ to install.
98
82
 
99
83
  ## What's supported? ##
100
84
 
@@ -126,9 +110,28 @@ When this option is enabled, you can insert `page.driver.debug` into
126
110
  your tests to pause the test and launch a browser which gives you the
127
111
  WebKit inspector to view your test run with.
128
112
 
129
- (This feature is considered experimental - it needs more polish
130
- and [apparently will only work on
131
- Linux](http://code.google.com/p/phantomjs/issues/detail?id=430).)
113
+ [Read more
114
+ here](http://jonathanleighton.com/articles/2012/poltergeist-0-6-0/)
115
+
116
+ ### Setting request headers ###
117
+
118
+ Additional HTTP request headers can be set like so:
119
+
120
+ ``` ruby
121
+ page.driver.headers = {
122
+ "Cookie" => "foo=bar",
123
+ "Host" => "foo.com"
124
+ }
125
+ ```
126
+
127
+ They will be cleared between tests, so you do not have to do this manually.
128
+
129
+ ### Inspecting network traffic ###
130
+
131
+ You can inspect the network traffic (i.e. what resources have been
132
+ loaded) on the current page by calling `page.driver.network_traffic`.
133
+ This returns an array of request objects. A request object has a
134
+ `response_parts` method containing data about the response chunks.
132
135
 
133
136
  ## Customization ##
134
137
 
@@ -150,6 +153,11 @@ end
150
153
  when communicating with PhantomJS. `nil` means wait forever. Default
151
154
  is 30.
152
155
  * `:inspector` (Boolean, String) - See 'Remote Debugging', above.
156
+ * `:js_errors` (Boolean) - When false, Javascript errors do not get re-raised in Ruby.
157
+ * `:window_size` (Array) - The dimensions of the browser window in which to test, expressed
158
+ as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
159
+ * `:phantomjs_options` (Array) - Additional [command line options](http://code.google.com/p/phantomjs/wiki/Interface#Command-line_Options)
160
+ to be passed to PhantomJS, e.g. `['--load-images=no', '--ignore-ssl-errors=yes']`
153
161
 
154
162
  ## Bugs ##
155
163
 
@@ -173,6 +181,35 @@ makes debugging easier). Running `rake autocompile` will watch the
173
181
 
174
182
  ## Changes ##
175
183
 
184
+ ### 0.7.0 ###
185
+
186
+ #### Features ####
187
+
188
+ * Added an option `:js_errors`, allowing poltergeist to continue
189
+ running after JS errors. (John Griffin & Tom Stuart) [Issue #62] [Issue #69]
190
+ * Added an option `:window_size`, allowing users to specify
191
+ dimensions to which the browser window will be resized.
192
+ (Tom Stuart) [Issue #53]
193
+ * Capybara 1.0 is no longer supported. Capybara ~> 1.1 is required.
194
+ * Added ability to set arbitrary http request headers
195
+ * Inspect network traffic on the page via
196
+ `page.driver.network_traffic` (Doug McInnes) [Issue #77]
197
+ * Added an option `:phantomjs_options`, allowing users to specify
198
+ additional command-line options passed to phantomjs executable.
199
+ (wynst) [Issue #97]
200
+ * Scroll element into viewport if needed on click (Gabriel Sobrinho)
201
+ [Issue #83]
202
+ * Added status code support. (Dmitriy Nesteryuk and Jon Leighton) [Issue #37]
203
+
204
+ #### Bug fixes ###
205
+
206
+ * Fix issue with `ClickFailed` exception happening with a negative
207
+ co-ordinate (which should be impossible). (Jon Leighton, Gabriel
208
+ Sobrinho, Tom Stuart) [Issue #60]
209
+ * Fix issue with `undefined method map for "[]":String`, which
210
+ happened when dealing with pages that include JS rewriting
211
+ Array.prototype.toJSON. (Tom Stuart) [Issue #63]
212
+
176
213
  ### 0.6.0 ###
177
214
 
178
215
  #### Features ####
@@ -2,17 +2,17 @@ require 'capybara'
2
2
 
3
3
  module Capybara
4
4
  module Poltergeist
5
- autoload :Driver, 'capybara/poltergeist/driver'
6
- autoload :Browser, 'capybara/poltergeist/browser'
7
- autoload :Node, 'capybara/poltergeist/node'
8
- autoload :ServerManager, 'capybara/poltergeist/server_manager'
9
- autoload :Server, 'capybara/poltergeist/server'
10
- autoload :WebSocketServer, 'capybara/poltergeist/web_socket_server'
11
- autoload :Client, 'capybara/poltergeist/client'
12
- autoload :Util, 'capybara/poltergeist/util'
13
- autoload :Inspector, 'capybara/poltergeist/inspector'
14
- autoload :Spawn, 'capybara/poltergeist/spawn'
15
-
5
+ require 'capybara/poltergeist/driver'
6
+ require 'capybara/poltergeist/browser'
7
+ require 'capybara/poltergeist/node'
8
+ require 'capybara/poltergeist/server'
9
+ require 'capybara/poltergeist/web_socket_server'
10
+ require 'capybara/poltergeist/client'
11
+ require 'capybara/poltergeist/util'
12
+ require 'capybara/poltergeist/inspector'
13
+ require 'capybara/poltergeist/spawn'
14
+ require 'capybara/poltergeist/json'
15
+ require 'capybara/poltergeist/network_traffic'
16
16
  require 'capybara/poltergeist/errors'
17
17
  end
18
18
  end
@@ -2,12 +2,13 @@ require 'multi_json'
2
2
 
3
3
  module Capybara::Poltergeist
4
4
  class Browser
5
- attr_reader :server, :client, :logger
5
+ attr_reader :server, :client, :logger, :js_errors
6
6
 
7
- def initialize(server, client, logger = nil)
8
- @server = server
9
- @client = client
10
- @logger = logger
7
+ def initialize(server, client, logger = nil, js_errors = true)
8
+ @server = server
9
+ @client = client
10
+ @logger = logger
11
+ @js_errors = js_errors
11
12
  end
12
13
 
13
14
  def restart
@@ -15,14 +16,18 @@ module Capybara::Poltergeist
15
16
  client.restart
16
17
  end
17
18
 
18
- def visit(url)
19
- command 'visit', url
19
+ def visit(url, headers)
20
+ command 'visit', url, headers
20
21
  end
21
22
 
22
23
  def current_url
23
24
  command 'current_url'
24
25
  end
25
26
 
27
+ def status_code
28
+ command 'status_code'
29
+ end
30
+
26
31
  def body
27
32
  command 'body'
28
33
  end
@@ -79,6 +84,7 @@ module Capybara::Poltergeist
79
84
  def within_frame(id, &block)
80
85
  command 'push_frame', id
81
86
  yield
87
+ ensure
82
88
  command 'pop_frame'
83
89
  end
84
90
 
@@ -103,29 +109,46 @@ module Capybara::Poltergeist
103
109
  end
104
110
 
105
111
  def render(path, options = {})
106
- command 'render', path, !!options[:full]
112
+ command 'render', path.to_s, !!options[:full]
107
113
  end
108
114
 
109
115
  def resize(width, height)
110
116
  command 'resize', width, height
111
117
  end
112
118
 
119
+ def network_traffic
120
+ command('network_traffic').values.map do |event|
121
+ NetworkTraffic::Request.new(
122
+ event['request'],
123
+ event['responseParts'].map { |response| NetworkTraffic::Response.new(response) }
124
+ )
125
+ end
126
+ end
127
+
128
+ def equals(page_id, id, other_id)
129
+ command('equals', page_id, id, other_id)
130
+ end
131
+
113
132
  def command(name, *args)
114
133
  message = { 'name' => name, 'args' => args }
115
134
  log message.inspect
116
135
 
117
- json = MultiJson.decode(server.send(MultiJson.encode(message)))
136
+ json = JSON.load(server.send(JSON.dump(message)))
118
137
  log json.inspect
119
138
 
120
139
  if json['error']
121
140
  if json['error']['name'] == 'Poltergeist.JavascriptError'
122
- raise JavascriptError.new(json['error'])
141
+ error = JavascriptError.new(json['error'])
142
+ if js_errors
143
+ raise error
144
+ else
145
+ log error
146
+ end
123
147
  else
124
148
  raise BrowserError.new(json['error'])
125
149
  end
126
- else
127
- json['response']
128
150
  end
151
+ json['response']
129
152
 
130
153
  rescue DeadClient
131
154
  restart
@@ -1,7 +1,7 @@
1
1
  module Capybara::Poltergeist
2
2
  class Client
3
3
  PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
4
- PHANTOMJS_VERSION = '1.5.0'
4
+ PHANTOMJS_VERSION = '1.6.0'
5
5
  PHANTOMJS_NAME = 'phantomjs'
6
6
 
7
7
  def self.start(*args)
@@ -10,12 +10,13 @@ module Capybara::Poltergeist
10
10
  client
11
11
  end
12
12
 
13
- attr_reader :pid, :port, :path, :inspector
13
+ attr_reader :pid, :port, :path, :window_size, :phantomjs_options
14
14
 
15
- def initialize(port, inspector = nil, path = nil)
16
- @port = port
17
- @inspector = inspector
18
- @path = path || PHANTOMJS_NAME
15
+ def initialize(port, options = {})
16
+ @port = port
17
+ @path = options[:path] || PHANTOMJS_NAME
18
+ @window_size = options[:window_size] || [1024, 768]
19
+ @phantomjs_options = options[:phantomjs_options] || []
19
20
 
20
21
  pid = Process.pid
21
22
  at_exit { stop if Process.pid == pid }
@@ -47,14 +48,10 @@ module Capybara::Poltergeist
47
48
  def command
48
49
  @command ||= begin
49
50
  parts = [path]
50
-
51
- if inspector
52
- parts << "--remote-debugger-port=#{inspector.port}"
53
- parts << "--remote-debugger-autorun=yes"
54
- end
55
-
51
+ parts.concat phantomjs_options
56
52
  parts << PHANTOMJS_SCRIPT
57
53
  parts << port
54
+ parts.concat window_size
58
55
  parts
59
56
  end
60
57
  end
@@ -7,8 +7,18 @@ class PoltergeistAgent
7
7
  @windows = []
8
8
  this.pushWindow(window)
9
9
 
10
- externalCall: (name, arguments) ->
11
- { value: this[name].apply(this, arguments) }
10
+ externalCall: (name, args) ->
11
+ try
12
+ { value: this[name].apply(this, args) }
13
+ catch error
14
+ { error: { message: error.toString(), stack: error.stack } }
15
+
16
+ @stringify: (object) ->
17
+ JSON.stringify object, (key, value) ->
18
+ if Array.isArray(this[key])
19
+ return this[key]
20
+ else
21
+ return value
12
22
 
13
23
  pushWindow: (new_window) ->
14
24
  @windows.push(new_window)
@@ -55,10 +65,10 @@ class PoltergeistAgent
55
65
  get: (id) ->
56
66
  @nodes[id] or= new PoltergeistAgent.Node(this, @elements[id])
57
67
 
58
- nodeCall: (id, name, arguments) ->
68
+ nodeCall: (id, name, args) ->
59
69
  node = this.get(id)
60
70
  throw new PoltergeistAgent.ObsoleteNode if node.isObsolete()
61
- node[name].apply(node, arguments)
71
+ node[name].apply(node, args)
62
72
 
63
73
  class PoltergeistAgent.ObsoleteNode
64
74
  toString: -> "PoltergeistAgent.ObsoleteNode"
@@ -91,7 +101,33 @@ class PoltergeistAgent.Node
91
101
 
92
102
  changed: ->
93
103
  event = document.createEvent('HTMLEvents')
94
- event.initEvent("change", true, false)
104
+ event.initEvent('change', true, false)
105
+ @element.dispatchEvent(event)
106
+
107
+ input: ->
108
+ event = document.createEvent('HTMLEvents')
109
+ event.initEvent('input', true, false)
110
+ @element.dispatchEvent(event)
111
+
112
+ keyupdowned: (eventName, keyCode) ->
113
+ event = document.createEvent('UIEvents')
114
+ event.initEvent(eventName, true, true)
115
+ event.keyCode = keyCode
116
+ event.which = keyCode
117
+ event.charCode = 0
118
+ @element.dispatchEvent(event)
119
+
120
+ keypressed: (altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) ->
121
+ event = document.createEvent('UIEvents')
122
+ event.initEvent('keypress', true, true)
123
+ event.window = @agent.window
124
+ event.altKey = altKey
125
+ event.ctrlKey = ctrlKey
126
+ event.shiftKey = shiftKey
127
+ event.metaKey = metaKey
128
+ event.keyCode = keyCode
129
+ event.charCode = charCode
130
+ event.which = keyCode
95
131
  @element.dispatchEvent(event)
96
132
 
97
133
  insideBody: ->
@@ -120,6 +156,9 @@ class PoltergeistAgent.Node
120
156
  else
121
157
  @element.getAttribute(name)
122
158
 
159
+ scrollIntoView: ->
160
+ @element.scrollIntoViewIfNeeded()
161
+
123
162
  value: ->
124
163
  if @element.tagName == 'SELECT' && @element.multiple
125
164
  option.value for option in @element.children when option.selected
@@ -130,8 +169,20 @@ class PoltergeistAgent.Node
130
169
  if (@element.maxLength >= 0)
131
170
  value = value.substr(0, @element.maxLength)
132
171
 
133
- @element.value = value
172
+ @element.value = ''
173
+ this.trigger('focus')
174
+
175
+ for char in value
176
+ @element.value += char
177
+
178
+ keyCode = this.characterToKeyCode(char)
179
+ this.keyupdowned('keydown', keyCode)
180
+ this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0))
181
+ this.keyupdowned('keyup', keyCode)
182
+
134
183
  this.changed()
184
+ this.input()
185
+ this.trigger('blur')
135
186
 
136
187
  isMultiple: ->
137
188
  @element.multiple
@@ -209,6 +260,48 @@ class PoltergeistAgent.Node
209
260
  selector += ".#{className}"
210
261
  selector
211
262
 
263
+ characterToKeyCode: (character) ->
264
+ code = character.toUpperCase().charCodeAt(0)
265
+ specialKeys =
266
+ 96: 192 #`
267
+ 45: 189 #-
268
+ 61: 187 #=
269
+ 91: 219 #[
270
+ 93: 221 #]
271
+ 92: 220 #\
272
+ 59: 186 #;
273
+ 39: 222 #'
274
+ 44: 188 #,
275
+ 46: 190 #.
276
+ 47: 191 #/
277
+ 127: 46 #delete
278
+ 126: 192 #~
279
+ 33: 49 #!
280
+ 64: 50 #@
281
+ 35: 51 ##
282
+ 36: 52 #$
283
+ 37: 53 #%
284
+ 94: 54 #^
285
+ 38: 55 #&
286
+ 42: 56 #*
287
+ 40: 57 #(
288
+ 41: 48 #)
289
+ 95: 189 #_
290
+ 43: 187 #+
291
+ 123: 219 #{
292
+ 125: 221 #}
293
+ 124: 220 #|
294
+ 58: 186 #:
295
+ 34: 222 #"
296
+ 60: 188 #<
297
+ 62: 190 #>
298
+ 63: 191 #?
299
+
300
+ specialKeys[code] || code
301
+
302
+ isDOMEqual: (other_id) ->
303
+ @element == @agent.get(other_id).element
304
+
212
305
  window.__poltergeist = new PoltergeistAgent
213
306
 
214
307
  document.addEventListener(