poltergeist 1.5.1 → 1.6.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 +5 -13
- data/LICENSE +1 -1
- data/README.md +15 -44
- data/lib/capybara/poltergeist/browser.rb +58 -10
- data/lib/capybara/poltergeist/client.rb +2 -2
- data/lib/capybara/poltergeist/client/agent.coffee +46 -10
- data/lib/capybara/poltergeist/client/browser.coffee +160 -117
- data/lib/capybara/poltergeist/client/compiled/agent.js +60 -10
- data/lib/capybara/poltergeist/client/compiled/browser.js +208 -137
- data/lib/capybara/poltergeist/client/compiled/main.js +40 -2
- data/lib/capybara/poltergeist/client/compiled/node.js +7 -2
- data/lib/capybara/poltergeist/client/compiled/web_page.js +172 -111
- data/lib/capybara/poltergeist/client/main.coffee +10 -1
- data/lib/capybara/poltergeist/client/node.coffee +8 -4
- data/lib/capybara/poltergeist/client/web_page.coffee +139 -94
- data/lib/capybara/poltergeist/driver.rb +36 -2
- data/lib/capybara/poltergeist/errors.rb +8 -2
- data/lib/capybara/poltergeist/inspector.rb +1 -1
- data/lib/capybara/poltergeist/network_traffic/response.rb +1 -1
- data/lib/capybara/poltergeist/node.rb +12 -0
- data/lib/capybara/poltergeist/version.rb +1 -1
- metadata +44 -31
@@ -75,6 +75,14 @@ class Poltergeist.BrowserError extends Poltergeist.Error
|
|
75
75
|
name: "Poltergeist.BrowserError"
|
76
76
|
args: -> [@message, @stack]
|
77
77
|
|
78
|
+
class Poltergeist.StatusFailError extends Poltergeist.Error
|
79
|
+
name: "Poltergeist.StatusFailError"
|
80
|
+
args: -> []
|
81
|
+
|
82
|
+
class Poltergeist.NoSuchWindowError extends Poltergeist.Error
|
83
|
+
name: "Poltergeist.NoSuchWindowError"
|
84
|
+
args: -> []
|
85
|
+
|
78
86
|
# We're using phantom.libraryPath so that any stack traces
|
79
87
|
# report the full path.
|
80
88
|
phantom.injectJs("#{phantom.libraryPath}/web_page.js")
|
@@ -82,4 +90,5 @@ phantom.injectJs("#{phantom.libraryPath}/node.js")
|
|
82
90
|
phantom.injectJs("#{phantom.libraryPath}/connection.js")
|
83
91
|
phantom.injectJs("#{phantom.libraryPath}/browser.js")
|
84
92
|
|
85
|
-
|
93
|
+
system = require 'system'
|
94
|
+
new Poltergeist(system.args[1], system.args[2], system.args[3])
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
class Poltergeist.Node
|
4
4
|
@DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete',
|
5
|
-
'removeAttribute', 'isMultiple', 'select', 'tagName', 'find',
|
6
|
-
'isVisible', 'position', 'trigger', 'parentId', 'mouseEventTest',
|
7
|
-
'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText']
|
5
|
+
'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes',
|
6
|
+
'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest',
|
7
|
+
'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection']
|
8
8
|
|
9
9
|
constructor: (@page, @id) ->
|
10
10
|
|
@@ -35,7 +35,11 @@ class Poltergeist.Node
|
|
35
35
|
test = this.mouseEventTest(pos.x, pos.y)
|
36
36
|
|
37
37
|
if test.status == 'success'
|
38
|
-
|
38
|
+
if name == 'rightclick'
|
39
|
+
@page.mouseEvent('click', pos.x, pos.y, 'right')
|
40
|
+
this.trigger('contextmenu')
|
41
|
+
else
|
42
|
+
@page.mouseEvent(name, pos.x, pos.y)
|
39
43
|
pos
|
40
44
|
else
|
41
45
|
throw new Poltergeist.MouseEventFailed(name, test.selector, pos)
|
@@ -1,22 +1,30 @@
|
|
1
1
|
class Poltergeist.WebPage
|
2
|
-
@CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished',
|
3
|
-
'
|
4
|
-
'
|
2
|
+
@CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished',
|
3
|
+
'onInitialized', 'onLoadStarted', 'onResourceRequested',
|
4
|
+
'onResourceReceived', 'onError', 'onNavigationRequested',
|
5
|
+
'onUrlChanged', 'onPageCreated', 'onClosing']
|
5
6
|
|
6
|
-
@DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render',
|
7
|
+
@DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render',
|
8
|
+
'renderBase64', 'goBack', 'goForward']
|
7
9
|
|
8
|
-
@COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize',
|
10
|
+
@COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize',
|
11
|
+
'beforeUpload', 'afterUpload', 'clearLocalStorage']
|
9
12
|
|
10
13
|
@EXTENSIONS = []
|
11
14
|
|
12
|
-
constructor: (@
|
13
|
-
@
|
15
|
+
constructor: (@_native) ->
|
16
|
+
@_native or= require('webpage').create()
|
14
17
|
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
18
|
+
@id = 0
|
19
|
+
@source = null
|
20
|
+
@closed = false
|
21
|
+
@state = 'default'
|
22
|
+
@urlBlacklist = []
|
19
23
|
@frames = []
|
24
|
+
@errors = []
|
25
|
+
@_networkTraffic = {}
|
26
|
+
@_tempHeaders = {}
|
27
|
+
@_blockedUrls = []
|
20
28
|
|
21
29
|
for callback in WebPage.CALLBACKS
|
22
30
|
this.bindCallback(callback)
|
@@ -29,37 +37,33 @@ class Poltergeist.WebPage
|
|
29
37
|
for delegate in @DELEGATES
|
30
38
|
do (delegate) =>
|
31
39
|
this.prototype[delegate] =
|
32
|
-
-> @
|
40
|
+
-> @_native[delegate].apply(@_native, arguments)
|
33
41
|
|
34
42
|
onInitializedNative: ->
|
35
|
-
@
|
43
|
+
@id += 1
|
44
|
+
@source = null
|
36
45
|
@injectAgent()
|
37
46
|
this.removeTempHeaders()
|
38
47
|
this.setScrollPosition(left: 0, top: 0)
|
39
48
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
for extension in WebPage.EXTENSIONS
|
44
|
-
@native.injectJs extension
|
45
|
-
|
46
|
-
injectExtension: (file) ->
|
47
|
-
WebPage.EXTENSIONS.push file
|
48
|
-
@native.injectJs file
|
49
|
+
onClosingNative: ->
|
50
|
+
@handle = null
|
51
|
+
@closed = true
|
49
52
|
|
50
53
|
onConsoleMessageNative: (message) ->
|
51
54
|
if message == '__DOMContentLoaded'
|
52
|
-
@
|
55
|
+
@source = @_native.content
|
53
56
|
false
|
57
|
+
else
|
58
|
+
console.log(message)
|
54
59
|
|
55
60
|
onLoadStartedNative: ->
|
61
|
+
@state = 'loading'
|
56
62
|
@requestId = @lastRequestId
|
57
63
|
|
58
|
-
onLoadFinishedNative: ->
|
59
|
-
@
|
60
|
-
|
61
|
-
onConsoleMessage: (message) ->
|
62
|
-
console.log(message)
|
64
|
+
onLoadFinishedNative: (@status) ->
|
65
|
+
@state = 'default'
|
66
|
+
@source or= @_native.content
|
63
67
|
|
64
68
|
onErrorNative: (message, stack) ->
|
65
69
|
stackString = message
|
@@ -69,19 +73,26 @@ class Poltergeist.WebPage
|
|
69
73
|
stackString += " at #{frame.file}:#{frame.line}"
|
70
74
|
stackString += " in #{frame.function}" if frame.function && frame.function != ''
|
71
75
|
|
72
|
-
@
|
76
|
+
@errors.push(message: message, stack: stackString)
|
73
77
|
|
74
|
-
onResourceRequestedNative: (request) ->
|
75
|
-
|
78
|
+
onResourceRequestedNative: (request, net) ->
|
79
|
+
abort = @urlBlacklist.some (blacklisted_url) ->
|
80
|
+
request.url.indexOf(blacklisted_url) != -1
|
81
|
+
|
82
|
+
if abort
|
83
|
+
@_blockedUrls.push request.url unless request.url in @_blockedUrls
|
84
|
+
net.abort()
|
85
|
+
else
|
86
|
+
@lastRequestId = request.id
|
76
87
|
|
77
|
-
|
78
|
-
|
79
|
-
|
88
|
+
if request.url == @redirectURL
|
89
|
+
@redirectURL = null
|
90
|
+
@requestId = request.id
|
80
91
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
92
|
+
@_networkTraffic[request.id] = {
|
93
|
+
request: request,
|
94
|
+
responseParts: []
|
95
|
+
}
|
85
96
|
|
86
97
|
onResourceReceivedNative: (response) ->
|
87
98
|
@_networkTraffic[response.id]?.responseParts.push(response)
|
@@ -90,12 +101,40 @@ class Poltergeist.WebPage
|
|
90
101
|
if response.redirectURL
|
91
102
|
@redirectURL = response.redirectURL
|
92
103
|
else
|
93
|
-
@
|
104
|
+
@statusCode = response.status
|
94
105
|
@_responseHeaders = response.headers
|
95
106
|
|
107
|
+
injectAgent: ->
|
108
|
+
if this.native().evaluate(-> typeof __poltergeist) == "undefined"
|
109
|
+
this.native().injectJs "#{phantom.libraryPath}/agent.js"
|
110
|
+
for extension in WebPage.EXTENSIONS
|
111
|
+
this.native().injectJs extension
|
112
|
+
|
113
|
+
injectExtension: (file) ->
|
114
|
+
WebPage.EXTENSIONS.push file
|
115
|
+
this.native().injectJs file
|
116
|
+
|
117
|
+
native: ->
|
118
|
+
if @closed
|
119
|
+
throw new Poltergeist.NoSuchWindowError
|
120
|
+
else
|
121
|
+
@_native
|
122
|
+
|
123
|
+
windowName: ->
|
124
|
+
this.native().windowName
|
125
|
+
|
126
|
+
keyCode: (name) ->
|
127
|
+
this.native().event.key[name]
|
128
|
+
|
129
|
+
waitState: (state, callback) ->
|
130
|
+
if @state == state
|
131
|
+
callback.call()
|
132
|
+
else
|
133
|
+
setTimeout (=> @waitState(state, callback)), 100
|
134
|
+
|
96
135
|
setHttpAuth: (user, password) ->
|
97
|
-
|
98
|
-
|
136
|
+
this.native().settings.userName = user
|
137
|
+
this.native().settings.password = password
|
99
138
|
|
100
139
|
networkTraffic: ->
|
101
140
|
@_networkTraffic
|
@@ -103,23 +142,25 @@ class Poltergeist.WebPage
|
|
103
142
|
clearNetworkTraffic: ->
|
104
143
|
@_networkTraffic = {}
|
105
144
|
|
106
|
-
|
107
|
-
@
|
145
|
+
blockedUrls: ->
|
146
|
+
@_blockedUrls
|
108
147
|
|
109
|
-
|
110
|
-
@
|
148
|
+
clearBlockedUrls: ->
|
149
|
+
@_blockedUrls = []
|
150
|
+
|
151
|
+
content: ->
|
152
|
+
this.native().frameContent
|
111
153
|
|
112
154
|
title: ->
|
113
|
-
|
155
|
+
this.native().frameTitle
|
114
156
|
|
115
|
-
|
116
|
-
|
157
|
+
frameUrl: (frameName) ->
|
158
|
+
query = (frameName) ->
|
159
|
+
document.querySelector("iframe[name='#{frameName}']")?.src
|
160
|
+
this.evaluate(query, frameName)
|
117
161
|
|
118
162
|
clearErrors: ->
|
119
|
-
@
|
120
|
-
|
121
|
-
statusCode: ->
|
122
|
-
@_statusCode
|
163
|
+
@errors = []
|
123
164
|
|
124
165
|
responseHeaders: ->
|
125
166
|
headers = {}
|
@@ -128,74 +169,70 @@ class Poltergeist.WebPage
|
|
128
169
|
headers
|
129
170
|
|
130
171
|
cookies: ->
|
131
|
-
|
172
|
+
this.native().cookies
|
132
173
|
|
133
174
|
deleteCookie: (name) ->
|
134
|
-
|
175
|
+
this.native().deleteCookie(name)
|
135
176
|
|
136
177
|
viewportSize: ->
|
137
|
-
|
178
|
+
this.native().viewportSize
|
138
179
|
|
139
180
|
setViewportSize: (size) ->
|
140
|
-
|
181
|
+
this.native().viewportSize = size
|
182
|
+
|
183
|
+
setZoomFactor: (zoom_factor) ->
|
184
|
+
this.native().zoomFactor = zoom_factor
|
141
185
|
|
142
186
|
setPaperSize: (size) ->
|
143
|
-
|
187
|
+
this.native().paperSize = size
|
144
188
|
|
145
189
|
scrollPosition: ->
|
146
|
-
|
190
|
+
this.native().scrollPosition
|
147
191
|
|
148
192
|
setScrollPosition: (pos) ->
|
149
|
-
|
193
|
+
this.native().scrollPosition = pos
|
150
194
|
|
151
195
|
clipRect: ->
|
152
|
-
|
196
|
+
this.native().clipRect
|
153
197
|
|
154
198
|
setClipRect: (rect) ->
|
155
|
-
|
199
|
+
this.native().clipRect = rect
|
156
200
|
|
157
201
|
elementBounds: (selector) ->
|
158
|
-
|
202
|
+
this.native().evaluate(
|
159
203
|
(selector) -> document.querySelector(selector).getBoundingClientRect(),
|
160
204
|
selector
|
161
205
|
)
|
162
206
|
|
163
207
|
setUserAgent: (userAgent) ->
|
164
|
-
|
208
|
+
this.native().settings.userAgent = userAgent
|
165
209
|
|
166
210
|
getCustomHeaders: ->
|
167
|
-
|
211
|
+
this.native().customHeaders
|
168
212
|
|
169
213
|
setCustomHeaders: (headers) ->
|
170
|
-
|
214
|
+
this.native().customHeaders = headers
|
171
215
|
|
172
216
|
addTempHeader: (header) ->
|
173
217
|
for name, value of header
|
174
|
-
@
|
218
|
+
@_tempHeaders[name] = value
|
175
219
|
|
176
220
|
removeTempHeaders: ->
|
177
221
|
allHeaders = this.getCustomHeaders()
|
178
|
-
for name, value of @
|
222
|
+
for name, value of @_tempHeaders
|
179
223
|
delete allHeaders[name]
|
180
224
|
this.setCustomHeaders(allHeaders)
|
181
225
|
|
182
226
|
pushFrame: (name) ->
|
183
|
-
if
|
227
|
+
if this.native().switchToFrame(name)
|
184
228
|
@frames.push(name)
|
185
229
|
true
|
186
230
|
else
|
187
231
|
false
|
188
232
|
|
189
|
-
pages: ->
|
190
|
-
@native.pagesWindowName
|
191
|
-
|
192
233
|
popFrame: ->
|
193
234
|
@frames.pop()
|
194
|
-
|
195
|
-
|
196
|
-
getPage: (name) ->
|
197
|
-
page = @native.getPage(name)
|
198
|
-
new Poltergeist.WebPage(page) if page
|
235
|
+
this.native().switchToParentFrame()
|
199
236
|
|
200
237
|
dimensions: ->
|
201
238
|
scroll = this.scrollPosition()
|
@@ -228,16 +265,23 @@ class Poltergeist.WebPage
|
|
228
265
|
|
229
266
|
# Before each mouse event we make sure that the mouse is moved to where the
|
230
267
|
# event will take place. This deals with e.g. :hover changes.
|
231
|
-
mouseEvent: (name, x, y) ->
|
268
|
+
mouseEvent: (name, x, y, button = 'left') ->
|
232
269
|
this.sendEvent('mousemove', x, y)
|
233
|
-
this.sendEvent(name, x, y)
|
270
|
+
this.sendEvent(name, x, y, button)
|
234
271
|
|
235
272
|
evaluate: (fn, args...) ->
|
236
273
|
this.injectAgent()
|
237
|
-
JSON.parse
|
274
|
+
JSON.parse this.sanitize(this.native().evaluate("function() { return PoltergeistAgent.stringify(#{this.stringifyCall(fn, args)}) }"))
|
275
|
+
|
276
|
+
sanitize: (potential_string) ->
|
277
|
+
if typeof(potential_string) == "string"
|
278
|
+
# JSON doesn't like \r or \n in strings unless escaped
|
279
|
+
potential_string.replace("\n","\\n").replace("\r","\\r")
|
280
|
+
else
|
281
|
+
potential_string
|
238
282
|
|
239
283
|
execute: (fn, args...) ->
|
240
|
-
|
284
|
+
this.native().evaluate("function() { #{this.stringifyCall(fn, args)} }")
|
241
285
|
|
242
286
|
stringifyCall: (fn, args) ->
|
243
287
|
if args.length == 0
|
@@ -251,7 +295,7 @@ class Poltergeist.WebPage
|
|
251
295
|
# hence the 'that' closure.
|
252
296
|
bindCallback: (name) ->
|
253
297
|
that = this
|
254
|
-
|
298
|
+
this.native()[name] = ->
|
255
299
|
if that[name + 'Native']? # For internal callbacks
|
256
300
|
result = that[name + 'Native'].apply(that, arguments)
|
257
301
|
|
@@ -267,20 +311,21 @@ class Poltergeist.WebPage
|
|
267
311
|
name, args
|
268
312
|
)
|
269
313
|
|
270
|
-
if result
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
314
|
+
if result != null
|
315
|
+
if result.error?
|
316
|
+
switch result.error.message
|
317
|
+
when 'PoltergeistAgent.ObsoleteNode'
|
318
|
+
throw new Poltergeist.ObsoleteNode
|
319
|
+
when 'PoltergeistAgent.InvalidSelector'
|
320
|
+
[method, selector] = args
|
321
|
+
throw new Poltergeist.InvalidSelector(method, selector)
|
322
|
+
else
|
323
|
+
throw new Poltergeist.BrowserError(result.error.message, result.error.stack)
|
324
|
+
else
|
325
|
+
result.value
|
281
326
|
|
282
327
|
canGoBack: ->
|
283
|
-
|
328
|
+
this.native().canGoBack
|
284
329
|
|
285
330
|
canGoForward: ->
|
286
|
-
|
331
|
+
this.native().canGoForward
|
@@ -49,6 +49,12 @@ module Capybara::Poltergeist
|
|
49
49
|
|
50
50
|
def phantomjs_options
|
51
51
|
list = options[:phantomjs_options] || []
|
52
|
+
|
53
|
+
# PhantomJS defaults to only using SSLv3, which since POODLE (Oct 2014)
|
54
|
+
# many sites have dropped from their supported protocols (eg PayPal,
|
55
|
+
# Braintree).
|
56
|
+
list += ["--ssl-protocol=any"] unless list.grep(/ssl-protocol/).any?
|
57
|
+
|
52
58
|
list += ["--remote-debugger-port=#{inspector.port}", "--remote-debugger-autorun=yes"] if inspector
|
53
59
|
list
|
54
60
|
end
|
@@ -139,14 +145,34 @@ module Capybara::Poltergeist
|
|
139
145
|
browser.within_frame(name, &block)
|
140
146
|
end
|
141
147
|
|
142
|
-
def
|
143
|
-
browser.
|
148
|
+
def current_window_handle
|
149
|
+
browser.window_handle
|
144
150
|
end
|
145
151
|
|
146
152
|
def window_handles
|
147
153
|
browser.window_handles
|
148
154
|
end
|
149
155
|
|
156
|
+
def close_window(handle)
|
157
|
+
browser.close_window(handle)
|
158
|
+
end
|
159
|
+
|
160
|
+
def open_new_window
|
161
|
+
browser.open_new_window
|
162
|
+
end
|
163
|
+
|
164
|
+
def switch_to_window(handle)
|
165
|
+
browser.switch_to_window(handle)
|
166
|
+
end
|
167
|
+
|
168
|
+
def within_window(name, &block)
|
169
|
+
browser.within_window(name, &block)
|
170
|
+
end
|
171
|
+
|
172
|
+
def no_such_window_error
|
173
|
+
NoSuchWindowError
|
174
|
+
end
|
175
|
+
|
150
176
|
def reset!
|
151
177
|
browser.reset
|
152
178
|
@started = false
|
@@ -165,6 +191,10 @@ module Capybara::Poltergeist
|
|
165
191
|
browser.set_paper_size(size)
|
166
192
|
end
|
167
193
|
|
194
|
+
def zoom_factor=(zoom_factor)
|
195
|
+
browser.set_zoom_factor(zoom_factor)
|
196
|
+
end
|
197
|
+
|
168
198
|
def resize(width, height)
|
169
199
|
browser.resize(width, height)
|
170
200
|
end
|
@@ -225,6 +255,10 @@ module Capybara::Poltergeist
|
|
225
255
|
browser.remove_cookie(name)
|
226
256
|
end
|
227
257
|
|
258
|
+
def clear_cookies
|
259
|
+
browser.clear_cookies
|
260
|
+
end
|
261
|
+
|
228
262
|
def cookies_enabled=(flag)
|
229
263
|
browser.cookies_enabled = flag
|
230
264
|
end
|