poltergeist 1.1.2 → 1.2.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.
@@ -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;