poltergeist 1.5.1 → 1.6.0

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