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
@@ -2,67 +2,46 @@ class Poltergeist.Browser
|
|
2
2
|
constructor: (@owner, width, height) ->
|
3
3
|
@width = width || 1024
|
4
4
|
@height = height || 768
|
5
|
-
@
|
6
|
-
@page_stack = []
|
7
|
-
@page_id = 0
|
5
|
+
@pages = []
|
8
6
|
@js_errors = true
|
9
7
|
@_debug = false
|
8
|
+
@_counter = 0
|
10
9
|
|
11
10
|
this.resetPage()
|
12
11
|
|
13
12
|
resetPage: ->
|
13
|
+
[@_counter, @pages] = [0, []]
|
14
|
+
|
14
15
|
if @page?
|
15
|
-
@page.
|
16
|
+
unless @page.closed
|
17
|
+
@page.clearLocalStorage() if @page.currentUrl() != 'about:blank'
|
18
|
+
@page.release()
|
16
19
|
phantom.clearCookies()
|
17
20
|
|
18
|
-
@page = new Poltergeist.WebPage
|
21
|
+
@page = @currentPage = new Poltergeist.WebPage
|
19
22
|
@page.setViewportSize(width: @width, height: @height)
|
23
|
+
@page.handle = "#{@_counter++}"
|
24
|
+
@pages.push(@page)
|
20
25
|
|
21
|
-
@page.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
this.setState 'loading' if @state == 'mouse_event' && navigation == 'FormSubmitted'
|
26
|
-
|
27
|
-
@page.onLoadFinished = (status) =>
|
28
|
-
if @state == 'loading'
|
29
|
-
this.sendResponse(status: status, position: @last_mouse_event)
|
30
|
-
this.setState 'default'
|
31
|
-
else if @state == 'awaiting_frame_load'
|
32
|
-
this.sendResponse(true)
|
33
|
-
this.setState 'default'
|
34
|
-
|
35
|
-
@page.onInitialized = =>
|
36
|
-
@page_id += 1
|
26
|
+
@page.onPageCreated = (newPage) =>
|
27
|
+
page = new Poltergeist.WebPage(newPage)
|
28
|
+
page.handle = "#{@_counter++}"
|
29
|
+
@pages.push(page)
|
37
30
|
|
38
|
-
|
39
|
-
|
40
|
-
name = @page_name
|
41
|
-
@page_name = null
|
42
|
-
|
43
|
-
this.setState 'default'
|
44
|
-
|
45
|
-
# At this point subpage isn't fully initialized, so we can't check
|
46
|
-
# its name. Instead, we just schedule another attempt to push the
|
47
|
-
# window.
|
48
|
-
setTimeout((=> this.push_window(name)), 0)
|
31
|
+
getPageByHandle: (handle) ->
|
32
|
+
@pages.filter((p) -> !p.closed && p.handle == handle)[0]
|
49
33
|
|
50
34
|
runCommand: (name, args) ->
|
51
|
-
|
35
|
+
@currentPage.state = 'default'
|
52
36
|
this[name].apply(this, args)
|
53
37
|
|
54
38
|
debug: (message) ->
|
55
39
|
if @_debug
|
56
40
|
console.log "poltergeist [#{new Date().getTime()}] #{message}"
|
57
41
|
|
58
|
-
setState: (state) ->
|
59
|
-
return if @state == state
|
60
|
-
this.debug "state #{@state} -> #{state}"
|
61
|
-
@state = state
|
62
|
-
|
63
42
|
sendResponse: (response) ->
|
64
|
-
errors = @
|
65
|
-
@
|
43
|
+
errors = @currentPage.errors
|
44
|
+
@currentPage.clearErrors()
|
66
45
|
|
67
46
|
if errors.length > 0 && @js_errors
|
68
47
|
@owner.sendError(new Poltergeist.JavascriptError(errors))
|
@@ -70,47 +49,53 @@ class Poltergeist.Browser
|
|
70
49
|
@owner.sendResponse(response)
|
71
50
|
|
72
51
|
add_extension: (extension) ->
|
73
|
-
@
|
52
|
+
@currentPage.injectExtension extension
|
74
53
|
this.sendResponse 'success'
|
75
54
|
|
76
55
|
node: (page_id, id) ->
|
77
|
-
if
|
78
|
-
@
|
56
|
+
if @currentPage.id == page_id
|
57
|
+
@currentPage.get(id)
|
79
58
|
else
|
80
59
|
throw new Poltergeist.ObsoleteNode
|
81
60
|
|
82
61
|
visit: (url) ->
|
83
|
-
|
62
|
+
@currentPage.state = 'loading'
|
84
63
|
|
85
64
|
# Prevent firing `page.onInitialized` event twice. Calling currentUrl
|
86
65
|
# method before page is actually opened fires this event for the first time.
|
87
66
|
# The second time will be in the right place after `page.open`
|
88
|
-
|
67
|
+
prevUrl = if @currentPage.source is null then 'about:blank' else @currentPage.currentUrl()
|
89
68
|
|
90
|
-
@
|
69
|
+
@currentPage.open(url)
|
91
70
|
|
92
|
-
if /#/.test(url) &&
|
93
|
-
#
|
94
|
-
|
95
|
-
this.sendResponse 'success'
|
71
|
+
if /#/.test(url) && prevUrl.split('#')[0] == url.split('#')[0]
|
72
|
+
# Hash change occurred, so there will be no onLoadFinished
|
73
|
+
@currentPage.state = 'default'
|
74
|
+
this.sendResponse(status: 'success')
|
75
|
+
else
|
76
|
+
@currentPage.waitState 'default', =>
|
77
|
+
if @currentPage.statusCode == null && @currentPage.status == 'fail'
|
78
|
+
@owner.sendError(new Poltergeist.StatusFailError)
|
79
|
+
else
|
80
|
+
this.sendResponse(status: @currentPage.status)
|
96
81
|
|
97
82
|
current_url: ->
|
98
|
-
this.sendResponse @
|
83
|
+
this.sendResponse @currentPage.currentUrl()
|
99
84
|
|
100
85
|
status_code: ->
|
101
|
-
this.sendResponse @
|
86
|
+
this.sendResponse @currentPage.statusCode
|
102
87
|
|
103
88
|
body: ->
|
104
|
-
this.sendResponse @
|
89
|
+
this.sendResponse @currentPage.content()
|
105
90
|
|
106
91
|
source: ->
|
107
|
-
this.sendResponse @
|
92
|
+
this.sendResponse @currentPage.source
|
108
93
|
|
109
94
|
title: ->
|
110
|
-
this.sendResponse @
|
95
|
+
this.sendResponse @currentPage.title()
|
111
96
|
|
112
97
|
find: (method, selector) ->
|
113
|
-
this.sendResponse(page_id: @
|
98
|
+
this.sendResponse(page_id: @currentPage.id, ids: @currentPage.find(method, selector))
|
114
99
|
|
115
100
|
find_within: (page_id, id, method, selector) ->
|
116
101
|
this.sendResponse this.node(page_id, id).find(method, selector)
|
@@ -127,6 +112,12 @@ class Poltergeist.Browser
|
|
127
112
|
attribute: (page_id, id, name) ->
|
128
113
|
this.sendResponse this.node(page_id, id).getAttribute(name)
|
129
114
|
|
115
|
+
attributes: (page_id, id, name) ->
|
116
|
+
this.sendResponse this.node(page_id, id).getAttributes()
|
117
|
+
|
118
|
+
parents: (page_id, id) ->
|
119
|
+
this.sendResponse this.node(page_id, id).parentIds()
|
120
|
+
|
130
121
|
value: (page_id, id) ->
|
131
122
|
this.sendResponse this.node(page_id, id).value()
|
132
123
|
|
@@ -140,9 +131,9 @@ class Poltergeist.Browser
|
|
140
131
|
select_file: (page_id, id, value) ->
|
141
132
|
node = this.node(page_id, id)
|
142
133
|
|
143
|
-
@
|
144
|
-
@
|
145
|
-
@
|
134
|
+
@currentPage.beforeUpload(node.id)
|
135
|
+
@currentPage.uploadFile('[_poltergeist_selected]', value)
|
136
|
+
@currentPage.afterUpload(node.id)
|
146
137
|
|
147
138
|
this.sendResponse(true)
|
148
139
|
|
@@ -159,16 +150,23 @@ class Poltergeist.Browser
|
|
159
150
|
this.sendResponse this.node(page_id, id).isDisabled()
|
160
151
|
|
161
152
|
evaluate: (script) ->
|
162
|
-
this.sendResponse @
|
153
|
+
this.sendResponse @currentPage.evaluate("function() { return #{script} }")
|
163
154
|
|
164
155
|
execute: (script) ->
|
165
|
-
@
|
156
|
+
@currentPage.execute("function() { #{script} }")
|
166
157
|
this.sendResponse(true)
|
167
158
|
|
159
|
+
frameUrl: (frame_name) ->
|
160
|
+
@currentPage.frameUrl(frame_name)
|
161
|
+
|
168
162
|
push_frame: (name, timeout = new Date().getTime() + 2000) ->
|
169
|
-
if @
|
170
|
-
|
171
|
-
|
163
|
+
if @frameUrl(name) in @currentPage.blockedUrls()
|
164
|
+
this.sendResponse(true)
|
165
|
+
else if @currentPage.pushFrame(name)
|
166
|
+
if @currentPage.currentUrl() == 'about:blank'
|
167
|
+
@currentPage.state = 'awaiting_frame_load'
|
168
|
+
@currentPage.waitState 'default', =>
|
169
|
+
this.sendResponse(true)
|
172
170
|
else
|
173
171
|
this.sendResponse(true)
|
174
172
|
else
|
@@ -177,53 +175,72 @@ class Poltergeist.Browser
|
|
177
175
|
else
|
178
176
|
@owner.sendError(new Poltergeist.FrameNotFound(name))
|
179
177
|
|
180
|
-
pages: ->
|
181
|
-
this.sendResponse(@page.pages())
|
182
|
-
|
183
178
|
pop_frame: ->
|
184
|
-
this.sendResponse(@
|
179
|
+
this.sendResponse(@currentPage.popFrame())
|
185
180
|
|
186
|
-
|
187
|
-
|
181
|
+
window_handles: ->
|
182
|
+
handles = @pages.filter((p) -> !p.closed).map((p) -> p.handle)
|
183
|
+
this.sendResponse(handles)
|
188
184
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
185
|
+
window_handle: (name = null) ->
|
186
|
+
handle = if name
|
187
|
+
page = @pages.filter((p) -> !p.closed && p.windowName() == name)[0]
|
188
|
+
if page then page.handle else null
|
189
|
+
else
|
190
|
+
@currentPage.handle
|
191
|
+
|
192
|
+
this.sendResponse(handle)
|
193
|
+
|
194
|
+
switch_to_window: (handle) ->
|
195
|
+
page = @getPageByHandle(handle)
|
196
|
+
if page
|
197
|
+
if page != @currentPage
|
198
|
+
page.waitState 'default', =>
|
199
|
+
@currentPage = page
|
200
|
+
this.sendResponse(true)
|
194
201
|
else
|
195
|
-
@page_stack.push(@page)
|
196
|
-
@page = sub_page
|
197
|
-
@page_id += 1
|
198
202
|
this.sendResponse(true)
|
199
203
|
else
|
200
|
-
|
201
|
-
this.setState 'awaiting_sub_page'
|
204
|
+
throw new Poltergeist.NoSuchWindowError
|
202
205
|
|
203
|
-
|
204
|
-
|
205
|
-
@page = prev_page if prev_page
|
206
|
+
open_new_window: ->
|
207
|
+
this.execute 'window.open()'
|
206
208
|
this.sendResponse(true)
|
207
209
|
|
210
|
+
close_window: (handle) ->
|
211
|
+
page = @getPageByHandle(handle)
|
212
|
+
if page
|
213
|
+
page.release()
|
214
|
+
this.sendResponse(true)
|
215
|
+
else
|
216
|
+
this.sendResponse(false)
|
217
|
+
|
208
218
|
mouse_event: (page_id, id, name) ->
|
209
219
|
# Get the node before changing state, in case there is an exception
|
210
220
|
node = this.node(page_id, id)
|
211
221
|
|
212
222
|
# If the event triggers onNavigationRequested, we will transition to the 'loading'
|
213
223
|
# state and wait for onLoadFinished before sending a response.
|
214
|
-
|
224
|
+
@currentPage.state = 'mouse_event'
|
215
225
|
|
216
226
|
@last_mouse_event = node.mouseEvent(name)
|
217
227
|
|
218
228
|
setTimeout =>
|
219
|
-
|
220
|
-
|
221
|
-
|
229
|
+
# If the state is still the same then navigation event won't happen
|
230
|
+
if @currentPage.state == 'mouse_event'
|
231
|
+
@currentPage.state = 'default'
|
232
|
+
this.sendResponse(position: @last_mouse_event)
|
233
|
+
else
|
234
|
+
@currentPage.waitState 'default', =>
|
235
|
+
this.sendResponse(position: @last_mouse_event)
|
222
236
|
, 5
|
223
237
|
|
224
238
|
click: (page_id, id) ->
|
225
239
|
this.mouse_event page_id, id, 'click'
|
226
240
|
|
241
|
+
right_click: (page_id, id) ->
|
242
|
+
this.mouse_event page_id, id, 'rightclick'
|
243
|
+
|
227
244
|
double_click: (page_id, id) ->
|
228
245
|
this.mouse_event page_id, id, 'doubleclick'
|
229
246
|
|
@@ -231,8 +248,8 @@ class Poltergeist.Browser
|
|
231
248
|
this.mouse_event page_id, id, 'mousemove'
|
232
249
|
|
233
250
|
click_coordinates: (x, y) ->
|
234
|
-
@
|
235
|
-
this.sendResponse(
|
251
|
+
@currentPage.sendEvent('click', x, y)
|
252
|
+
this.sendResponse(click: { x: x, y: y })
|
236
253
|
|
237
254
|
drag: (page_id, id, other_id) ->
|
238
255
|
this.node(page_id, id).dragTo this.node(page_id, other_id)
|
@@ -250,84 +267,92 @@ class Poltergeist.Browser
|
|
250
267
|
this.sendResponse(true)
|
251
268
|
|
252
269
|
scroll_to: (left, top) ->
|
253
|
-
@
|
270
|
+
@currentPage.setScrollPosition(left: left, top: top)
|
254
271
|
this.sendResponse(true)
|
255
272
|
|
256
273
|
send_keys: (page_id, id, keys) ->
|
274
|
+
target = this.node(page_id, id)
|
275
|
+
|
257
276
|
# Programmatically generated focus doesn't work for `sendKeys`.
|
258
277
|
# That's why we need something more realistic like user behavior.
|
259
|
-
|
278
|
+
if !target.containsSelection()
|
279
|
+
target.mouseEvent('click')
|
280
|
+
|
260
281
|
for sequence in keys
|
261
|
-
key = if sequence.key? then @
|
262
|
-
@
|
282
|
+
key = if sequence.key? then @currentPage.keyCode(sequence.key) else sequence
|
283
|
+
@currentPage.sendEvent('keypress', key)
|
263
284
|
this.sendResponse(true)
|
264
285
|
|
265
286
|
render_base64: (format, full, selector = null)->
|
266
287
|
this.set_clip_rect(full, selector)
|
267
|
-
encoded_image = @
|
288
|
+
encoded_image = @currentPage.renderBase64(format)
|
268
289
|
this.sendResponse(encoded_image)
|
269
290
|
|
270
291
|
render: (path, full, selector = null) ->
|
271
292
|
dimensions = this.set_clip_rect(full, selector)
|
272
|
-
@
|
273
|
-
@
|
274
|
-
@
|
293
|
+
@currentPage.setScrollPosition(left: 0, top: 0)
|
294
|
+
@currentPage.render(path)
|
295
|
+
@currentPage.setScrollPosition(left: dimensions.left, top: dimensions.top)
|
275
296
|
this.sendResponse(true)
|
276
297
|
|
277
298
|
set_clip_rect: (full, selector) ->
|
278
|
-
dimensions = @
|
299
|
+
dimensions = @currentPage.validatedDimensions()
|
279
300
|
[document, viewport] = [dimensions.document, dimensions.viewport]
|
280
301
|
|
281
302
|
rect = if full
|
282
303
|
left: 0, top: 0, width: document.width, height: document.height
|
283
304
|
else
|
284
305
|
if selector?
|
285
|
-
@
|
306
|
+
@currentPage.elementBounds(selector)
|
286
307
|
else
|
287
308
|
left: 0, top: 0, width: viewport.width, height: viewport.height
|
288
309
|
|
289
|
-
@
|
310
|
+
@currentPage.setClipRect(rect)
|
290
311
|
dimensions
|
291
312
|
|
292
313
|
set_paper_size: (size) ->
|
293
|
-
@
|
314
|
+
@currentPage.setPaperSize(size)
|
315
|
+
this.sendResponse(true)
|
316
|
+
|
317
|
+
set_zoom_factor: (zoom_factor) ->
|
318
|
+
@currentPage.setZoomFactor(zoom_factor)
|
294
319
|
this.sendResponse(true)
|
295
320
|
|
296
321
|
resize: (width, height) ->
|
297
|
-
@
|
322
|
+
@currentPage.setViewportSize(width: width, height: height)
|
298
323
|
this.sendResponse(true)
|
299
324
|
|
300
325
|
network_traffic: ->
|
301
|
-
this.sendResponse(@
|
326
|
+
this.sendResponse(@currentPage.networkTraffic())
|
302
327
|
|
303
328
|
clear_network_traffic: ->
|
304
|
-
@
|
329
|
+
@currentPage.clearNetworkTraffic()
|
305
330
|
this.sendResponse(true)
|
306
331
|
|
307
332
|
get_headers: ->
|
308
|
-
this.sendResponse(@
|
333
|
+
this.sendResponse(@currentPage.getCustomHeaders())
|
309
334
|
|
310
335
|
set_headers: (headers) ->
|
311
336
|
# Workaround for https://code.google.com/p/phantomjs/issues/detail?id=745
|
312
|
-
@
|
313
|
-
@
|
337
|
+
@currentPage.setUserAgent(headers['User-Agent']) if headers['User-Agent']
|
338
|
+
@currentPage.setCustomHeaders(headers)
|
314
339
|
this.sendResponse(true)
|
315
340
|
|
316
341
|
add_headers: (headers) ->
|
317
|
-
allHeaders = @
|
342
|
+
allHeaders = @currentPage.getCustomHeaders()
|
318
343
|
for name, value of headers
|
319
344
|
allHeaders[name] = value
|
320
345
|
this.set_headers(allHeaders)
|
321
346
|
|
322
347
|
add_header: (header, permanent) ->
|
323
|
-
@
|
348
|
+
@currentPage.addTempHeader(header) unless permanent
|
324
349
|
this.add_headers(header)
|
325
350
|
|
326
351
|
response_headers: ->
|
327
|
-
this.sendResponse(@
|
352
|
+
this.sendResponse(@currentPage.responseHeaders())
|
328
353
|
|
329
354
|
cookies: ->
|
330
|
-
this.sendResponse(@
|
355
|
+
this.sendResponse(@currentPage.cookies())
|
331
356
|
|
332
357
|
# We're using phantom.addCookie so that cookies can be set
|
333
358
|
# before the first page load has taken place.
|
@@ -336,7 +361,11 @@ class Poltergeist.Browser
|
|
336
361
|
this.sendResponse(true)
|
337
362
|
|
338
363
|
remove_cookie: (name) ->
|
339
|
-
@
|
364
|
+
@currentPage.deleteCookie(name)
|
365
|
+
this.sendResponse(true)
|
366
|
+
|
367
|
+
clear_cookies: () ->
|
368
|
+
phantom.clearCookies()
|
340
369
|
this.sendResponse(true)
|
341
370
|
|
342
371
|
cookies_enabled: (flag) ->
|
@@ -344,7 +373,7 @@ class Poltergeist.Browser
|
|
344
373
|
this.sendResponse(true)
|
345
374
|
|
346
375
|
set_http_auth: (user, password) ->
|
347
|
-
@
|
376
|
+
@currentPage.setHttpAuth(user, password)
|
348
377
|
this.sendResponse(true)
|
349
378
|
|
350
379
|
set_js_errors: (value) ->
|
@@ -366,9 +395,23 @@ class Poltergeist.Browser
|
|
366
395
|
throw new Error('zomg')
|
367
396
|
|
368
397
|
go_back: ->
|
369
|
-
|
370
|
-
|
398
|
+
if @currentPage.canGoBack
|
399
|
+
@currentPage.state = 'loading'
|
400
|
+
@currentPage.goBack()
|
401
|
+
@currentPage.waitState 'default', =>
|
402
|
+
this.sendResponse(true)
|
403
|
+
else
|
404
|
+
this.sendResponse(false)
|
371
405
|
|
372
406
|
go_forward: ->
|
373
|
-
|
374
|
-
|
407
|
+
if @currentPage.canGoForward
|
408
|
+
@currentPage.state = 'loading'
|
409
|
+
@currentPage.goForward()
|
410
|
+
@currentPage.waitState 'default', =>
|
411
|
+
this.sendResponse(true)
|
412
|
+
else
|
413
|
+
this.sendResponse(false)
|
414
|
+
|
415
|
+
set_url_blacklist: ->
|
416
|
+
@currentPage.urlBlacklist = Array.prototype.slice.call(arguments)
|
417
|
+
@sendResponse(true)
|