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.
@@ -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
- new Poltergeist(phantom.args[0], phantom.args[1], phantom.args[2])
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
- @page.mouseEvent(name, pos.x, pos.y)
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', 'onInitialized',
3
- 'onLoadStarted', 'onResourceRequested', 'onResourceReceived',
4
- 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated']
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', 'renderBase64', 'goBack', 'goForward']
7
+ @DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render',
8
+ 'renderBase64', 'goBack', 'goForward']
7
9
 
8
- @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload']
10
+ @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize',
11
+ 'beforeUpload', 'afterUpload', 'clearLocalStorage']
9
12
 
10
13
  @EXTENSIONS = []
11
14
 
12
- constructor: (@native) ->
13
- @native or= require('webpage').create()
15
+ constructor: (@_native) ->
16
+ @_native or= require('webpage').create()
14
17
 
15
- @_source = null
16
- @_errors = []
17
- @_networkTraffic = {}
18
- @_temp_headers = {}
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
- -> @native[delegate].apply(@native, arguments)
40
+ -> @_native[delegate].apply(@_native, arguments)
33
41
 
34
42
  onInitializedNative: ->
35
- @_source = null
43
+ @id += 1
44
+ @source = null
36
45
  @injectAgent()
37
46
  this.removeTempHeaders()
38
47
  this.setScrollPosition(left: 0, top: 0)
39
48
 
40
- injectAgent: ->
41
- if @native.evaluate(-> typeof __poltergeist) == "undefined"
42
- @native.injectJs "#{phantom.libraryPath}/agent.js"
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
- @_source = @native.content
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
- @_source or= @native.content
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
- @_errors.push(message: message, stack: stackString)
76
+ @errors.push(message: message, stack: stackString)
73
77
 
74
- onResourceRequestedNative: (request) ->
75
- @lastRequestId = request.id
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
- if request.url == @redirectURL
78
- @redirectURL = null
79
- @requestId = request.id
88
+ if request.url == @redirectURL
89
+ @redirectURL = null
90
+ @requestId = request.id
80
91
 
81
- @_networkTraffic[request.id] = {
82
- request: request,
83
- responseParts: []
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
- @_statusCode = response.status
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
- @native.settings.userName = user
98
- @native.settings.password = password
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
- content: ->
107
- @native.frameContent
145
+ blockedUrls: ->
146
+ @_blockedUrls
108
147
 
109
- source: ->
110
- @_source
148
+ clearBlockedUrls: ->
149
+ @_blockedUrls = []
150
+
151
+ content: ->
152
+ this.native().frameContent
111
153
 
112
154
  title: ->
113
- @native.frameTitle
155
+ this.native().frameTitle
114
156
 
115
- errors: ->
116
- @_errors
157
+ frameUrl: (frameName) ->
158
+ query = (frameName) ->
159
+ document.querySelector("iframe[name='#{frameName}']")?.src
160
+ this.evaluate(query, frameName)
117
161
 
118
162
  clearErrors: ->
119
- @_errors = []
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
- @native.cookies
172
+ this.native().cookies
132
173
 
133
174
  deleteCookie: (name) ->
134
- @native.deleteCookie(name)
175
+ this.native().deleteCookie(name)
135
176
 
136
177
  viewportSize: ->
137
- @native.viewportSize
178
+ this.native().viewportSize
138
179
 
139
180
  setViewportSize: (size) ->
140
- @native.viewportSize = size
181
+ this.native().viewportSize = size
182
+
183
+ setZoomFactor: (zoom_factor) ->
184
+ this.native().zoomFactor = zoom_factor
141
185
 
142
186
  setPaperSize: (size) ->
143
- @native.paperSize = size
187
+ this.native().paperSize = size
144
188
 
145
189
  scrollPosition: ->
146
- @native.scrollPosition
190
+ this.native().scrollPosition
147
191
 
148
192
  setScrollPosition: (pos) ->
149
- @native.scrollPosition = pos
193
+ this.native().scrollPosition = pos
150
194
 
151
195
  clipRect: ->
152
- @native.clipRect
196
+ this.native().clipRect
153
197
 
154
198
  setClipRect: (rect) ->
155
- @native.clipRect = rect
199
+ this.native().clipRect = rect
156
200
 
157
201
  elementBounds: (selector) ->
158
- @native.evaluate(
202
+ this.native().evaluate(
159
203
  (selector) -> document.querySelector(selector).getBoundingClientRect(),
160
204
  selector
161
205
  )
162
206
 
163
207
  setUserAgent: (userAgent) ->
164
- @native.settings.userAgent = userAgent
208
+ this.native().settings.userAgent = userAgent
165
209
 
166
210
  getCustomHeaders: ->
167
- @native.customHeaders
211
+ this.native().customHeaders
168
212
 
169
213
  setCustomHeaders: (headers) ->
170
- @native.customHeaders = headers
214
+ this.native().customHeaders = headers
171
215
 
172
216
  addTempHeader: (header) ->
173
217
  for name, value of header
174
- @_temp_headers[name] = value
218
+ @_tempHeaders[name] = value
175
219
 
176
220
  removeTempHeaders: ->
177
221
  allHeaders = this.getCustomHeaders()
178
- for name, value of @_temp_headers
222
+ for name, value of @_tempHeaders
179
223
  delete allHeaders[name]
180
224
  this.setCustomHeaders(allHeaders)
181
225
 
182
226
  pushFrame: (name) ->
183
- if @native.switchToFrame(name)
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
- @native.switchToParentFrame()
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 @native.evaluate("function() { return PoltergeistAgent.stringify(#{this.stringifyCall(fn, args)}) }")
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
- @native.evaluate("function() { #{this.stringifyCall(fn, args)} }")
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
- @native[name] = ->
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.error?
271
- switch result.error.message
272
- when 'PoltergeistAgent.ObsoleteNode'
273
- throw new Poltergeist.ObsoleteNode
274
- when 'PoltergeistAgent.InvalidSelector'
275
- [method, selector] = args
276
- throw new Poltergeist.InvalidSelector(method, selector)
277
- else
278
- throw new Poltergeist.BrowserError(result.error.message, result.error.stack)
279
- else
280
- result.value
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
- @native.canGoBack
328
+ this.native().canGoBack
284
329
 
285
330
  canGoForward: ->
286
- @native.canGoForward
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 within_window(name, &block)
143
- browser.within_window(name, &block)
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