poltergeist 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 804da55f4a9b382633c1ea2388078a0a464dcf20
4
- data.tar.gz: 50f49ce4999e47f266a048ada3a4edb453aef88d
3
+ metadata.gz: 440dc77fc5ba49d78d8e7af8134da82b93a500cd
4
+ data.tar.gz: afc6fba6b9d6be96866c9810cb1b9a729dddc4a8
5
5
  SHA512:
6
- metadata.gz: 6129b9fb4c4cd02030a40f4d1afe79a4fb8dbb4244c0bb93b00bfd6587dccb93ece045c089c6731e2538c8abd2ea73ceb18c7aa9fe34a883112a5682493caabe
7
- data.tar.gz: 9abd3b5839e1342411eb869c83917fe1a45341278ec663d870274d9f5ca501f897b1b49505367d5f3601cd80eb0e456999b1cfec49f2c96428477f81e7eae8f7
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://www.phantomjs.org/).
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.6.0).](https://github.com/teampoltergeist/poltergeist/tree/v1.6.0)**
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.7-macosx.zip)
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.7-linux-i686.tar.bz2)
51
- or [64 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.7-linux-x86_64.tar.bz2)
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.7-windows.zip)
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.7-source.zip)
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 Poltegeist via the following code in your
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-2014 Jonathan Leighton
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
- # String itself with modifiers like :alt, :shift, etc
358
- raise Error, 'PhantomJS behaviour for key modifiers is currently ' \
359
- 'broken, we will add this in later versions'
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
- encodeURI(decodeURI(window.location.href))
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
- @element.dispatchEvent(event)
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 value == false && !@element.parentNode.multiple
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
- @element.dispatchEvent(event)
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
- @currentPage.sendEvent('keypress', key)
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 encodeURI(decodeURI(window.location.href));
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
- return this.element.dispatchEvent(event);
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 (value === false && !this.element.parentNode.multiple) {
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 this.element.dispatchEvent(event);
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
- this.currentPage.sendEvent('keypress', key);
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 = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing'];
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 = ['onAlert', 'onConsoleMessage', 'onLoadFinished',
3
- 'onInitialized', 'onLoadStarted', 'onResourceRequested',
4
- 'onResourceReceived', 'onError', 'onNavigationRequested',
5
- 'onUrlChanged', 'onPageCreated', 'onClosing']
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, :server, :client, :browser, :options
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)
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
2
  module Poltergeist
3
- VERSION = "1.6.0"
3
+ VERSION = "1.7.0"
4
4
  end
5
5
  end
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.6.0
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-02-05 00:00:00.000000000 Z
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: http://github.com/teampoltergeist/poltergeist
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