poltergeist 1.6.0 → 1.7.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 +4 -4
- data/README.md +23 -9
- data/lib/capybara/poltergeist/browser.rb +47 -5
- data/lib/capybara/poltergeist/client/agent.coffee +32 -10
- data/lib/capybara/poltergeist/client/browser.coffee +61 -1
- data/lib/capybara/poltergeist/client/compiled/agent.js +34 -15
- data/lib/capybara/poltergeist/client/compiled/browser.js +73 -2
- data/lib/capybara/poltergeist/client/compiled/node.js +13 -1
- data/lib/capybara/poltergeist/client/compiled/web_page.js +17 -1
- data/lib/capybara/poltergeist/client/node.coffee +14 -1
- data/lib/capybara/poltergeist/client/web_page.coffee +15 -5
- data/lib/capybara/poltergeist/driver.rb +47 -1
- data/lib/capybara/poltergeist/node.rb +8 -0
- data/lib/capybara/poltergeist/version.rb +1 -1
- metadata +4 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 440dc77fc5ba49d78d8e7af8134da82b93a500cd
|
4
|
+
data.tar.gz: afc6fba6b9d6be96866c9810cb1b9a729dddc4a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 704dea9cd5162c277fd8b5b9ee2f816beec71d4deabd64616a7ebe87d64d6c8a922a9fd9ba1d9b71b05be8b28eeaacc6aad225140ee47be19c4d8c1536726eb6
|
7
|
+
data.tar.gz: 6ba1c737813c8a09c47a51e31032626ad21e251f1de4a7e05e758057d99702720313950c307d04b458be52ffc665eae5e892ac256e536afdf4baf399bf37b5df
|
data/README.md
CHANGED
@@ -4,12 +4,12 @@
|
|
4
4
|
|
5
5
|
Poltergeist is a driver for [Capybara](https://github.com/jnicklas/capybara). It allows you to
|
6
6
|
run your Capybara tests on a headless [WebKit](http://webkit.org) browser,
|
7
|
-
provided by [PhantomJS](http://
|
7
|
+
provided by [PhantomJS](http://phantomjs.org/).
|
8
8
|
|
9
9
|
**If you're viewing this at https://github.com/teampoltergeist/poltergeist,
|
10
10
|
you're reading the documentation for the master branch.
|
11
11
|
[View documentation for the latest release
|
12
|
-
(1.
|
12
|
+
(1.7.0).](https://github.com/teampoltergeist/poltergeist/tree/v1.7.0)**
|
13
13
|
|
14
14
|
## Getting help ##
|
15
15
|
|
@@ -43,17 +43,17 @@ dependencies* (you don't need Qt, or a running X server, etc.)
|
|
43
43
|
|
44
44
|
* *Homebrew*: `brew install phantomjs`
|
45
45
|
* *MacPorts*: `sudo port install phantomjs`
|
46
|
-
* *Manual install*: [Download this](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.
|
46
|
+
* *Manual install*: [Download this](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-macosx.zip)
|
47
47
|
|
48
48
|
### Linux ###
|
49
49
|
|
50
|
-
* Download the [32 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.
|
51
|
-
or [64 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.
|
50
|
+
* Download the [32 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-i686.tar.bz2)
|
51
|
+
or [64 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2)
|
52
52
|
binary.
|
53
53
|
* Extract the tarball and copy `bin/phantomjs` into your `PATH`
|
54
54
|
|
55
55
|
### Windows ###
|
56
|
-
* Download the [precompiled binary](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.
|
56
|
+
* Download the [precompiled binary](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-windows.zip)
|
57
57
|
for Windows
|
58
58
|
|
59
59
|
### Manual compilation ###
|
@@ -61,7 +61,7 @@ for Windows
|
|
61
61
|
Do this as a last resort if the binaries don't work for you. It will
|
62
62
|
take quite a long time as it has to build WebKit.
|
63
63
|
|
64
|
-
* Download [the source tarball](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.
|
64
|
+
* Download [the source tarball](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-source.zip)
|
65
65
|
* Extract and cd in
|
66
66
|
* `./build.sh`
|
67
67
|
|
@@ -235,7 +235,7 @@ See more about [sendEvent](http://phantomjs.org/api/webpage/method/send-event.ht
|
|
235
235
|
|
236
236
|
## Customization ##
|
237
237
|
|
238
|
-
You can customize the way that Capybara sets up
|
238
|
+
You can customize the way that Capybara sets up Poltergeist via the following code in your
|
239
239
|
test setup:
|
240
240
|
|
241
241
|
``` ruby
|
@@ -266,6 +266,20 @@ end
|
|
266
266
|
* `:port` (Fixnum) - The port which should be used to communicate
|
267
267
|
with the PhantomJS process. Defaults to a random open port.
|
268
268
|
|
269
|
+
### URL Blacklisting ###
|
270
|
+
|
271
|
+
Poltergeist supports URL blacklisting which allows you
|
272
|
+
to prevent scripts from running on designated domains. If you are experiencing
|
273
|
+
slower run times, consider creating a URL blacklist of domains that are not
|
274
|
+
essential to your testing environment, such as ad networks or analytics.
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
page.driver.browser.url_blacklist = ['http://www.example.com']
|
278
|
+
```
|
279
|
+
|
280
|
+
Make sure you set it before each running test, because this setting's cleaned
|
281
|
+
up when capybara does reset.
|
282
|
+
|
269
283
|
## Troubleshooting ##
|
270
284
|
|
271
285
|
Unfortunately, the nature of full-stack testing is that things can and
|
@@ -383,7 +397,7 @@ the [changelog](CHANGELOG.md).
|
|
383
397
|
|
384
398
|
## License ##
|
385
399
|
|
386
|
-
Copyright (c) 2011-
|
400
|
+
Copyright (c) 2011-2015 Jonathan Leighton
|
387
401
|
|
388
402
|
Permission is hereby granted, free of charge, to any person obtaining
|
389
403
|
a copy of this software and associated documentation files (the
|
@@ -24,7 +24,9 @@ module Capybara::Poltergeist
|
|
24
24
|
server.restart
|
25
25
|
client.restart
|
26
26
|
|
27
|
-
self.debug = @debug if @debug
|
27
|
+
self.debug = @debug if defined?(@debug)
|
28
|
+
self.js_errors = @js_errors if defined?(@js_errors)
|
29
|
+
self.extensions = @extensions if @extensions
|
28
30
|
end
|
29
31
|
|
30
32
|
def visit(url)
|
@@ -183,6 +185,10 @@ module Capybara::Poltergeist
|
|
183
185
|
command 'drag', page_id, id, other_id
|
184
186
|
end
|
185
187
|
|
188
|
+
def drag_by(page_id, id, x, y)
|
189
|
+
command 'drag_by', page_id, id, x, y
|
190
|
+
end
|
191
|
+
|
186
192
|
def select(page_id, id, value)
|
187
193
|
command 'select', page_id, id, value
|
188
194
|
end
|
@@ -225,6 +231,10 @@ module Capybara::Poltergeist
|
|
225
231
|
command 'send_keys', page_id, id, normalize_keys(keys)
|
226
232
|
end
|
227
233
|
|
234
|
+
def path(page_id, id)
|
235
|
+
command 'path', page_id, id
|
236
|
+
end
|
237
|
+
|
228
238
|
def network_traffic
|
229
239
|
command('network_traffic').values.map do |event|
|
230
240
|
NetworkTraffic::Request.new(
|
@@ -291,10 +301,12 @@ module Capybara::Poltergeist
|
|
291
301
|
end
|
292
302
|
|
293
303
|
def js_errors=(val)
|
304
|
+
@js_errors = val
|
294
305
|
command 'set_js_errors', !!val
|
295
306
|
end
|
296
307
|
|
297
308
|
def extensions=(names)
|
309
|
+
@extensions = names
|
298
310
|
Array(names).each do |name|
|
299
311
|
command 'add_extension', name
|
300
312
|
end
|
@@ -337,6 +349,32 @@ module Capybara::Poltergeist
|
|
337
349
|
command 'go_forward'
|
338
350
|
end
|
339
351
|
|
352
|
+
def accept_confirm
|
353
|
+
command 'set_confirm_process', true
|
354
|
+
end
|
355
|
+
|
356
|
+
def dismiss_confirm
|
357
|
+
command 'set_confirm_process', false
|
358
|
+
end
|
359
|
+
|
360
|
+
#
|
361
|
+
# press "OK" with text (response) or default value
|
362
|
+
#
|
363
|
+
def accept_prompt(response)
|
364
|
+
command 'set_prompt_response', response || false
|
365
|
+
end
|
366
|
+
|
367
|
+
#
|
368
|
+
# press "Cancel"
|
369
|
+
#
|
370
|
+
def dismiss_prompt
|
371
|
+
command 'set_prompt_response', nil
|
372
|
+
end
|
373
|
+
|
374
|
+
def modal_message
|
375
|
+
command 'modal_message'
|
376
|
+
end
|
377
|
+
|
340
378
|
private
|
341
379
|
|
342
380
|
def log(message)
|
@@ -354,11 +392,15 @@ module Capybara::Poltergeist
|
|
354
392
|
keys.map do |key|
|
355
393
|
case key
|
356
394
|
when Array
|
357
|
-
#
|
358
|
-
|
359
|
-
|
395
|
+
# [:Shift, "s"] => { modifier: "shift", key: "S" }
|
396
|
+
# [:Ctrl, :Left] => { modifier: "ctrl", key: :Left }
|
397
|
+
# [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: :Left }
|
398
|
+
letter = key.pop
|
399
|
+
symbol = key.map { |k| k.to_s.downcase }.join(',')
|
400
|
+
|
401
|
+
{ modifier: symbol.to_s.downcase, key: letter.capitalize }
|
360
402
|
when Symbol
|
361
|
-
{ key: key } # Return a known sequence for PhantomJS
|
403
|
+
{ key: key.capitalize } # Return a known sequence for PhantomJS
|
362
404
|
when String
|
363
405
|
key # Plain string, nothing to do
|
364
406
|
end
|
@@ -24,8 +24,11 @@ class PoltergeistAgent
|
|
24
24
|
else
|
25
25
|
throw error
|
26
26
|
|
27
|
+
# Somehow PhantomJS returns all characters(brackets, etc) properly encoded
|
28
|
+
# except whitespace character in pathname part of the location. This hack
|
29
|
+
# is intended to fix this up.
|
27
30
|
currentUrl: ->
|
28
|
-
|
31
|
+
window.location.href.replace(/\ /g, '%20')
|
29
32
|
|
30
33
|
find: (method, selector, within = document) ->
|
31
34
|
try
|
@@ -112,7 +115,15 @@ class PoltergeistAgent.Node
|
|
112
115
|
changed: ->
|
113
116
|
event = document.createEvent('HTMLEvents')
|
114
117
|
event.initEvent('change', true, false)
|
115
|
-
|
118
|
+
|
119
|
+
# In the case of an OPTION tag, the change event should come
|
120
|
+
# from the parent SELECT
|
121
|
+
if @element.nodeName == 'OPTION'
|
122
|
+
element = @element.parentNode
|
123
|
+
else
|
124
|
+
element = @element
|
125
|
+
|
126
|
+
element.dispatchEvent(event)
|
116
127
|
|
117
128
|
input: ->
|
118
129
|
event = document.createEvent('HTMLEvents')
|
@@ -152,7 +163,7 @@ class PoltergeistAgent.Node
|
|
152
163
|
if @element.nodeName == "TEXTAREA"
|
153
164
|
@element.textContent
|
154
165
|
else
|
155
|
-
@element.innerText
|
166
|
+
@element.innerText || @element.textContent
|
156
167
|
|
157
168
|
deleteText: ->
|
158
169
|
range = document.createRange()
|
@@ -216,11 +227,17 @@ class PoltergeistAgent.Node
|
|
216
227
|
@element.removeAttribute(name)
|
217
228
|
|
218
229
|
select: (value) ->
|
219
|
-
if
|
230
|
+
if @isDisabled()
|
231
|
+
false
|
232
|
+
else if value == false && !@element.parentNode.multiple
|
220
233
|
false
|
221
234
|
else
|
235
|
+
this.trigger('focus', @element.parentNode)
|
236
|
+
|
222
237
|
@element.selected = value
|
223
238
|
this.changed()
|
239
|
+
|
240
|
+
this.trigger('blur', @element.parentNode)
|
224
241
|
true
|
225
242
|
|
226
243
|
tagName: ->
|
@@ -239,6 +256,14 @@ class PoltergeistAgent.Node
|
|
239
256
|
isDisabled: ->
|
240
257
|
@element.disabled || @element.tagName == 'OPTION' && @element.parentNode.disabled
|
241
258
|
|
259
|
+
path: ->
|
260
|
+
elements = @parentIds().reverse().map((id) => @agent.get(id))
|
261
|
+
elements.push(this)
|
262
|
+
selectors = elements.map (el)->
|
263
|
+
prev_siblings = el.find('xpath', "./preceding-sibling::#{el.tagName()}")
|
264
|
+
"#{el.tagName()}[#{prev_siblings.length + 1}]"
|
265
|
+
"//" + selectors.join('/')
|
266
|
+
|
242
267
|
containsSelection: ->
|
243
268
|
selectedNode = document.getSelection().focusNode
|
244
269
|
|
@@ -257,7 +282,7 @@ class PoltergeistAgent.Node
|
|
257
282
|
rect = win.frameElement.getClientRects()[0]
|
258
283
|
style = win.getComputedStyle(win.frameElement)
|
259
284
|
win = win.parent
|
260
|
-
|
285
|
+
|
261
286
|
offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10)
|
262
287
|
offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10)
|
263
288
|
|
@@ -279,7 +304,7 @@ class PoltergeistAgent.Node
|
|
279
304
|
|
280
305
|
pos
|
281
306
|
|
282
|
-
trigger: (name) ->
|
307
|
+
trigger: (name, element = @element) ->
|
283
308
|
if Node.EVENTS.MOUSE.indexOf(name) != -1
|
284
309
|
event = document.createEvent('MouseEvent')
|
285
310
|
event.initMouseEvent(
|
@@ -293,7 +318,7 @@ class PoltergeistAgent.Node
|
|
293
318
|
else
|
294
319
|
throw "Unknown event"
|
295
320
|
|
296
|
-
|
321
|
+
element.dispatchEvent(event)
|
297
322
|
|
298
323
|
obtainEvent: (name) ->
|
299
324
|
event = document.createEvent('HTMLEvents')
|
@@ -372,6 +397,3 @@ document.addEventListener(
|
|
372
397
|
'DOMContentLoaded',
|
373
398
|
-> console.log('__DOMContentLoaded')
|
374
399
|
)
|
375
|
-
|
376
|
-
window.confirm = (message) -> true
|
377
|
-
window.prompt = (message, _default) -> _default or null
|
@@ -7,6 +7,10 @@ class Poltergeist.Browser
|
|
7
7
|
@_debug = false
|
8
8
|
@_counter = 0
|
9
9
|
|
10
|
+
@processed_modal_messages = []
|
11
|
+
@confirm_processes = []
|
12
|
+
@prompt_responses = []
|
13
|
+
|
10
14
|
this.resetPage()
|
11
15
|
|
12
16
|
resetPage: ->
|
@@ -23,6 +27,28 @@ class Poltergeist.Browser
|
|
23
27
|
@page.handle = "#{@_counter++}"
|
24
28
|
@pages.push(@page)
|
25
29
|
|
30
|
+
@processed_modal_messages = []
|
31
|
+
@confirm_processes = []
|
32
|
+
@prompt_responses = []
|
33
|
+
|
34
|
+
|
35
|
+
@page.native().onAlert = (msg) =>
|
36
|
+
@setModalMessage msg
|
37
|
+
return
|
38
|
+
|
39
|
+
@page.native().onConfirm = (msg) =>
|
40
|
+
process = @confirm_processes.pop()
|
41
|
+
process = true if process == undefined
|
42
|
+
@setModalMessage msg
|
43
|
+
return process
|
44
|
+
|
45
|
+
@page.native().onPrompt = (msg, defaultVal) =>
|
46
|
+
response = @prompt_responses.pop()
|
47
|
+
response = defaultVal if (response == undefined || response == false)
|
48
|
+
|
49
|
+
@setModalMessage msg
|
50
|
+
return response
|
51
|
+
|
26
52
|
@page.onPageCreated = (newPage) =>
|
27
53
|
page = new Poltergeist.WebPage(newPage)
|
28
54
|
page.handle = "#{@_counter++}"
|
@@ -39,6 +65,9 @@ class Poltergeist.Browser
|
|
39
65
|
if @_debug
|
40
66
|
console.log "poltergeist [#{new Date().getTime()}] #{message}"
|
41
67
|
|
68
|
+
setModalMessage: (msg) ->
|
69
|
+
@processed_modal_messages.push(msg)
|
70
|
+
|
42
71
|
sendResponse: (response) ->
|
43
72
|
errors = @currentPage.errors
|
44
73
|
@currentPage.clearErrors()
|
@@ -60,6 +89,11 @@ class Poltergeist.Browser
|
|
60
89
|
|
61
90
|
visit: (url) ->
|
62
91
|
@currentPage.state = 'loading'
|
92
|
+
#reset modal processing state when changing page
|
93
|
+
@processed_modal_messages = []
|
94
|
+
@confirm_processes = []
|
95
|
+
@prompt_responses = []
|
96
|
+
|
63
97
|
|
64
98
|
# Prevent firing `page.onInitialized` event twice. Calling currentUrl
|
65
99
|
# method before page is actually opened fires this event for the first time.
|
@@ -149,6 +183,9 @@ class Poltergeist.Browser
|
|
149
183
|
disabled: (page_id, id) ->
|
150
184
|
this.sendResponse this.node(page_id, id).isDisabled()
|
151
185
|
|
186
|
+
path: (page_id, id) ->
|
187
|
+
this.sendResponse this.node(page_id, id).path()
|
188
|
+
|
152
189
|
evaluate: (script) ->
|
153
190
|
this.sendResponse @currentPage.evaluate("function() { return #{script} }")
|
154
191
|
|
@@ -255,6 +292,10 @@ class Poltergeist.Browser
|
|
255
292
|
this.node(page_id, id).dragTo this.node(page_id, other_id)
|
256
293
|
this.sendResponse(true)
|
257
294
|
|
295
|
+
drag_by: (page_id, id, x, y) ->
|
296
|
+
this.node(page_id, id).dragBy(x, y)
|
297
|
+
this.sendResponse(true)
|
298
|
+
|
258
299
|
trigger: (page_id, id, event) ->
|
259
300
|
this.node(page_id, id).trigger(event)
|
260
301
|
this.sendResponse(event)
|
@@ -280,7 +321,15 @@ class Poltergeist.Browser
|
|
280
321
|
|
281
322
|
for sequence in keys
|
282
323
|
key = if sequence.key? then @currentPage.keyCode(sequence.key) else sequence
|
283
|
-
|
324
|
+
if sequence.modifier?
|
325
|
+
modifier_keys = @currentPage.keyModifierKeys(sequence.modifier)
|
326
|
+
modifier_code = @currentPage.keyModifierCode(sequence.modifier)
|
327
|
+
@currentPage.sendEvent('keydown', modifier_key) for modifier_key in modifier_keys
|
328
|
+
@currentPage.sendEvent('keypress', key, null, null, modifier_code)
|
329
|
+
@currentPage.sendEvent('keyup', modifier_key) for modifier_key in modifier_keys
|
330
|
+
else
|
331
|
+
@currentPage.sendEvent('keypress', key)
|
332
|
+
|
284
333
|
this.sendResponse(true)
|
285
334
|
|
286
335
|
render_base64: (format, full, selector = null)->
|
@@ -415,3 +464,14 @@ class Poltergeist.Browser
|
|
415
464
|
set_url_blacklist: ->
|
416
465
|
@currentPage.urlBlacklist = Array.prototype.slice.call(arguments)
|
417
466
|
@sendResponse(true)
|
467
|
+
|
468
|
+
set_confirm_process: (process) ->
|
469
|
+
@confirm_processes.push process
|
470
|
+
@sendResponse(true)
|
471
|
+
|
472
|
+
set_prompt_response: (response) ->
|
473
|
+
@prompt_responses.push response
|
474
|
+
@sendResponse(true)
|
475
|
+
|
476
|
+
modal_message: ->
|
477
|
+
@sendResponse(@processed_modal_messages.shift())
|
@@ -44,7 +44,7 @@ PoltergeistAgent = (function() {
|
|
44
44
|
};
|
45
45
|
|
46
46
|
PoltergeistAgent.prototype.currentUrl = function() {
|
47
|
-
return
|
47
|
+
return window.location.href.replace(/\ /g, '%20');
|
48
48
|
};
|
49
49
|
|
50
50
|
PoltergeistAgent.prototype.find = function(method, selector, within) {
|
@@ -195,10 +195,15 @@ PoltergeistAgent.Node = (function() {
|
|
195
195
|
};
|
196
196
|
|
197
197
|
Node.prototype.changed = function() {
|
198
|
-
var event;
|
198
|
+
var element, event;
|
199
199
|
event = document.createEvent('HTMLEvents');
|
200
200
|
event.initEvent('change', true, false);
|
201
|
-
|
201
|
+
if (this.element.nodeName === 'OPTION') {
|
202
|
+
element = this.element.parentNode;
|
203
|
+
} else {
|
204
|
+
element = this.element;
|
205
|
+
}
|
206
|
+
return element.dispatchEvent(event);
|
202
207
|
};
|
203
208
|
|
204
209
|
Node.prototype.input = function() {
|
@@ -246,7 +251,7 @@ PoltergeistAgent.Node = (function() {
|
|
246
251
|
if (this.element.nodeName === "TEXTAREA") {
|
247
252
|
return this.element.textContent;
|
248
253
|
} else {
|
249
|
-
return this.element.innerText;
|
254
|
+
return this.element.innerText || this.element.textContent;
|
250
255
|
}
|
251
256
|
}
|
252
257
|
};
|
@@ -340,11 +345,15 @@ PoltergeistAgent.Node = (function() {
|
|
340
345
|
};
|
341
346
|
|
342
347
|
Node.prototype.select = function(value) {
|
343
|
-
if (
|
348
|
+
if (this.isDisabled()) {
|
349
|
+
return false;
|
350
|
+
} else if (value === false && !this.element.parentNode.multiple) {
|
344
351
|
return false;
|
345
352
|
} else {
|
353
|
+
this.trigger('focus', this.element.parentNode);
|
346
354
|
this.element.selected = value;
|
347
355
|
this.changed();
|
356
|
+
this.trigger('blur', this.element.parentNode);
|
348
357
|
return true;
|
349
358
|
}
|
350
359
|
};
|
@@ -370,6 +379,21 @@ PoltergeistAgent.Node = (function() {
|
|
370
379
|
return this.element.disabled || this.element.tagName === 'OPTION' && this.element.parentNode.disabled;
|
371
380
|
};
|
372
381
|
|
382
|
+
Node.prototype.path = function() {
|
383
|
+
var elements, selectors,
|
384
|
+
_this = this;
|
385
|
+
elements = this.parentIds().reverse().map(function(id) {
|
386
|
+
return _this.agent.get(id);
|
387
|
+
});
|
388
|
+
elements.push(this);
|
389
|
+
selectors = elements.map(function(el) {
|
390
|
+
var prev_siblings;
|
391
|
+
prev_siblings = el.find('xpath', "./preceding-sibling::" + (el.tagName()));
|
392
|
+
return "" + (el.tagName()) + "[" + (prev_siblings.length + 1) + "]";
|
393
|
+
});
|
394
|
+
return "//" + selectors.join('/');
|
395
|
+
};
|
396
|
+
|
373
397
|
Node.prototype.containsSelection = function() {
|
374
398
|
var selectedNode;
|
375
399
|
selectedNode = document.getSelection().focusNode;
|
@@ -417,8 +441,11 @@ PoltergeistAgent.Node = (function() {
|
|
417
441
|
return pos;
|
418
442
|
};
|
419
443
|
|
420
|
-
Node.prototype.trigger = function(name) {
|
444
|
+
Node.prototype.trigger = function(name, element) {
|
421
445
|
var event;
|
446
|
+
if (element == null) {
|
447
|
+
element = this.element;
|
448
|
+
}
|
422
449
|
if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
|
423
450
|
event = document.createEvent('MouseEvent');
|
424
451
|
event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
@@ -429,7 +456,7 @@ PoltergeistAgent.Node = (function() {
|
|
429
456
|
} else {
|
430
457
|
throw "Unknown event";
|
431
458
|
}
|
432
|
-
return
|
459
|
+
return element.dispatchEvent(event);
|
433
460
|
};
|
434
461
|
|
435
462
|
Node.prototype.obtainEvent = function(name) {
|
@@ -529,11 +556,3 @@ window.__poltergeist = new PoltergeistAgent;
|
|
529
556
|
document.addEventListener('DOMContentLoaded', function() {
|
530
557
|
return console.log('__DOMContentLoaded');
|
531
558
|
});
|
532
|
-
|
533
|
-
window.confirm = function(message) {
|
534
|
-
return true;
|
535
|
-
};
|
536
|
-
|
537
|
-
window.prompt = function(message, _default) {
|
538
|
-
return _default || null;
|
539
|
-
};
|
@@ -9,6 +9,9 @@ Poltergeist.Browser = (function() {
|
|
9
9
|
this.js_errors = true;
|
10
10
|
this._debug = false;
|
11
11
|
this._counter = 0;
|
12
|
+
this.processed_modal_messages = [];
|
13
|
+
this.confirm_processes = [];
|
14
|
+
this.prompt_responses = [];
|
12
15
|
this.resetPage();
|
13
16
|
}
|
14
17
|
|
@@ -32,6 +35,30 @@ Poltergeist.Browser = (function() {
|
|
32
35
|
});
|
33
36
|
this.page.handle = "" + (this._counter++);
|
34
37
|
this.pages.push(this.page);
|
38
|
+
this.processed_modal_messages = [];
|
39
|
+
this.confirm_processes = [];
|
40
|
+
this.prompt_responses = [];
|
41
|
+
this.page["native"]().onAlert = function(msg) {
|
42
|
+
_this.setModalMessage(msg);
|
43
|
+
};
|
44
|
+
this.page["native"]().onConfirm = function(msg) {
|
45
|
+
var process;
|
46
|
+
process = _this.confirm_processes.pop();
|
47
|
+
if (process === void 0) {
|
48
|
+
process = true;
|
49
|
+
}
|
50
|
+
_this.setModalMessage(msg);
|
51
|
+
return process;
|
52
|
+
};
|
53
|
+
this.page["native"]().onPrompt = function(msg, defaultVal) {
|
54
|
+
var response;
|
55
|
+
response = _this.prompt_responses.pop();
|
56
|
+
if (response === void 0 || response === false) {
|
57
|
+
response = defaultVal;
|
58
|
+
}
|
59
|
+
_this.setModalMessage(msg);
|
60
|
+
return response;
|
61
|
+
};
|
35
62
|
return this.page.onPageCreated = function(newPage) {
|
36
63
|
var page;
|
37
64
|
page = new Poltergeist.WebPage(newPage);
|
@@ -57,6 +84,10 @@ Poltergeist.Browser = (function() {
|
|
57
84
|
}
|
58
85
|
};
|
59
86
|
|
87
|
+
Browser.prototype.setModalMessage = function(msg) {
|
88
|
+
return this.processed_modal_messages.push(msg);
|
89
|
+
};
|
90
|
+
|
60
91
|
Browser.prototype.sendResponse = function(response) {
|
61
92
|
var errors;
|
62
93
|
errors = this.currentPage.errors;
|
@@ -85,6 +116,9 @@ Poltergeist.Browser = (function() {
|
|
85
116
|
var prevUrl,
|
86
117
|
_this = this;
|
87
118
|
this.currentPage.state = 'loading';
|
119
|
+
this.processed_modal_messages = [];
|
120
|
+
this.confirm_processes = [];
|
121
|
+
this.prompt_responses = [];
|
88
122
|
prevUrl = this.currentPage.source === null ? 'about:blank' : this.currentPage.currentUrl();
|
89
123
|
this.currentPage.open(url);
|
90
124
|
if (/#/.test(url) && prevUrl.split('#')[0] === url.split('#')[0]) {
|
@@ -194,6 +228,10 @@ Poltergeist.Browser = (function() {
|
|
194
228
|
return this.sendResponse(this.node(page_id, id).isDisabled());
|
195
229
|
};
|
196
230
|
|
231
|
+
Browser.prototype.path = function(page_id, id) {
|
232
|
+
return this.sendResponse(this.node(page_id, id).path());
|
233
|
+
};
|
234
|
+
|
197
235
|
Browser.prototype.evaluate = function(script) {
|
198
236
|
return this.sendResponse(this.currentPage.evaluate("function() { return " + script + " }"));
|
199
237
|
};
|
@@ -347,6 +385,11 @@ Poltergeist.Browser = (function() {
|
|
347
385
|
return this.sendResponse(true);
|
348
386
|
};
|
349
387
|
|
388
|
+
Browser.prototype.drag_by = function(page_id, id, x, y) {
|
389
|
+
this.node(page_id, id).dragBy(x, y);
|
390
|
+
return this.sendResponse(true);
|
391
|
+
};
|
392
|
+
|
350
393
|
Browser.prototype.trigger = function(page_id, id, event) {
|
351
394
|
this.node(page_id, id).trigger(event);
|
352
395
|
return this.sendResponse(event);
|
@@ -370,7 +413,7 @@ Poltergeist.Browser = (function() {
|
|
370
413
|
};
|
371
414
|
|
372
415
|
Browser.prototype.send_keys = function(page_id, id, keys) {
|
373
|
-
var key, sequence, target, _i, _len;
|
416
|
+
var key, modifier_code, modifier_key, modifier_keys, sequence, target, _i, _j, _k, _len, _len1, _len2;
|
374
417
|
target = this.node(page_id, id);
|
375
418
|
if (!target.containsSelection()) {
|
376
419
|
target.mouseEvent('click');
|
@@ -378,7 +421,21 @@ Poltergeist.Browser = (function() {
|
|
378
421
|
for (_i = 0, _len = keys.length; _i < _len; _i++) {
|
379
422
|
sequence = keys[_i];
|
380
423
|
key = sequence.key != null ? this.currentPage.keyCode(sequence.key) : sequence;
|
381
|
-
|
424
|
+
if (sequence.modifier != null) {
|
425
|
+
modifier_keys = this.currentPage.keyModifierKeys(sequence.modifier);
|
426
|
+
modifier_code = this.currentPage.keyModifierCode(sequence.modifier);
|
427
|
+
for (_j = 0, _len1 = modifier_keys.length; _j < _len1; _j++) {
|
428
|
+
modifier_key = modifier_keys[_j];
|
429
|
+
this.currentPage.sendEvent('keydown', modifier_key);
|
430
|
+
}
|
431
|
+
this.currentPage.sendEvent('keypress', key, null, null, modifier_code);
|
432
|
+
for (_k = 0, _len2 = modifier_keys.length; _k < _len2; _k++) {
|
433
|
+
modifier_key = modifier_keys[_k];
|
434
|
+
this.currentPage.sendEvent('keyup', modifier_key);
|
435
|
+
}
|
436
|
+
} else {
|
437
|
+
this.currentPage.sendEvent('keypress', key);
|
438
|
+
}
|
382
439
|
}
|
383
440
|
return this.sendResponse(true);
|
384
441
|
};
|
@@ -570,6 +627,20 @@ Poltergeist.Browser = (function() {
|
|
570
627
|
return this.sendResponse(true);
|
571
628
|
};
|
572
629
|
|
630
|
+
Browser.prototype.set_confirm_process = function(process) {
|
631
|
+
this.confirm_processes.push(process);
|
632
|
+
return this.sendResponse(true);
|
633
|
+
};
|
634
|
+
|
635
|
+
Browser.prototype.set_prompt_response = function(response) {
|
636
|
+
this.prompt_responses.push(response);
|
637
|
+
return this.sendResponse(true);
|
638
|
+
};
|
639
|
+
|
640
|
+
Browser.prototype.modal_message = function() {
|
641
|
+
return this.sendResponse(this.processed_modal_messages.shift());
|
642
|
+
};
|
643
|
+
|
573
644
|
return Browser;
|
574
645
|
|
575
646
|
})();
|
@@ -4,7 +4,7 @@ Poltergeist.Node = (function() {
|
|
4
4
|
var name, _fn, _i, _len, _ref,
|
5
5
|
_this = this;
|
6
6
|
|
7
|
-
Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection'];
|
7
|
+
Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection', 'path'];
|
8
8
|
|
9
9
|
function Node(page, id) {
|
10
10
|
this.page = page;
|
@@ -68,6 +68,18 @@ Poltergeist.Node = (function() {
|
|
68
68
|
return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y);
|
69
69
|
};
|
70
70
|
|
71
|
+
Node.prototype.dragBy = function(x, y) {
|
72
|
+
var final_pos, position;
|
73
|
+
this.scrollIntoView();
|
74
|
+
position = this.mouseEventPosition();
|
75
|
+
final_pos = {
|
76
|
+
x: position.x + x,
|
77
|
+
y: position.y + y
|
78
|
+
};
|
79
|
+
this.page.mouseEvent('mousedown', position.x, position.y);
|
80
|
+
return this.page.mouseEvent('mouseup', final_pos.x, final_pos.y);
|
81
|
+
};
|
82
|
+
|
71
83
|
Node.prototype.isEqual = function(other) {
|
72
84
|
return this.page === other.page && this.isDOMEqual(other.id);
|
73
85
|
};
|
@@ -5,7 +5,7 @@ Poltergeist.WebPage = (function() {
|
|
5
5
|
var command, delegate, _fn, _fn1, _i, _j, _len, _len1, _ref, _ref1,
|
6
6
|
_this = this;
|
7
7
|
|
8
|
-
WebPage.CALLBACKS = ['
|
8
|
+
WebPage.CALLBACKS = ['onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing'];
|
9
9
|
|
10
10
|
WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward'];
|
11
11
|
|
@@ -185,6 +185,22 @@ Poltergeist.WebPage = (function() {
|
|
185
185
|
return this["native"]().event.key[name];
|
186
186
|
};
|
187
187
|
|
188
|
+
WebPage.prototype.keyModifierCode = function(names) {
|
189
|
+
var modifiers;
|
190
|
+
modifiers = this["native"]().event.modifier;
|
191
|
+
names = names.split(',').map((function(name) {
|
192
|
+
return modifiers[name];
|
193
|
+
}));
|
194
|
+
return names[0] | names[1];
|
195
|
+
};
|
196
|
+
|
197
|
+
WebPage.prototype.keyModifierKeys = function(names) {
|
198
|
+
var _this = this;
|
199
|
+
return names.split(',').map(function(name) {
|
200
|
+
return _this.keyCode(name.charAt(0).toUpperCase() + name.substring(1));
|
201
|
+
});
|
202
|
+
};
|
203
|
+
|
188
204
|
WebPage.prototype.waitState = function(state, callback) {
|
189
205
|
var _this = this;
|
190
206
|
if (this.state === state) {
|
@@ -4,7 +4,7 @@ class Poltergeist.Node
|
|
4
4
|
@DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete',
|
5
5
|
'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes',
|
6
6
|
'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest',
|
7
|
-
'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection']
|
7
|
+
'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection', 'path']
|
8
8
|
|
9
9
|
constructor: (@page, @id) ->
|
10
10
|
|
@@ -53,5 +53,18 @@ class Poltergeist.Node
|
|
53
53
|
@page.mouseEvent('mousedown', position.x, position.y)
|
54
54
|
@page.mouseEvent('mouseup', otherPosition.x, otherPosition.y)
|
55
55
|
|
56
|
+
dragBy: (x, y) ->
|
57
|
+
this.scrollIntoView()
|
58
|
+
|
59
|
+
position = this.mouseEventPosition()
|
60
|
+
|
61
|
+
final_pos =
|
62
|
+
x: position.x + x
|
63
|
+
y: position.y + y
|
64
|
+
|
65
|
+
@page.mouseEvent('mousedown', position.x, position.y)
|
66
|
+
@page.mouseEvent('mouseup', final_pos.x, final_pos.y)
|
67
|
+
|
68
|
+
|
56
69
|
isEqual: (other) ->
|
57
70
|
@page == other.page && this.isDOMEqual(other.id)
|
@@ -1,8 +1,9 @@
|
|
1
1
|
class Poltergeist.WebPage
|
2
|
-
@CALLBACKS = ['
|
3
|
-
'
|
4
|
-
'
|
5
|
-
'
|
2
|
+
@CALLBACKS = ['onConsoleMessage',
|
3
|
+
'onLoadFinished', 'onInitialized', 'onLoadStarted',
|
4
|
+
'onResourceRequested', 'onResourceReceived', 'onError',
|
5
|
+
'onNavigationRequested', 'onUrlChanged', 'onPageCreated',
|
6
|
+
'onClosing']
|
6
7
|
|
7
8
|
@DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render',
|
8
9
|
'renderBase64', 'goBack', 'goForward']
|
@@ -78,7 +79,7 @@ class Poltergeist.WebPage
|
|
78
79
|
onResourceRequestedNative: (request, net) ->
|
79
80
|
abort = @urlBlacklist.some (blacklisted_url) ->
|
80
81
|
request.url.indexOf(blacklisted_url) != -1
|
81
|
-
|
82
|
+
|
82
83
|
if abort
|
83
84
|
@_blockedUrls.push request.url unless request.url in @_blockedUrls
|
84
85
|
net.abort()
|
@@ -126,6 +127,15 @@ class Poltergeist.WebPage
|
|
126
127
|
keyCode: (name) ->
|
127
128
|
this.native().event.key[name]
|
128
129
|
|
130
|
+
keyModifierCode: (names) ->
|
131
|
+
modifiers = this.native().event.modifier
|
132
|
+
names = names.split(',').map ((name) -> modifiers[name])
|
133
|
+
names[0] | names[1] # return codes for 1 or 2 modifiers
|
134
|
+
|
135
|
+
keyModifierKeys: (names) ->
|
136
|
+
names.split(',').map (name) =>
|
137
|
+
this.keyCode(name.charAt(0).toUpperCase() + name.substring(1))
|
138
|
+
|
129
139
|
waitState: (state, callback) ->
|
130
140
|
if @state == state
|
131
141
|
callback.call()
|
@@ -4,7 +4,7 @@ module Capybara::Poltergeist
|
|
4
4
|
class Driver < Capybara::Driver::Base
|
5
5
|
DEFAULT_TIMEOUT = 30
|
6
6
|
|
7
|
-
attr_reader :app, :
|
7
|
+
attr_reader :app, :options
|
8
8
|
|
9
9
|
def initialize(app, options = {})
|
10
10
|
@app = app
|
@@ -304,5 +304,51 @@ module Capybara::Poltergeist
|
|
304
304
|
def go_forward
|
305
305
|
browser.go_forward
|
306
306
|
end
|
307
|
+
|
308
|
+
def accept_modal(type, options = {})
|
309
|
+
case type
|
310
|
+
when :confirm
|
311
|
+
browser.accept_confirm
|
312
|
+
when :prompt
|
313
|
+
browser.accept_prompt options[:with]
|
314
|
+
end
|
315
|
+
|
316
|
+
yield if block_given?
|
317
|
+
|
318
|
+
find_modal(options)
|
319
|
+
end
|
320
|
+
|
321
|
+
def dismiss_modal(type, options = {})
|
322
|
+
case type
|
323
|
+
when :confirm
|
324
|
+
browser.dismiss_confirm
|
325
|
+
when :prompt
|
326
|
+
browser.dismiss_prompt
|
327
|
+
end
|
328
|
+
|
329
|
+
yield if block_given?
|
330
|
+
find_modal(options)
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
|
335
|
+
def find_modal(options)
|
336
|
+
start_time = Time.now
|
337
|
+
timeout_sec = options[:wait] || begin Capybara.default_max_wait_time rescue Capybara.default_wait_time end
|
338
|
+
expect_text = options[:text]
|
339
|
+
not_found_msg = 'Unable to find modal dialog'
|
340
|
+
not_found_msg += " with #{expect_text}" if expect_text
|
341
|
+
|
342
|
+
begin
|
343
|
+
modal_text = browser.modal_message
|
344
|
+
raise Capybara::ModalNotFound if modal_text.nil?
|
345
|
+
raise Capybara::ModalNotFound if (expect_text && (modal_text != expect_text))
|
346
|
+
rescue Capybara::ModalNotFound => e
|
347
|
+
raise e, not_found_msg if (Time.now - start_time) >= timeout_sec
|
348
|
+
sleep(0.05)
|
349
|
+
retry
|
350
|
+
end
|
351
|
+
modal_text
|
352
|
+
end
|
307
353
|
end
|
308
354
|
end
|
@@ -132,6 +132,10 @@ module Capybara::Poltergeist
|
|
132
132
|
command :drag, other.id
|
133
133
|
end
|
134
134
|
|
135
|
+
def drag_by(x, y)
|
136
|
+
command :drag_by, x, y
|
137
|
+
end
|
138
|
+
|
135
139
|
def trigger(event)
|
136
140
|
command :trigger, event
|
137
141
|
end
|
@@ -145,6 +149,10 @@ module Capybara::Poltergeist
|
|
145
149
|
end
|
146
150
|
alias_method :send_key, :send_keys
|
147
151
|
|
152
|
+
def path
|
153
|
+
command :path
|
154
|
+
end
|
155
|
+
|
148
156
|
private
|
149
157
|
|
150
158
|
def filter_text(text)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: poltergeist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Leighton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -178,20 +178,6 @@ dependencies:
|
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: 1.0.0
|
181
|
-
- !ruby/object:Gem::Dependency
|
182
|
-
name: rspec-rerun
|
183
|
-
requirement: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - "~>"
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: '0.1'
|
188
|
-
type: :development
|
189
|
-
prerelease: false
|
190
|
-
version_requirements: !ruby/object:Gem::Requirement
|
191
|
-
requirements:
|
192
|
-
- - "~>"
|
193
|
-
- !ruby/object:Gem::Version
|
194
|
-
version: '0.1'
|
195
181
|
description: Poltergeist is a driver for Capybara that allows you to run your tests
|
196
182
|
on a headless WebKit browser, provided by PhantomJS.
|
197
183
|
email:
|
@@ -230,7 +216,7 @@ files:
|
|
230
216
|
- lib/capybara/poltergeist/utility.rb
|
231
217
|
- lib/capybara/poltergeist/version.rb
|
232
218
|
- lib/capybara/poltergeist/web_socket_server.rb
|
233
|
-
homepage:
|
219
|
+
homepage: https://github.com/teampoltergeist/poltergeist
|
234
220
|
licenses:
|
235
221
|
- MIT
|
236
222
|
metadata: {}
|
@@ -250,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
250
236
|
version: '0'
|
251
237
|
requirements: []
|
252
238
|
rubyforge_project:
|
253
|
-
rubygems_version: 2.4.5
|
239
|
+
rubygems_version: 2.4.5.1
|
254
240
|
signing_key:
|
255
241
|
specification_version: 4
|
256
242
|
summary: PhantomJS driver for Capybara
|