poltergeist 1.0.3 → 1.1.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 +70 -18
- data/lib/capybara/poltergeist.rb +5 -3
- data/lib/capybara/poltergeist/browser.rb +35 -16
- data/lib/capybara/poltergeist/client.rb +46 -14
- data/lib/capybara/poltergeist/client/agent.coffee +32 -5
- data/lib/capybara/poltergeist/client/browser.coffee +49 -18
- data/lib/capybara/poltergeist/client/compiled/agent.js +36 -7
- data/lib/capybara/poltergeist/client/compiled/browser.js +62 -17
- data/lib/capybara/poltergeist/client/compiled/node.js +5 -2
- data/lib/capybara/poltergeist/client/compiled/web_page.js +25 -9
- data/lib/capybara/poltergeist/client/node.coffee +2 -2
- data/lib/capybara/poltergeist/client/web_page.coffee +18 -10
- data/lib/capybara/poltergeist/cookie.rb +1 -1
- data/lib/capybara/poltergeist/driver.rb +41 -21
- data/lib/capybara/poltergeist/errors.rb +21 -5
- data/lib/capybara/poltergeist/inspector.rb +1 -1
- data/lib/capybara/poltergeist/node.rb +10 -2
- data/lib/capybara/poltergeist/server.rb +9 -5
- data/lib/capybara/poltergeist/version.rb +1 -1
- data/lib/capybara/poltergeist/web_socket_server.rb +11 -6
- metadata +18 -44
- data/lib/capybara/poltergeist/json.rb +0 -25
- data/lib/capybara/poltergeist/spawn.rb +0 -17
- data/lib/capybara/poltergeist/util.rb +0 -12
data/README.md
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
# Poltergeist - A PhantomJS driver for Capybara #
|
2
2
|
|
3
|
-
Version: 1.0.3
|
4
|
-
|
5
3
|
[](http://travis-ci.org/jonleighton/poltergeist)
|
6
4
|
|
7
5
|
Poltergeist is a driver for [Capybara](https://github.com/jnicklas/capybara). It allows you to
|
8
6
|
run your Capybara tests on a headless [WebKit](http://webkit.org) browser,
|
9
7
|
provided by [PhantomJS](http://www.phantomjs.org/).
|
10
8
|
|
9
|
+
**If you're viewing this at https://github.com/jonleighton/poltergeist,
|
10
|
+
you're reading the documentation for the master branch.
|
11
|
+
[View documentation for the latest release
|
12
|
+
(1.1.0).](https://github.com/jonleighton/poltergeist/tree/v1.1.0)**
|
13
|
+
|
11
14
|
## Installation ##
|
12
15
|
|
13
16
|
Add `poltergeist` to your Gemfile, and in your test setup add:
|
@@ -24,20 +27,21 @@ detail](https://github.com/jnicklas/capybara/blob/master/README.md#transactions-
|
|
24
27
|
|
25
28
|
## Installing PhantomJS ##
|
26
29
|
|
27
|
-
You need at least PhantomJS 1.
|
30
|
+
You need at least PhantomJS 1.8.1. There are *no other external
|
28
31
|
dependencies* (you don't need Qt, or a running X server, etc.)
|
29
32
|
|
30
33
|
### Mac ###
|
31
34
|
|
32
35
|
* *Homebrew*: `brew install phantomjs`
|
33
|
-
* *
|
36
|
+
* *MacPorts*: `sudo port install phantomjs`
|
37
|
+
* *Manual install*: [Download this](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-macosx.zip&can=2&q=)
|
34
38
|
|
35
39
|
### Linux ###
|
36
40
|
|
37
41
|
* Download the [32
|
38
|
-
bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.
|
42
|
+
bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-linux-i686.tar.bz2&can=2&q=)
|
39
43
|
or [64
|
40
|
-
bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.
|
44
|
+
bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-linux-x86_64.tar.bz2&can=2&q=)
|
41
45
|
binary.
|
42
46
|
* Extract the tarball and copy `bin/phantomjs` into your `PATH`
|
43
47
|
|
@@ -46,7 +50,7 @@ binary.
|
|
46
50
|
Do this as a last resort if the binaries don't work for you. It will
|
47
51
|
take quite a long time as it has to build WebKit.
|
48
52
|
|
49
|
-
* Download [the source tarball](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.
|
53
|
+
* Download [the source tarball](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-source.zip&can=2&q=)
|
50
54
|
* Extract and cd in
|
51
55
|
* `./build.sh`
|
52
56
|
|
@@ -55,13 +59,12 @@ guide](http://phantomjs.org/build.html).)
|
|
55
59
|
|
56
60
|
## Compatibility ##
|
57
61
|
|
58
|
-
|
59
|
-
Rubinius 1.8 on UNIX platforms.
|
62
|
+
Poltergeist runs on MRI 1.9, JRuby 1.9 and Rubinius 1.9.
|
60
63
|
|
61
|
-
|
64
|
+
Ruby 1.8 is no longer supported. The last release to support Ruby 1.8
|
65
|
+
was 1.0.2, so you should use that if you still need Ruby 1.8 support.
|
62
66
|
|
63
|
-
|
64
|
-
items into the 'supported' list.
|
67
|
+
Poltergeist does not currently support the Windows operating system.
|
65
68
|
|
66
69
|
## Running on a CI ##
|
67
70
|
|
@@ -108,6 +111,11 @@ the entire page, use `page.driver.render('/path/to/file.png', :full => true)`.
|
|
108
111
|
Sometimes the window size is important to how things are rendered. Poltergeist sets the window
|
109
112
|
size to 1024x768 by default, but you can set this yourself with `page.driver.resize(width, height)`.
|
110
113
|
|
114
|
+
### Clicking precise coordinates ###
|
115
|
+
|
116
|
+
Sometimes its desirable to click a very specific area of the screen. You can accomplish this with
|
117
|
+
`page.driver.click(x, y)`, where x and y are the screen coordinates.
|
118
|
+
|
111
119
|
### Remote debugging (experimental) ###
|
112
120
|
|
113
121
|
If you use the `:inspector => true` option (see below), remote debugging
|
@@ -167,17 +175,22 @@ end
|
|
167
175
|
`options` is a hash of options. The following options are supported:
|
168
176
|
|
169
177
|
* `:phantomjs` (String) - A custom path to the phantomjs executable
|
170
|
-
* `:debug` (Boolean) - When true, debug output is logged to `STDERR
|
178
|
+
* `:debug` (Boolean) - When true, debug output is logged to `STDERR`.
|
179
|
+
Some debug info from the PhantomJS portion of Poltergeist is also
|
180
|
+
output, but this goes to `STDOUT` due to technical limitations.
|
171
181
|
* `:logger` (Object responding to `puts`) - When present, debug output is written to this object
|
182
|
+
* `:phantomjs_logger` (`IO` object) - Where the `STDOUT` from PhantomJS is written to. This is
|
183
|
+
where you `console.log` statements will show up. Default: `STDOUT`
|
172
184
|
* `:timeout` (Numeric) - The number of seconds we'll wait for a response
|
173
|
-
when communicating with PhantomJS.
|
174
|
-
is 30.
|
185
|
+
when communicating with PhantomJS. Default is 30.
|
175
186
|
* `:inspector` (Boolean, String) - See 'Remote Debugging', above.
|
176
187
|
* `:js_errors` (Boolean) - When false, Javascript errors do not get re-raised in Ruby.
|
177
188
|
* `:window_size` (Array) - The dimensions of the browser window in which to test, expressed
|
178
189
|
as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
|
179
|
-
* `:phantomjs_options` (Array) - Additional [command line options](
|
190
|
+
* `:phantomjs_options` (Array) - Additional [command line options](https://github.com/ariya/phantomjs/wiki/API-Reference)
|
180
191
|
to be passed to PhantomJS, e.g. `['--load-images=no', '--ignore-ssl-errors=yes']`
|
192
|
+
* `:extensions` (Array) - An array of JS files to be preloaded into
|
193
|
+
the phantomjs browser. Useful for faking unsupported APIs.
|
181
194
|
* `:port` (Fixnum) - The port which should be used to communicate
|
182
195
|
with the PhantomJS process. Default: 44678.
|
183
196
|
|
@@ -286,11 +299,50 @@ Include as much information as possible. For example:
|
|
286
299
|
|
287
300
|
## Changes ##
|
288
301
|
|
289
|
-
### 1.0
|
302
|
+
### 1.1.0 ###
|
303
|
+
|
304
|
+
#### Features ####
|
305
|
+
|
306
|
+
* Add support for custom phantomjs loggers via `:phantomjs_logger` option.
|
307
|
+
(Gabe Bell)
|
308
|
+
* Add `page.driver.click(x, y)` to click precise coordinates.
|
309
|
+
(Micah Geisel)
|
310
|
+
* Add Capybara 2.0 support. Capybara 1.1 and Ruby 1.8 are *no
|
311
|
+
longer supported*. (Mauro Asprea) [Issue #163]
|
312
|
+
* Add `node.base.double_click` to double click the node.
|
313
|
+
(Andy Shen)
|
314
|
+
* The `:debug` option now causes the PhantomJS portion of Poltergeist
|
315
|
+
to output some additional debug info, which may be useful in
|
316
|
+
figuring out timeout errors.
|
290
317
|
|
291
318
|
#### Bug fixes ####
|
292
319
|
|
293
|
-
*
|
320
|
+
* Fix timing issue when using `within_frame` that could cause errors.
|
321
|
+
[Issue #183, #211] (@errm, @motemen)
|
322
|
+
* Fix bug with `within_frame` not properly switching the context back
|
323
|
+
after the block has executed. [Issue #242]
|
324
|
+
* Fix calculation of click position when clicking within a frame.
|
325
|
+
[Issue #222, #225]
|
326
|
+
* Fix error raising when calling `expires` if not set on cookie.
|
327
|
+
[Issue #203] (@arnvald)
|
328
|
+
* Fix the `:js_errors` option. Previously errors were not being
|
329
|
+
reported, but would still cause commands to fail. [Issue #229]
|
330
|
+
* Fix incorrect time zone handling when setting cookie expiry time
|
331
|
+
[Issue #228]
|
332
|
+
* Send SIGKILL to PhantomJS if it doesn't exit within 2 seconds
|
333
|
+
[Issue #196]
|
334
|
+
* Provide a more informative message for the `ObsoleteNode` error.
|
335
|
+
[Issue #192]
|
336
|
+
* Fix `ObsoleteNode` error when using `attach_file` with the `jQuery
|
337
|
+
File Upload` plugin. [Issue #115]
|
338
|
+
* Add the ability to extend the phantomjs environment via browser
|
339
|
+
options. e.g.
|
340
|
+
`Capybara::Poltergeist::Driver.new( app, :extensions => ['file.js', 'another.js'])`
|
341
|
+
(@JonRowe)
|
342
|
+
* Ensure that a `String` is passed over-the-wire to PhantomJS for
|
343
|
+
file input paths, allowing `attach_file` to be called with arbitry
|
344
|
+
objects such as a Pathname. (@mjtko) [Issue #215]
|
345
|
+
* Cookies can now be set before the first request. [Issue #193]
|
294
346
|
|
295
347
|
### 1.0.2 ###
|
296
348
|
|
data/lib/capybara/poltergeist.rb
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
if RUBY_VERSION < "1.9.2"
|
2
|
+
raise "This version of Capybara/Poltergeist does not support Ruby versions " \
|
3
|
+
"less than 1.9.2."
|
4
|
+
end
|
5
|
+
|
1
6
|
require 'capybara'
|
2
7
|
|
3
8
|
module Capybara
|
@@ -9,12 +14,9 @@ module Capybara
|
|
9
14
|
require 'capybara/poltergeist/web_socket_server'
|
10
15
|
require 'capybara/poltergeist/client'
|
11
16
|
require 'capybara/poltergeist/inspector'
|
12
|
-
require 'capybara/poltergeist/spawn'
|
13
|
-
require 'capybara/poltergeist/json'
|
14
17
|
require 'capybara/poltergeist/network_traffic'
|
15
18
|
require 'capybara/poltergeist/errors'
|
16
19
|
require 'capybara/poltergeist/cookie'
|
17
|
-
require 'capybara/poltergeist/util'
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -1,20 +1,21 @@
|
|
1
|
-
require '
|
1
|
+
require 'json'
|
2
2
|
require 'time'
|
3
3
|
|
4
4
|
module Capybara::Poltergeist
|
5
5
|
class Browser
|
6
|
-
attr_reader :server, :client, :logger
|
6
|
+
attr_reader :server, :client, :logger
|
7
7
|
|
8
|
-
def initialize(server, client, logger = nil
|
9
|
-
@server
|
10
|
-
@client
|
11
|
-
@logger
|
12
|
-
@js_errors = js_errors
|
8
|
+
def initialize(server, client, logger = nil)
|
9
|
+
@server = server
|
10
|
+
@client = client
|
11
|
+
@logger = logger
|
13
12
|
end
|
14
13
|
|
15
14
|
def restart
|
16
15
|
server.restart
|
17
16
|
client.restart
|
17
|
+
|
18
|
+
self.debug = @debug if @debug
|
18
19
|
end
|
19
20
|
|
20
21
|
def visit(url)
|
@@ -74,6 +75,10 @@ module Capybara::Poltergeist
|
|
74
75
|
command 'visible', page_id, id
|
75
76
|
end
|
76
77
|
|
78
|
+
def click_coordinates(x, y)
|
79
|
+
command 'click_coordinates', x, y
|
80
|
+
end
|
81
|
+
|
77
82
|
def evaluate(script)
|
78
83
|
command 'evaluate', script
|
79
84
|
end
|
@@ -100,6 +105,10 @@ module Capybara::Poltergeist
|
|
100
105
|
command 'click', page_id, id
|
101
106
|
end
|
102
107
|
|
108
|
+
def double_click(page_id, id)
|
109
|
+
command 'double_click', page_id, id
|
110
|
+
end
|
111
|
+
|
103
112
|
def drag(page_id, id, other_id)
|
104
113
|
command 'drag', page_id, id, other_id
|
105
114
|
end
|
@@ -150,8 +159,8 @@ module Capybara::Poltergeist
|
|
150
159
|
end
|
151
160
|
|
152
161
|
def set_cookie(cookie)
|
153
|
-
if cookie[:expires]
|
154
|
-
cookie[:expires] = cookie[:expires].
|
162
|
+
if cookie[:expires]
|
163
|
+
cookie[:expires] = cookie[:expires].to_i * 1000
|
155
164
|
end
|
156
165
|
|
157
166
|
command 'set_cookie', cookie
|
@@ -161,21 +170,31 @@ module Capybara::Poltergeist
|
|
161
170
|
command 'remove_cookie', name
|
162
171
|
end
|
163
172
|
|
173
|
+
def js_errors=(val)
|
174
|
+
command 'set_js_errors', !!val
|
175
|
+
end
|
176
|
+
|
177
|
+
def extensions=(names)
|
178
|
+
Array(names).each do |name|
|
179
|
+
command 'add_extension', name
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def debug=(val)
|
184
|
+
@debug = val
|
185
|
+
command 'set_debug', !!val
|
186
|
+
end
|
187
|
+
|
164
188
|
def command(name, *args)
|
165
189
|
message = { 'name' => name, 'args' => args }
|
166
190
|
log message.inspect
|
167
191
|
|
168
|
-
json = JSON.load(server.send(JSON.
|
192
|
+
json = JSON.load(server.send(JSON.generate(message)))
|
169
193
|
log json.inspect
|
170
194
|
|
171
195
|
if json['error']
|
172
196
|
if json['error']['name'] == 'Poltergeist.JavascriptError'
|
173
|
-
|
174
|
-
if js_errors
|
175
|
-
raise error
|
176
|
-
else
|
177
|
-
log error
|
178
|
-
end
|
197
|
+
raise JavascriptError.new(json['error'])
|
179
198
|
else
|
180
199
|
raise BrowserError.new(json['error'])
|
181
200
|
end
|
@@ -1,22 +1,27 @@
|
|
1
|
+
require "timeout"
|
2
|
+
|
1
3
|
module Capybara::Poltergeist
|
2
4
|
class Client
|
3
5
|
PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
|
4
|
-
PHANTOMJS_VERSION = '1.
|
6
|
+
PHANTOMJS_VERSION = '1.8.1'
|
5
7
|
PHANTOMJS_NAME = 'phantomjs'
|
6
8
|
|
9
|
+
KILL_TIMEOUT = 2 # seconds
|
10
|
+
|
7
11
|
def self.start(*args)
|
8
12
|
client = new(*args)
|
9
13
|
client.start
|
10
14
|
client
|
11
15
|
end
|
12
16
|
|
13
|
-
attr_reader :pid, :
|
17
|
+
attr_reader :pid, :server, :path, :window_size, :phantomjs_options
|
14
18
|
|
15
|
-
def initialize(
|
16
|
-
@
|
19
|
+
def initialize(server, options = {})
|
20
|
+
@server = server
|
17
21
|
@path = options[:path] || PHANTOMJS_NAME
|
18
22
|
@window_size = options[:window_size] || [1024, 768]
|
19
23
|
@phantomjs_options = options[:phantomjs_options] || []
|
24
|
+
@phantomjs_logger = options[:phantomjs_logger] || $stdout
|
20
25
|
|
21
26
|
pid = Process.pid
|
22
27
|
at_exit { stop if Process.pid == pid }
|
@@ -24,18 +29,34 @@ module Capybara::Poltergeist
|
|
24
29
|
|
25
30
|
def start
|
26
31
|
check_phantomjs_version
|
27
|
-
|
32
|
+
read, write = IO.pipe
|
33
|
+
@out_thread = Thread.new {
|
34
|
+
while !read.eof? && data = read.readpartial(1024)
|
35
|
+
@phantomjs_logger.write(data)
|
36
|
+
end
|
37
|
+
}
|
38
|
+
|
39
|
+
redirect_stdout(write) do
|
40
|
+
@pid = Process.spawn(*command.map(&:to_s))
|
41
|
+
end
|
28
42
|
end
|
29
43
|
|
30
44
|
def stop
|
31
45
|
if pid
|
32
46
|
begin
|
33
47
|
Process.kill('TERM', pid)
|
34
|
-
|
48
|
+
|
49
|
+
begin
|
50
|
+
Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
|
51
|
+
rescue Timeout::Error
|
52
|
+
Process.kill('KILL', pid)
|
53
|
+
Process.wait(pid)
|
54
|
+
end
|
35
55
|
rescue Errno::ESRCH, Errno::ECHILD
|
36
56
|
# Zed's dead, baby
|
37
57
|
end
|
38
58
|
|
59
|
+
@out_thread.kill
|
39
60
|
@pid = nil
|
40
61
|
end
|
41
62
|
end
|
@@ -46,14 +67,12 @@ module Capybara::Poltergeist
|
|
46
67
|
end
|
47
68
|
|
48
69
|
def command
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
parts
|
56
|
-
end
|
70
|
+
parts = [path]
|
71
|
+
parts.concat phantomjs_options
|
72
|
+
parts << PHANTOMJS_SCRIPT
|
73
|
+
parts << server.port
|
74
|
+
parts.concat window_size
|
75
|
+
parts
|
57
76
|
end
|
58
77
|
|
59
78
|
private
|
@@ -71,5 +90,18 @@ module Capybara::Poltergeist
|
|
71
90
|
|
72
91
|
@phantomjs_version_checked = true
|
73
92
|
end
|
93
|
+
|
94
|
+
# This abomination is because JRuby doesn't support the :out option of
|
95
|
+
# Process.spawn
|
96
|
+
def redirect_stdout(to)
|
97
|
+
prev = STDOUT.dup
|
98
|
+
prev.autoclose = false
|
99
|
+
$stdout = to
|
100
|
+
STDOUT.reopen(to)
|
101
|
+
yield
|
102
|
+
ensure
|
103
|
+
STDOUT.reopen(prev)
|
104
|
+
$stdout = STDOUT
|
105
|
+
end
|
74
106
|
end
|
75
107
|
end
|
@@ -52,6 +52,12 @@ class PoltergeistAgent
|
|
52
52
|
throw new PoltergeistAgent.ObsoleteNode if node.isObsolete()
|
53
53
|
node[name].apply(node, args)
|
54
54
|
|
55
|
+
beforeUpload: (id) ->
|
56
|
+
this.get(id).setAttribute('_poltergeist_selected', '')
|
57
|
+
|
58
|
+
afterUpload: (id) ->
|
59
|
+
this.get(id).removeAttribute('_poltergeist_selected')
|
60
|
+
|
55
61
|
class PoltergeistAgent.ObsoleteNode
|
56
62
|
toString: -> "PoltergeistAgent.ObsoleteNode"
|
57
63
|
|
@@ -141,19 +147,35 @@ class PoltergeistAgent.Node
|
|
141
147
|
else
|
142
148
|
true
|
143
149
|
|
150
|
+
frameOffset: ->
|
151
|
+
win = window
|
152
|
+
offset = { top: 0, left: 0 }
|
153
|
+
|
154
|
+
while win.frameElement
|
155
|
+
rect = window.frameElement.getClientRects()[0]
|
156
|
+
win = win.parent
|
157
|
+
|
158
|
+
offset.top += rect.top
|
159
|
+
offset.left += rect.left
|
160
|
+
|
161
|
+
offset
|
162
|
+
|
144
163
|
position: ->
|
145
164
|
rect = @element.getClientRects()[0]
|
146
165
|
throw new PoltergeistAgent.ObsoleteNode unless rect
|
166
|
+
frameOffset = this.frameOffset()
|
147
167
|
|
148
|
-
{
|
149
|
-
top: rect.top,
|
150
|
-
right: rect.right,
|
151
|
-
left: rect.left,
|
152
|
-
bottom: rect.bottom,
|
168
|
+
pos = {
|
169
|
+
top: rect.top + frameOffset.top,
|
170
|
+
right: rect.right + frameOffset.left,
|
171
|
+
left: rect.left + frameOffset.left,
|
172
|
+
bottom: rect.bottom + frameOffset.top,
|
153
173
|
width: rect.width,
|
154
174
|
height: rect.height
|
155
175
|
}
|
156
176
|
|
177
|
+
pos
|
178
|
+
|
157
179
|
trigger: (name) ->
|
158
180
|
if Node.EVENTS.MOUSE.indexOf(name) != -1
|
159
181
|
event = document.createEvent('MouseEvent')
|
@@ -177,6 +199,11 @@ class PoltergeistAgent.Node
|
|
177
199
|
@element.blur()
|
178
200
|
|
179
201
|
clickTest: (x, y) ->
|
202
|
+
frameOffset = this.frameOffset()
|
203
|
+
|
204
|
+
x -= frameOffset.left
|
205
|
+
y -= frameOffset.top
|
206
|
+
|
180
207
|
el = origEl = document.elementFromPoint(x, y)
|
181
208
|
|
182
209
|
while el
|