poltergeist 1.1.2 → 1.2.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 +50 -21
- data/lib/capybara/poltergeist.rb +1 -0
- data/lib/capybara/poltergeist/browser.rb +39 -15
- data/lib/capybara/poltergeist/client.rb +11 -7
- data/lib/capybara/poltergeist/client/agent.coffee +104 -19
- data/lib/capybara/poltergeist/client/browser.coffee +34 -18
- data/lib/capybara/poltergeist/client/compiled/agent.js +154 -26
- data/lib/capybara/poltergeist/client/compiled/browser.js +54 -22
- data/lib/capybara/poltergeist/client/compiled/connection.js +0 -2
- data/lib/capybara/poltergeist/client/compiled/main.js +32 -15
- data/lib/capybara/poltergeist/client/compiled/node.js +13 -19
- data/lib/capybara/poltergeist/client/compiled/web_page.js +19 -5
- data/lib/capybara/poltergeist/client/main.coffee +9 -4
- data/lib/capybara/poltergeist/client/node.coffee +11 -19
- data/lib/capybara/poltergeist/client/web_page.coffee +4 -5
- data/lib/capybara/poltergeist/driver.rb +15 -3
- data/lib/capybara/poltergeist/errors.rb +20 -5
- data/lib/capybara/poltergeist/node.rb +32 -6
- data/lib/capybara/poltergeist/utility.rb +9 -0
- data/lib/capybara/poltergeist/version.rb +1 -1
- metadata +14 -7
data/README.md
CHANGED
@@ -9,7 +9,16 @@ 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.
|
12
|
+
(1.2.0).](https://github.com/jonleighton/poltergeist/tree/v1.2.0)**
|
13
|
+
|
14
|
+
## Getting help ##
|
15
|
+
|
16
|
+
Questions should be posted [on Stack
|
17
|
+
Overflow, using the 'poltergeist' tag](http://stackoverflow.com/questions/tagged/poltergeist).
|
18
|
+
|
19
|
+
Bug reports should be posted [on
|
20
|
+
GitHub](https://github.com/jonleighton/poltergeist/issues) (and be sure
|
21
|
+
to read the bug reporting guidance below).
|
13
22
|
|
14
23
|
## Installation ##
|
15
24
|
|
@@ -45,6 +54,9 @@ bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.1-li
|
|
45
54
|
binary.
|
46
55
|
* Extract the tarball and copy `bin/phantomjs` into your `PATH`
|
47
56
|
|
57
|
+
### Windows ###
|
58
|
+
* Download the [precompiled binary](http://phantomjs.org/download.html) for Windows
|
59
|
+
|
48
60
|
### Manual compilation ###
|
49
61
|
|
50
62
|
Do this as a last resort if the binaries don't work for you. It will
|
@@ -59,13 +71,13 @@ guide](http://phantomjs.org/build.html).)
|
|
59
71
|
|
60
72
|
## Compatibility ##
|
61
73
|
|
62
|
-
Poltergeist runs on MRI 1.9, JRuby 1.9 and Rubinius 1.9.
|
74
|
+
Poltergeist runs on MRI 1.9, JRuby 1.9 and Rubinius 1.9. Poltergeist
|
75
|
+
and PhantomJS are currently supported on Mac OS X, Linux, and Windows
|
76
|
+
platforms.
|
63
77
|
|
64
78
|
Ruby 1.8 is no longer supported. The last release to support Ruby 1.8
|
65
79
|
was 1.0.2, so you should use that if you still need Ruby 1.8 support.
|
66
80
|
|
67
|
-
Poltergeist does not currently support the Windows operating system.
|
68
|
-
|
69
81
|
## Running on a CI ##
|
70
82
|
|
71
83
|
There are no special steps to take. You don't need Xvfb or any running X
|
@@ -92,19 +104,20 @@ and the following optional features:
|
|
92
104
|
* `page.within_window`
|
93
105
|
* `page.status_code`
|
94
106
|
* `page.response_headers`
|
107
|
+
* `page.save_screenshot`
|
95
108
|
* cookie handling
|
96
109
|
* drag-and-drop
|
97
110
|
|
98
111
|
There are some additional features:
|
99
112
|
|
100
|
-
### Taking screenshots ###
|
113
|
+
### Taking screenshots with some extensions ###
|
101
114
|
|
102
115
|
You can grab screenshots of the page at any point by calling
|
103
|
-
`
|
116
|
+
`save_screenshot('/path/to/file.png')` (this works the same way as the PhantomJS
|
104
117
|
render feature, so you can specify other extensions like `.pdf`, `.gif`, etc.)
|
105
118
|
|
106
119
|
By default, only the viewport will be rendered (the part of the page that is in view). To render
|
107
|
-
the entire page, use `
|
120
|
+
the entire page, use `save_screenshot('/path/to/file.png', :full => true)`.
|
108
121
|
|
109
122
|
### Resizing the window ###
|
110
123
|
|
@@ -125,6 +138,18 @@ When this option is enabled, you can insert `page.driver.debug` into
|
|
125
138
|
your tests to pause the test and launch a browser which gives you the
|
126
139
|
WebKit inspector to view your test run with.
|
127
140
|
|
141
|
+
You can register this debugger driver with a different name and set it
|
142
|
+
as the current javascript driver. By example, in your helper file:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
Capybara.register_driver :poltergeist_debug do |app|
|
146
|
+
Capybara::Poltergeist::Driver.new(app, :inspector => true)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Capybara.javascript_driver = :poltergeist
|
150
|
+
Capybara.javascript_driver = :poltergeist_debug
|
151
|
+
```
|
152
|
+
|
128
153
|
[Read more
|
129
154
|
here](http://jonathanleighton.com/articles/2012/poltergeist-0-6-0/)
|
130
155
|
|
@@ -225,7 +250,7 @@ If you experience sporadic crashes a lot, it may be worth configuring
|
|
225
250
|
your CI to automatically re-run failing tests before reporting a failed
|
226
251
|
build.
|
227
252
|
|
228
|
-
###
|
253
|
+
### MouseEventFailed errors ###
|
229
254
|
|
230
255
|
When Poltergeist clicks on an element, rather than generating a DOM
|
231
256
|
click event, it actually generates a "proper" click. This is much closer
|
@@ -238,7 +263,7 @@ your user won't be able to click a covered up element either).
|
|
238
263
|
Sometimes there can be issues with this behavior. If you have problems,
|
239
264
|
it's worth taking screenshots of the page and trying to work out what's
|
240
265
|
going on. If your click is failing, but you're not getting a
|
241
|
-
`
|
266
|
+
`MouseEventFailed` error, then you can turn on the `:debug` option and look
|
242
267
|
in the output to see what co-ordinates Poltergeist is using for the
|
243
268
|
click. You can then cross-reference this with a screenshot to see if
|
244
269
|
something is obviously wrong.
|
@@ -299,18 +324,22 @@ Include as much information as possible. For example:
|
|
299
324
|
|
300
325
|
## Changes ##
|
301
326
|
|
302
|
-
### 1.
|
327
|
+
### 1.2.0 ###
|
303
328
|
|
304
|
-
####
|
305
|
-
|
306
|
-
* Tie to faye-websocket 0.4 as 0.5 introduces incompatibilities.
|
329
|
+
#### Features ####
|
307
330
|
|
308
|
-
|
331
|
+
* Support for Windows hosted Poltergeist (Aaron Tull).
|
332
|
+
* Capybara 2.1 support
|
309
333
|
|
310
|
-
####
|
334
|
+
#### Bug fixes ####
|
311
335
|
|
312
|
-
*
|
313
|
-
|
336
|
+
* Reverted the "native" implementation for filling in form fields,
|
337
|
+
which was introduced in 1.0. This implementation caused various bugs
|
338
|
+
and in general doesn't seem to be worth the trouble at the moment.
|
339
|
+
It can be reconsidered in the future when PhantomJS has upgraded its
|
340
|
+
WebKit version. [Issue #176, #223]
|
341
|
+
* Run phantomjs in a new process group so ^C doesn't trigger a
|
342
|
+
DeadClient error [Issue #252]
|
314
343
|
|
315
344
|
### 1.1.0 ###
|
316
345
|
|
@@ -327,6 +356,10 @@ Include as much information as possible. For example:
|
|
327
356
|
* The `:debug` option now causes the PhantomJS portion of Poltergeist
|
328
357
|
to output some additional debug info, which may be useful in
|
329
358
|
figuring out timeout errors.
|
359
|
+
* Add the ability to extend the phantomjs environment via browser
|
360
|
+
options. e.g.
|
361
|
+
`Capybara::Poltergeist::Driver.new( app, :extensions => ['file.js', 'another.js'])`
|
362
|
+
(Jon Rowe)
|
330
363
|
|
331
364
|
#### Bug fixes ####
|
332
365
|
|
@@ -348,10 +381,6 @@ Include as much information as possible. For example:
|
|
348
381
|
[Issue #192]
|
349
382
|
* Fix `ObsoleteNode` error when using `attach_file` with the `jQuery
|
350
383
|
File Upload` plugin. [Issue #115]
|
351
|
-
* Add the ability to extend the phantomjs environment via browser
|
352
|
-
options. e.g.
|
353
|
-
`Capybara::Poltergeist::Driver.new( app, :extensions => ['file.js', 'another.js'])`
|
354
|
-
(@JonRowe)
|
355
384
|
* Ensure that a `String` is passed over-the-wire to PhantomJS for
|
356
385
|
file input paths, allowing `attach_file` to be called with arbitry
|
357
386
|
objects such as a Pathname. (@mjtko) [Issue #215]
|
data/lib/capybara/poltergeist.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
+
require "capybara/poltergeist/errors"
|
1
2
|
require 'json'
|
2
3
|
require 'time'
|
3
4
|
|
4
5
|
module Capybara::Poltergeist
|
5
6
|
class Browser
|
7
|
+
ERROR_MAPPINGS = {
|
8
|
+
"Poltergeist.JavascriptError" => JavascriptError,
|
9
|
+
"Poltergeist.FrameNotFound" => FrameNotFound
|
10
|
+
}
|
11
|
+
|
6
12
|
attr_reader :server, :client, :logger
|
7
13
|
|
8
14
|
def initialize(server, client, logger = nil)
|
@@ -38,17 +44,25 @@ module Capybara::Poltergeist
|
|
38
44
|
command 'source'
|
39
45
|
end
|
40
46
|
|
41
|
-
def
|
42
|
-
|
47
|
+
def title
|
48
|
+
command 'title'
|
49
|
+
end
|
50
|
+
|
51
|
+
def find(method, selector)
|
52
|
+
result = command('find', method, selector)
|
43
53
|
result['ids'].map { |id| [result['page_id'], id] }
|
44
54
|
end
|
45
55
|
|
46
|
-
def find_within(page_id, id, selector)
|
47
|
-
command 'find_within', page_id, id, selector
|
56
|
+
def find_within(page_id, id, method, selector)
|
57
|
+
command 'find_within', page_id, id, method, selector
|
48
58
|
end
|
49
59
|
|
50
|
-
def
|
51
|
-
command '
|
60
|
+
def all_text(page_id, id)
|
61
|
+
command 'all_text', page_id, id
|
62
|
+
end
|
63
|
+
|
64
|
+
def visible_text(page_id, id)
|
65
|
+
command 'visible_text', page_id, id
|
52
66
|
end
|
53
67
|
|
54
68
|
def attribute(page_id, id, name)
|
@@ -75,6 +89,10 @@ module Capybara::Poltergeist
|
|
75
89
|
command 'visible', page_id, id
|
76
90
|
end
|
77
91
|
|
92
|
+
def disabled?(page_id, id)
|
93
|
+
command 'disabled', page_id, id
|
94
|
+
end
|
95
|
+
|
78
96
|
def click_coordinates(x, y)
|
79
97
|
command 'click_coordinates', x, y
|
80
98
|
end
|
@@ -87,8 +105,13 @@ module Capybara::Poltergeist
|
|
87
105
|
command 'execute', script
|
88
106
|
end
|
89
107
|
|
90
|
-
def within_frame(
|
91
|
-
|
108
|
+
def within_frame(handle, &block)
|
109
|
+
if handle.is_a?(Capybara::Node::Base)
|
110
|
+
command 'push_frame', handle['id']
|
111
|
+
else
|
112
|
+
command 'push_frame', handle
|
113
|
+
end
|
114
|
+
|
92
115
|
yield
|
93
116
|
ensure
|
94
117
|
command 'pop_frame'
|
@@ -109,6 +132,10 @@ module Capybara::Poltergeist
|
|
109
132
|
command 'double_click', page_id, id
|
110
133
|
end
|
111
134
|
|
135
|
+
def hover(page_id, id)
|
136
|
+
command 'hover', page_id, id
|
137
|
+
end
|
138
|
+
|
112
139
|
def drag(page_id, id, other_id)
|
113
140
|
command 'drag', page_id, id, other_id
|
114
141
|
end
|
@@ -193,14 +220,11 @@ module Capybara::Poltergeist
|
|
193
220
|
log json.inspect
|
194
221
|
|
195
222
|
if json['error']
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
end
|
223
|
+
klass = ERROR_MAPPINGS[json['error']['name']] || BrowserError
|
224
|
+
raise klass.new(json['error'])
|
225
|
+
else
|
226
|
+
json['response']
|
201
227
|
end
|
202
|
-
json['response']
|
203
|
-
|
204
228
|
rescue DeadClient
|
205
229
|
restart
|
206
230
|
raise
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "timeout"
|
2
|
+
require "capybara/poltergeist/utility"
|
2
3
|
|
3
4
|
module Capybara::Poltergeist
|
4
5
|
class Client
|
@@ -37,20 +38,23 @@ module Capybara::Poltergeist
|
|
37
38
|
}
|
38
39
|
|
39
40
|
redirect_stdout(write) do
|
40
|
-
@pid = Process.spawn(*command.map(&:to_s))
|
41
|
+
@pid = Process.spawn(*command.map(&:to_s), pgroup: true)
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
45
|
def stop
|
45
46
|
if pid
|
46
47
|
begin
|
47
|
-
|
48
|
-
|
49
|
-
begin
|
50
|
-
Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
|
51
|
-
rescue Timeout::Error
|
48
|
+
if Capybara::Poltergeist.windows?
|
52
49
|
Process.kill('KILL', pid)
|
53
|
-
|
50
|
+
else
|
51
|
+
Process.kill('TERM', pid)
|
52
|
+
begin
|
53
|
+
Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) }
|
54
|
+
rescue Timeout::Error
|
55
|
+
Process.kill('KILL', pid)
|
56
|
+
Process.wait(pid)
|
57
|
+
end
|
54
58
|
end
|
55
59
|
rescue Errno::ESRCH, Errno::ECHILD
|
56
60
|
# Zed's dead, baby
|
@@ -27,14 +27,14 @@ class PoltergeistAgent
|
|
27
27
|
currentUrl: ->
|
28
28
|
window.location.toString()
|
29
29
|
|
30
|
-
find: (selector, within = document) ->
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
find: (method, selector, within = document) ->
|
31
|
+
if method == "xpath"
|
32
|
+
xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
|
33
|
+
results = (xpath.snapshotItem(i) for i in [0...xpath.snapshotLength])
|
34
|
+
else
|
35
|
+
results = within.querySelectorAll(selector)
|
36
36
|
|
37
|
-
|
37
|
+
this.register(el) for el in results
|
38
38
|
|
39
39
|
register: (element) ->
|
40
40
|
@elements.push(element)
|
@@ -73,8 +73,8 @@ class PoltergeistAgent.Node
|
|
73
73
|
parentId: ->
|
74
74
|
@agent.register(@element.parentNode)
|
75
75
|
|
76
|
-
find: (selector) ->
|
77
|
-
@agent.find(selector, @element)
|
76
|
+
find: (method, selector) ->
|
77
|
+
@agent.find(method, selector, @element)
|
78
78
|
|
79
79
|
isObsolete: ->
|
80
80
|
obsolete = (element) =>
|
@@ -92,12 +92,41 @@ class PoltergeistAgent.Node
|
|
92
92
|
event.initEvent('change', true, false)
|
93
93
|
@element.dispatchEvent(event)
|
94
94
|
|
95
|
+
input: ->
|
96
|
+
event = document.createEvent('HTMLEvents')
|
97
|
+
event.initEvent('input', true, false)
|
98
|
+
@element.dispatchEvent(event)
|
99
|
+
|
100
|
+
keyupdowned: (eventName, keyCode) ->
|
101
|
+
event = document.createEvent('UIEvents')
|
102
|
+
event.initEvent(eventName, true, true)
|
103
|
+
event.keyCode = keyCode
|
104
|
+
event.which = keyCode
|
105
|
+
event.charCode = 0
|
106
|
+
@element.dispatchEvent(event)
|
107
|
+
|
108
|
+
keypressed: (altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) ->
|
109
|
+
event = document.createEvent('UIEvents')
|
110
|
+
event.initEvent('keypress', true, true)
|
111
|
+
event.window = @agent.window
|
112
|
+
event.altKey = altKey
|
113
|
+
event.ctrlKey = ctrlKey
|
114
|
+
event.shiftKey = shiftKey
|
115
|
+
event.metaKey = metaKey
|
116
|
+
event.keyCode = keyCode
|
117
|
+
event.charCode = charCode
|
118
|
+
event.which = keyCode
|
119
|
+
@element.dispatchEvent(event)
|
120
|
+
|
95
121
|
insideBody: ->
|
96
122
|
@element == document.body ||
|
97
123
|
document.evaluate('ancestor::body', @element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue
|
98
124
|
|
99
|
-
|
100
|
-
|
125
|
+
allText: ->
|
126
|
+
@element.textContent
|
127
|
+
|
128
|
+
visibleText: ->
|
129
|
+
if @element.nodeName == "TEXTAREA"
|
101
130
|
@element.textContent
|
102
131
|
else
|
103
132
|
@element.innerText
|
@@ -117,6 +146,27 @@ class PoltergeistAgent.Node
|
|
117
146
|
else
|
118
147
|
@element.value
|
119
148
|
|
149
|
+
set: (value) ->
|
150
|
+
return if @element.readOnly
|
151
|
+
|
152
|
+
if (@element.maxLength >= 0)
|
153
|
+
value = value.substr(0, @element.maxLength)
|
154
|
+
|
155
|
+
@element.value = ''
|
156
|
+
this.trigger('focus')
|
157
|
+
|
158
|
+
for char in value
|
159
|
+
keyCode = this.characterToKeyCode(char)
|
160
|
+
this.keyupdowned('keydown', keyCode)
|
161
|
+
@element.value += char
|
162
|
+
|
163
|
+
this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0))
|
164
|
+
this.keyupdowned('keyup', keyCode)
|
165
|
+
|
166
|
+
this.changed()
|
167
|
+
this.input()
|
168
|
+
this.trigger('blur')
|
169
|
+
|
120
170
|
isMultiple: ->
|
121
171
|
@element.multiple
|
122
172
|
|
@@ -147,6 +197,9 @@ class PoltergeistAgent.Node
|
|
147
197
|
else
|
148
198
|
true
|
149
199
|
|
200
|
+
isDisabled: ->
|
201
|
+
@element.disabled || @element.tagName == 'OPTION' && @element.parentNode.disabled
|
202
|
+
|
150
203
|
frameOffset: ->
|
151
204
|
win = window
|
152
205
|
offset = { top: 0, left: 0 }
|
@@ -191,14 +244,7 @@ class PoltergeistAgent.Node
|
|
191
244
|
|
192
245
|
@element.dispatchEvent(event)
|
193
246
|
|
194
|
-
|
195
|
-
@element.focus()
|
196
|
-
@element.select()
|
197
|
-
|
198
|
-
blur: ->
|
199
|
-
@element.blur()
|
200
|
-
|
201
|
-
clickTest: (x, y) ->
|
247
|
+
mouseEventTest: (x, y) ->
|
202
248
|
frameOffset = this.frameOffset()
|
203
249
|
|
204
250
|
x -= frameOffset.left
|
@@ -222,6 +268,45 @@ class PoltergeistAgent.Node
|
|
222
268
|
selector += ".#{className}"
|
223
269
|
selector
|
224
270
|
|
271
|
+
characterToKeyCode: (character) ->
|
272
|
+
code = character.toUpperCase().charCodeAt(0)
|
273
|
+
specialKeys =
|
274
|
+
96: 192 #`
|
275
|
+
45: 189 #-
|
276
|
+
61: 187 #=
|
277
|
+
91: 219 #[
|
278
|
+
93: 221 #]
|
279
|
+
92: 220 #\
|
280
|
+
59: 186 #;
|
281
|
+
39: 222 #'
|
282
|
+
44: 188 #,
|
283
|
+
46: 190 #.
|
284
|
+
47: 191 #/
|
285
|
+
127: 46 #delete
|
286
|
+
126: 192 #~
|
287
|
+
33: 49 #!
|
288
|
+
64: 50 #@
|
289
|
+
35: 51 ##
|
290
|
+
36: 52 #$
|
291
|
+
37: 53 #%
|
292
|
+
94: 54 #^
|
293
|
+
38: 55 #&
|
294
|
+
42: 56 #*
|
295
|
+
40: 57 #(
|
296
|
+
41: 48 #)
|
297
|
+
95: 189 #_
|
298
|
+
43: 187 #+
|
299
|
+
123: 219 #{
|
300
|
+
125: 221 #}
|
301
|
+
124: 220 #|
|
302
|
+
58: 186 #:
|
303
|
+
34: 222 #"
|
304
|
+
60: 188 #<
|
305
|
+
62: 190 #>
|
306
|
+
63: 191 #?
|
307
|
+
|
308
|
+
specialKeys[code] || code
|
309
|
+
|
225
310
|
isDOMEqual: (other_id) ->
|
226
311
|
@element == @agent.get(other_id).element
|
227
312
|
|