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