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
@@ -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)
|