poltergeist 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|