poltergeist 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,10 +19,10 @@ class Poltergeist.Browser
19
19
  @page.setViewportSize(width: @width, height: @height)
20
20
 
21
21
  @page.onLoadStarted = =>
22
- this.setState 'loading' if @state == 'clicked'
22
+ this.setState 'loading' if @state == 'mouse_event'
23
23
 
24
24
  @page.onNavigationRequested = (url, navigation) =>
25
- this.setState 'loading' if @state == 'clicked' && navigation == 'FormSubmitted'
25
+ this.setState 'loading' if @state == 'mouse_event' && navigation == 'FormSubmitted'
26
26
 
27
27
  @page.onLoadFinished = (status) =>
28
28
  if @state == 'loading'
@@ -98,14 +98,20 @@ class Poltergeist.Browser
98
98
  source: ->
99
99
  this.sendResponse @page.source()
100
100
 
101
- find: (selector) ->
102
- this.sendResponse(page_id: @page_id, ids: @page.find(selector))
101
+ title: ->
102
+ this.sendResponse @page.title()
103
103
 
104
- find_within: (page_id, id, selector) ->
105
- this.sendResponse this.node(page_id, id).find(selector)
104
+ find: (method, selector) ->
105
+ this.sendResponse(page_id: @page_id, ids: @page.find(method, selector))
106
106
 
107
- text: (page_id, id) ->
108
- this.sendResponse this.node(page_id, id).text()
107
+ find_within: (page_id, id, method, selector) ->
108
+ this.sendResponse this.node(page_id, id).find(method, selector)
109
+
110
+ all_text: (page_id, id) ->
111
+ this.sendResponse this.node(page_id, id).allText()
112
+
113
+ visible_text: (page_id, id) ->
114
+ this.sendResponse this.node(page_id, id).visibleText()
109
115
 
110
116
  attribute: (page_id, id, name) ->
111
117
  this.sendResponse this.node(page_id, id).getAttribute(name)
@@ -138,6 +144,9 @@ class Poltergeist.Browser
138
144
  visible: (page_id, id) ->
139
145
  this.sendResponse this.node(page_id, id).isVisible()
140
146
 
147
+ disabled: (page_id, id) ->
148
+ this.sendResponse this.node(page_id, id).isDisabled()
149
+
141
150
  evaluate: (script) ->
142
151
  this.sendResponse @page.evaluate("function() { return #{script} }")
143
152
 
@@ -145,16 +154,17 @@ class Poltergeist.Browser
145
154
  @page.execute("function() { #{script} }")
146
155
  this.sendResponse(true)
147
156
 
148
- push_frame: (name) ->
157
+ push_frame: (name, timeout = new Date().getTime() + 2000) ->
149
158
  if @page.pushFrame(name)
150
159
  if @page.currentUrl() == 'about:blank'
151
160
  this.setState 'awaiting_frame_load'
152
161
  else
153
162
  this.sendResponse(true)
154
163
  else
155
- # There's currently no PhantomJS callback available for frame creation,
156
- # so we have to poll
157
- setTimeout((=> this.push_frame(name)), 50)
164
+ if new Date().getTime() < timeout
165
+ setTimeout((=> this.push_frame(name, timeout)), 50)
166
+ else
167
+ @owner.sendError(new Poltergeist.FrameNotFound(name))
158
168
 
159
169
  pop_frame: ->
160
170
  this.sendResponse(@page.popFrame())
@@ -181,24 +191,30 @@ class Poltergeist.Browser
181
191
  @page = prev_page if prev_page
182
192
  this.sendResponse(true)
183
193
 
184
- click: (page_id, id, event = 'click') ->
194
+ mouse_event: (page_id, id, name) ->
185
195
  # Get the node before changing state, in case there is an exception
186
196
  node = this.node(page_id, id)
187
197
 
188
- # If the click event triggers onNavigationRequested, we will transition to the 'loading'
198
+ # If the event triggers onNavigationRequested, we will transition to the 'loading'
189
199
  # state and wait for onLoadFinished before sending a response.
190
- this.setState 'clicked'
200
+ this.setState 'mouse_event'
191
201
 
192
- @last_click = node.click(event)
202
+ @last_mouse_event = node.mouseEvent(name)
193
203
 
194
204
  setTimeout =>
195
205
  if @state != 'loading'
196
206
  this.setState 'default'
197
- this.sendResponse(@last_click)
207
+ this.sendResponse(@last_mouse_event)
198
208
  , 5
199
209
 
210
+ click: (page_id, id) ->
211
+ this.mouse_event page_id, id, 'click'
212
+
200
213
  double_click: (page_id, id) ->
201
- this.click page_id, id, 'doubleclick'
214
+ this.mouse_event page_id, id, 'doubleclick'
215
+
216
+ hover: (page_id, id) ->
217
+ this.mouse_event page_id, id, 'mousemove'
202
218
 
203
219
  click_coordinates: (x, y) ->
204
220
  @page.sendEvent('click', x, y)
@@ -1,18 +1,20 @@
1
1
  var PoltergeistAgent;
2
2
 
3
3
  PoltergeistAgent = (function() {
4
-
5
4
  function PoltergeistAgent() {
6
5
  this.elements = [];
7
6
  this.nodes = {};
8
7
  }
9
8
 
10
9
  PoltergeistAgent.prototype.externalCall = function(name, args) {
10
+ var error;
11
+
11
12
  try {
12
13
  return {
13
14
  value: this[name].apply(this, args)
14
15
  };
15
- } catch (error) {
16
+ } catch (_error) {
17
+ error = _error;
16
18
  return {
17
19
  error: {
18
20
  message: error.toString(),
@@ -23,6 +25,8 @@ PoltergeistAgent = (function() {
23
25
  };
24
26
 
25
27
  PoltergeistAgent.stringify = function(object) {
28
+ var error;
29
+
26
30
  try {
27
31
  return JSON.stringify(object, function(key, value) {
28
32
  if (Array.isArray(this[key])) {
@@ -31,7 +35,8 @@ PoltergeistAgent = (function() {
31
35
  return value;
32
36
  }
33
37
  });
34
- } catch (error) {
38
+ } catch (_error) {
39
+ error = _error;
35
40
  if (error instanceof TypeError) {
36
41
  return '"(cyclic structure)"';
37
42
  } else {
@@ -44,17 +49,32 @@ PoltergeistAgent = (function() {
44
49
  return window.location.toString();
45
50
  };
46
51
 
47
- PoltergeistAgent.prototype.find = function(selector, within) {
48
- var i, ids, results, _i, _ref;
52
+ PoltergeistAgent.prototype.find = function(method, selector, within) {
53
+ var el, i, results, xpath, _i, _len, _results;
54
+
49
55
  if (within == null) {
50
56
  within = document;
51
57
  }
52
- results = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
53
- ids = [];
54
- for (i = _i = 0, _ref = results.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
55
- ids.push(this.register(results.snapshotItem(i)));
58
+ if (method === "xpath") {
59
+ xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
60
+ results = (function() {
61
+ var _i, _ref, _results;
62
+
63
+ _results = [];
64
+ for (i = _i = 0, _ref = xpath.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) {
65
+ _results.push(xpath.snapshotItem(i));
66
+ }
67
+ return _results;
68
+ })();
69
+ } else {
70
+ results = within.querySelectorAll(selector);
71
+ }
72
+ _results = [];
73
+ for (_i = 0, _len = results.length; _i < _len; _i++) {
74
+ el = results[_i];
75
+ _results.push(this.register(el));
56
76
  }
57
- return ids;
77
+ return _results;
58
78
  };
59
79
 
60
80
  PoltergeistAgent.prototype.register = function(element) {
@@ -71,11 +91,13 @@ PoltergeistAgent = (function() {
71
91
 
72
92
  PoltergeistAgent.prototype.get = function(id) {
73
93
  var _base;
94
+
74
95
  return (_base = this.nodes)[id] || (_base[id] = new PoltergeistAgent.Node(this, this.elements[id]));
75
96
  };
76
97
 
77
98
  PoltergeistAgent.prototype.nodeCall = function(id, name, args) {
78
99
  var node;
100
+
79
101
  node = this.get(id);
80
102
  if (node.isObsolete()) {
81
103
  throw new PoltergeistAgent.ObsoleteNode;
@@ -96,7 +118,6 @@ PoltergeistAgent = (function() {
96
118
  })();
97
119
 
98
120
  PoltergeistAgent.ObsoleteNode = (function() {
99
-
100
121
  function ObsoleteNode() {}
101
122
 
102
123
  ObsoleteNode.prototype.toString = function() {
@@ -108,7 +129,6 @@ PoltergeistAgent.ObsoleteNode = (function() {
108
129
  })();
109
130
 
110
131
  PoltergeistAgent.Node = (function() {
111
-
112
132
  Node.EVENTS = {
113
133
  FOCUS: ['blur', 'focus', 'focusin', 'focusout'],
114
134
  MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup']
@@ -123,13 +143,14 @@ PoltergeistAgent.Node = (function() {
123
143
  return this.agent.register(this.element.parentNode);
124
144
  };
125
145
 
126
- Node.prototype.find = function(selector) {
127
- return this.agent.find(selector, this.element);
146
+ Node.prototype.find = function(method, selector) {
147
+ return this.agent.find(method, selector, this.element);
128
148
  };
129
149
 
130
150
  Node.prototype.isObsolete = function() {
131
151
  var obsolete,
132
152
  _this = this;
153
+
133
154
  obsolete = function(element) {
134
155
  if (element.parentNode != null) {
135
156
  if (element.parentNode === document) {
@@ -146,17 +167,57 @@ PoltergeistAgent.Node = (function() {
146
167
 
147
168
  Node.prototype.changed = function() {
148
169
  var event;
170
+
149
171
  event = document.createEvent('HTMLEvents');
150
172
  event.initEvent('change', true, false);
151
173
  return this.element.dispatchEvent(event);
152
174
  };
153
175
 
176
+ Node.prototype.input = function() {
177
+ var event;
178
+
179
+ event = document.createEvent('HTMLEvents');
180
+ event.initEvent('input', true, false);
181
+ return this.element.dispatchEvent(event);
182
+ };
183
+
184
+ Node.prototype.keyupdowned = function(eventName, keyCode) {
185
+ var event;
186
+
187
+ event = document.createEvent('UIEvents');
188
+ event.initEvent(eventName, true, true);
189
+ event.keyCode = keyCode;
190
+ event.which = keyCode;
191
+ event.charCode = 0;
192
+ return this.element.dispatchEvent(event);
193
+ };
194
+
195
+ Node.prototype.keypressed = function(altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
196
+ var event;
197
+
198
+ event = document.createEvent('UIEvents');
199
+ event.initEvent('keypress', true, true);
200
+ event.window = this.agent.window;
201
+ event.altKey = altKey;
202
+ event.ctrlKey = ctrlKey;
203
+ event.shiftKey = shiftKey;
204
+ event.metaKey = metaKey;
205
+ event.keyCode = keyCode;
206
+ event.charCode = charCode;
207
+ event.which = keyCode;
208
+ return this.element.dispatchEvent(event);
209
+ };
210
+
154
211
  Node.prototype.insideBody = function() {
155
212
  return this.element === document.body || document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
156
213
  };
157
214
 
158
- Node.prototype.text = function() {
159
- if (this.element.tagName === 'TEXTAREA') {
215
+ Node.prototype.allText = function() {
216
+ return this.element.textContent;
217
+ };
218
+
219
+ Node.prototype.visibleText = function() {
220
+ if (this.element.nodeName === "TEXTAREA") {
160
221
  return this.element.textContent;
161
222
  } else {
162
223
  return this.element.innerText;
@@ -177,6 +238,7 @@ PoltergeistAgent.Node = (function() {
177
238
 
178
239
  Node.prototype.value = function() {
179
240
  var option, _i, _len, _ref, _results;
241
+
180
242
  if (this.element.tagName === 'SELECT' && this.element.multiple) {
181
243
  _ref = this.element.children;
182
244
  _results = [];
@@ -192,6 +254,30 @@ PoltergeistAgent.Node = (function() {
192
254
  }
193
255
  };
194
256
 
257
+ Node.prototype.set = function(value) {
258
+ var char, keyCode, _i, _len;
259
+
260
+ if (this.element.readOnly) {
261
+ return;
262
+ }
263
+ if (this.element.maxLength >= 0) {
264
+ value = value.substr(0, this.element.maxLength);
265
+ }
266
+ this.element.value = '';
267
+ this.trigger('focus');
268
+ for (_i = 0, _len = value.length; _i < _len; _i++) {
269
+ char = value[_i];
270
+ keyCode = this.characterToKeyCode(char);
271
+ this.keyupdowned('keydown', keyCode);
272
+ this.element.value += char;
273
+ this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
274
+ this.keyupdowned('keyup', keyCode);
275
+ }
276
+ this.changed();
277
+ this.input();
278
+ return this.trigger('blur');
279
+ };
280
+
195
281
  Node.prototype.isMultiple = function() {
196
282
  return this.element.multiple;
197
283
  };
@@ -231,8 +317,13 @@ PoltergeistAgent.Node = (function() {
231
317
  }
232
318
  };
233
319
 
320
+ Node.prototype.isDisabled = function() {
321
+ return this.element.disabled || this.element.tagName === 'OPTION' && this.element.parentNode.disabled;
322
+ };
323
+
234
324
  Node.prototype.frameOffset = function() {
235
325
  var offset, rect, win;
326
+
236
327
  win = window;
237
328
  offset = {
238
329
  top: 0,
@@ -249,6 +340,7 @@ PoltergeistAgent.Node = (function() {
249
340
 
250
341
  Node.prototype.position = function() {
251
342
  var frameOffset, pos, rect;
343
+
252
344
  rect = this.element.getClientRects()[0];
253
345
  if (!rect) {
254
346
  throw new PoltergeistAgent.ObsoleteNode;
@@ -267,6 +359,7 @@ PoltergeistAgent.Node = (function() {
267
359
 
268
360
  Node.prototype.trigger = function(name) {
269
361
  var event;
362
+
270
363
  if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
271
364
  event = document.createEvent('MouseEvent');
272
365
  event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
@@ -279,17 +372,9 @@ PoltergeistAgent.Node = (function() {
279
372
  return this.element.dispatchEvent(event);
280
373
  };
281
374
 
282
- Node.prototype.focusAndHighlight = function() {
283
- this.element.focus();
284
- return this.element.select();
285
- };
286
-
287
- Node.prototype.blur = function() {
288
- return this.element.blur();
289
- };
290
-
291
- Node.prototype.clickTest = function(x, y) {
375
+ Node.prototype.mouseEventTest = function(x, y) {
292
376
  var el, frameOffset, origEl;
377
+
293
378
  frameOffset = this.frameOffset();
294
379
  x -= frameOffset.left;
295
380
  y -= frameOffset.top;
@@ -311,6 +396,7 @@ PoltergeistAgent.Node = (function() {
311
396
 
312
397
  Node.prototype.getSelector = function(el) {
313
398
  var className, selector, _i, _len, _ref;
399
+
314
400
  selector = el.tagName !== 'HTML' ? this.getSelector(el.parentNode) + ' ' : '';
315
401
  selector += el.tagName.toLowerCase();
316
402
  if (el.id) {
@@ -324,6 +410,48 @@ PoltergeistAgent.Node = (function() {
324
410
  return selector;
325
411
  };
326
412
 
413
+ Node.prototype.characterToKeyCode = function(character) {
414
+ var code, specialKeys;
415
+
416
+ code = character.toUpperCase().charCodeAt(0);
417
+ specialKeys = {
418
+ 96: 192,
419
+ 45: 189,
420
+ 61: 187,
421
+ 91: 219,
422
+ 93: 221,
423
+ 92: 220,
424
+ 59: 186,
425
+ 39: 222,
426
+ 44: 188,
427
+ 46: 190,
428
+ 47: 191,
429
+ 127: 46,
430
+ 126: 192,
431
+ 33: 49,
432
+ 64: 50,
433
+ 35: 51,
434
+ 36: 52,
435
+ 37: 53,
436
+ 94: 54,
437
+ 38: 55,
438
+ 42: 56,
439
+ 40: 57,
440
+ 41: 48,
441
+ 95: 189,
442
+ 43: 187,
443
+ 123: 219,
444
+ 125: 221,
445
+ 124: 220,
446
+ 58: 186,
447
+ 34: 222,
448
+ 60: 188,
449
+ 62: 190,
450
+ 63: 191
451
+ };
452
+ return specialKeys[code] || code;
453
+ };
454
+
327
455
  Node.prototype.isDOMEqual = function(other_id) {
328
456
  return this.element === this.agent.get(other_id).element;
329
457
  };
@@ -1,6 +1,4 @@
1
-
2
1
  Poltergeist.Browser = (function() {
3
-
4
2
  function Browser(owner, width, height) {
5
3
  this.owner = owner;
6
4
  this.width = width || 1024;
@@ -15,6 +13,7 @@ Poltergeist.Browser = (function() {
15
13
 
16
14
  Browser.prototype.resetPage = function() {
17
15
  var _this = this;
16
+
18
17
  if (this.page != null) {
19
18
  this.page.release();
20
19
  phantom.clearCookies();
@@ -25,12 +24,12 @@ Poltergeist.Browser = (function() {
25
24
  height: this.height
26
25
  });
27
26
  this.page.onLoadStarted = function() {
28
- if (_this.state === 'clicked') {
27
+ if (_this.state === 'mouse_event') {
29
28
  return _this.setState('loading');
30
29
  }
31
30
  };
32
31
  this.page.onNavigationRequested = function(url, navigation) {
33
- if (_this.state === 'clicked' && navigation === 'FormSubmitted') {
32
+ if (_this.state === 'mouse_event' && navigation === 'FormSubmitted') {
34
33
  return _this.setState('loading');
35
34
  }
36
35
  };
@@ -51,6 +50,7 @@ Poltergeist.Browser = (function() {
51
50
  };
52
51
  return this.page.onPageCreated = function(sub_page) {
53
52
  var name;
53
+
54
54
  if (_this.state === 'awaiting_sub_page') {
55
55
  name = _this.page_name;
56
56
  _this.page_name = null;
@@ -75,6 +75,7 @@ Poltergeist.Browser = (function() {
75
75
 
76
76
  Browser.prototype.sendResponse = function(response) {
77
77
  var errors;
78
+
78
79
  errors = this.page.errors();
79
80
  this.page.clearErrors();
80
81
  if (errors.length > 0 && this.js_errors) {
@@ -99,6 +100,7 @@ Poltergeist.Browser = (function() {
99
100
 
100
101
  Browser.prototype.visit = function(url) {
101
102
  var prev_url;
103
+
102
104
  this.setState('loading');
103
105
  prev_url = this.page.currentUrl();
104
106
  this.page.open(url);
@@ -124,19 +126,27 @@ Poltergeist.Browser = (function() {
124
126
  return this.sendResponse(this.page.source());
125
127
  };
126
128
 
127
- Browser.prototype.find = function(selector) {
129
+ Browser.prototype.title = function() {
130
+ return this.sendResponse(this.page.title());
131
+ };
132
+
133
+ Browser.prototype.find = function(method, selector) {
128
134
  return this.sendResponse({
129
135
  page_id: this.page_id,
130
- ids: this.page.find(selector)
136
+ ids: this.page.find(method, selector)
131
137
  });
132
138
  };
133
139
 
134
- Browser.prototype.find_within = function(page_id, id, selector) {
135
- return this.sendResponse(this.node(page_id, id).find(selector));
140
+ Browser.prototype.find_within = function(page_id, id, method, selector) {
141
+ return this.sendResponse(this.node(page_id, id).find(method, selector));
142
+ };
143
+
144
+ Browser.prototype.all_text = function(page_id, id) {
145
+ return this.sendResponse(this.node(page_id, id).allText());
136
146
  };
137
147
 
138
- Browser.prototype.text = function(page_id, id) {
139
- return this.sendResponse(this.node(page_id, id).text());
148
+ Browser.prototype.visible_text = function(page_id, id) {
149
+ return this.sendResponse(this.node(page_id, id).visibleText());
140
150
  };
141
151
 
142
152
  Browser.prototype.attribute = function(page_id, id, name) {
@@ -154,6 +164,7 @@ Poltergeist.Browser = (function() {
154
164
 
155
165
  Browser.prototype.select_file = function(page_id, id, value) {
156
166
  var node;
167
+
157
168
  node = this.node(page_id, id);
158
169
  this.page.beforeUpload(node.id);
159
170
  this.page.uploadFile('[_poltergeist_selected]', value);
@@ -173,6 +184,10 @@ Poltergeist.Browser = (function() {
173
184
  return this.sendResponse(this.node(page_id, id).isVisible());
174
185
  };
175
186
 
187
+ Browser.prototype.disabled = function(page_id, id) {
188
+ return this.sendResponse(this.node(page_id, id).isDisabled());
189
+ };
190
+
176
191
  Browser.prototype.evaluate = function(script) {
177
192
  return this.sendResponse(this.page.evaluate("function() { return " + script + " }"));
178
193
  };
@@ -182,8 +197,12 @@ Poltergeist.Browser = (function() {
182
197
  return this.sendResponse(true);
183
198
  };
184
199
 
185
- Browser.prototype.push_frame = function(name) {
200
+ Browser.prototype.push_frame = function(name, timeout) {
186
201
  var _this = this;
202
+
203
+ if (timeout == null) {
204
+ timeout = new Date().getTime() + 2000;
205
+ }
187
206
  if (this.page.pushFrame(name)) {
188
207
  if (this.page.currentUrl() === 'about:blank') {
189
208
  return this.setState('awaiting_frame_load');
@@ -191,9 +210,13 @@ Poltergeist.Browser = (function() {
191
210
  return this.sendResponse(true);
192
211
  }
193
212
  } else {
194
- return setTimeout((function() {
195
- return _this.push_frame(name);
196
- }), 50);
213
+ if (new Date().getTime() < timeout) {
214
+ return setTimeout((function() {
215
+ return _this.push_frame(name, timeout);
216
+ }), 50);
217
+ } else {
218
+ return this.owner.sendError(new Poltergeist.FrameNotFound(name));
219
+ }
197
220
  }
198
221
  };
199
222
 
@@ -204,6 +227,7 @@ Poltergeist.Browser = (function() {
204
227
  Browser.prototype.push_window = function(name) {
205
228
  var sub_page,
206
229
  _this = this;
230
+
207
231
  sub_page = this.page.getPage(name);
208
232
  if (sub_page) {
209
233
  if (sub_page.currentUrl() === 'about:blank') {
@@ -225,6 +249,7 @@ Poltergeist.Browser = (function() {
225
249
 
226
250
  Browser.prototype.pop_window = function() {
227
251
  var prev_page;
252
+
228
253
  prev_page = this.page_stack.pop();
229
254
  if (prev_page) {
230
255
  this.page = prev_page;
@@ -232,25 +257,31 @@ Poltergeist.Browser = (function() {
232
257
  return this.sendResponse(true);
233
258
  };
234
259
 
235
- Browser.prototype.click = function(page_id, id, event) {
260
+ Browser.prototype.mouse_event = function(page_id, id, name) {
236
261
  var node,
237
262
  _this = this;
238
- if (event == null) {
239
- event = 'click';
240
- }
263
+
241
264
  node = this.node(page_id, id);
242
- this.setState('clicked');
243
- this.last_click = node.click(event);
265
+ this.setState('mouse_event');
266
+ this.last_mouse_event = node.mouseEvent(name);
244
267
  return setTimeout(function() {
245
268
  if (_this.state !== 'loading') {
246
269
  _this.setState('default');
247
- return _this.sendResponse(_this.last_click);
270
+ return _this.sendResponse(_this.last_mouse_event);
248
271
  }
249
272
  }, 5);
250
273
  };
251
274
 
275
+ Browser.prototype.click = function(page_id, id) {
276
+ return this.mouse_event(page_id, id, 'click');
277
+ };
278
+
252
279
  Browser.prototype.double_click = function(page_id, id) {
253
- return this.click(page_id, id, 'doubleclick');
280
+ return this.mouse_event(page_id, id, 'doubleclick');
281
+ };
282
+
283
+ Browser.prototype.hover = function(page_id, id) {
284
+ return this.mouse_event(page_id, id, 'mousemove');
254
285
  };
255
286
 
256
287
  Browser.prototype.click_coordinates = function(x, y) {
@@ -284,6 +315,7 @@ Poltergeist.Browser = (function() {
284
315
 
285
316
  Browser.prototype.render = function(path, full) {
286
317
  var dimensions, document, viewport;
318
+
287
319
  dimensions = this.page.validatedDimensions();
288
320
  document = dimensions.document;
289
321
  viewport = dimensions.viewport;