poltergeist 0.7.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,6 +6,7 @@ Poltergeist.Browser = (function() {
6
6
  this.width = width || 1024;
7
7
  this.height = height || 768;
8
8
  this.state = 'default';
9
+ this.page_stack = [];
9
10
  this.page_id = 0;
10
11
  this.resetPage();
11
12
  }
@@ -14,8 +15,13 @@ Poltergeist.Browser = (function() {
14
15
  var _this = this;
15
16
  if (this.page != null) {
16
17
  this.page.release();
18
+ phantom.clearCookies();
17
19
  }
18
- this.page = new Poltergeist.WebPage(this.width, this.height);
20
+ this.page = new Poltergeist.WebPage;
21
+ this.page.setViewportSize({
22
+ width: this.width,
23
+ height: this.height
24
+ });
19
25
  this.page.onLoadStarted = function() {
20
26
  if (_this.state === 'clicked') {
21
27
  return _this.state = 'loading';
@@ -28,13 +34,30 @@ Poltergeist.Browser = (function() {
28
34
  };
29
35
  this.page.onLoadFinished = function(status) {
30
36
  if (_this.state === 'loading') {
31
- _this.sendResponse(status);
37
+ _this.sendResponse({
38
+ status: status,
39
+ click: _this.last_click
40
+ });
41
+ return _this.state = 'default';
42
+ } else if (_this.state === 'awaiting_frame_load') {
43
+ _this.sendResponse(true);
32
44
  return _this.state = 'default';
33
45
  }
34
46
  };
35
- return this.page.onInitialized = function() {
47
+ this.page.onInitialized = function() {
36
48
  return _this.page_id += 1;
37
49
  };
50
+ return this.page.onPageCreated = function(sub_page) {
51
+ var name;
52
+ if (_this.state === 'awaiting_sub_page') {
53
+ name = _this.page_name;
54
+ _this.state = 'default';
55
+ _this.page_name = null;
56
+ return setTimeout((function() {
57
+ return _this.push_window(name);
58
+ }), 0);
59
+ }
60
+ };
38
61
  };
39
62
 
40
63
  Browser.prototype.sendResponse = function(response) {
@@ -42,7 +65,7 @@ Poltergeist.Browser = (function() {
42
65
  errors = this.page.errors();
43
66
  if (errors.length > 0) {
44
67
  this.page.clearErrors();
45
- throw new Poltergeist.JavascriptError(errors);
68
+ return this.owner.sendError(new Poltergeist.JavascriptError(errors));
46
69
  } else {
47
70
  return this.owner.sendResponse(response);
48
71
  }
@@ -56,12 +79,15 @@ Poltergeist.Browser = (function() {
56
79
  }
57
80
  };
58
81
 
59
- Browser.prototype.visit = function(url, headers) {
82
+ Browser.prototype.visit = function(url) {
83
+ var prev_url;
60
84
  this.state = 'loading';
61
- return this.page.open(url, {
62
- operation: "get",
63
- headers: headers
64
- });
85
+ prev_url = this.page.currentUrl();
86
+ this.page.open(url);
87
+ if (/#/.test(url) && prev_url.split('#')[0] === url.split('#')[0]) {
88
+ this.state = 'default';
89
+ return this.sendResponse('success');
90
+ }
65
91
  };
66
92
 
67
93
  Browser.prototype.current_url = function() {
@@ -138,25 +164,68 @@ Poltergeist.Browser = (function() {
138
164
  return this.sendResponse(true);
139
165
  };
140
166
 
141
- Browser.prototype.push_frame = function(id) {
142
- this.page.pushFrame(id);
143
- return this.sendResponse(true);
167
+ Browser.prototype.push_frame = function(name) {
168
+ var _this = this;
169
+ if (this.page.pushFrame(name)) {
170
+ if (this.page.currentUrl() === 'about:blank') {
171
+ return this.state = 'awaiting_frame_load';
172
+ } else {
173
+ return this.sendResponse(true);
174
+ }
175
+ } else {
176
+ return setTimeout((function() {
177
+ return _this.push_frame(name);
178
+ }), 50);
179
+ }
144
180
  };
145
181
 
146
182
  Browser.prototype.pop_frame = function() {
147
- this.page.popFrame();
183
+ return this.sendResponse(this.page.popFrame());
184
+ };
185
+
186
+ Browser.prototype.push_window = function(name) {
187
+ var sub_page,
188
+ _this = this;
189
+ sub_page = this.page.getPage(name);
190
+ if (sub_page) {
191
+ if (sub_page.currentUrl() === 'about:blank') {
192
+ return sub_page.onLoadFinished = function() {
193
+ sub_page.onLoadFinished = null;
194
+ return _this.push_window(name);
195
+ };
196
+ } else {
197
+ this.page_stack.push(this.page);
198
+ this.page = sub_page;
199
+ this.page_id += 1;
200
+ return this.sendResponse(true);
201
+ }
202
+ } else {
203
+ this.page_name = name;
204
+ return this.state = 'awaiting_sub_page';
205
+ }
206
+ };
207
+
208
+ Browser.prototype.pop_window = function() {
209
+ var prev_page;
210
+ prev_page = this.page_stack.pop();
211
+ if (prev_page) {
212
+ this.page = prev_page;
213
+ }
148
214
  return this.sendResponse(true);
149
215
  };
150
216
 
151
217
  Browser.prototype.click = function(page_id, id) {
152
- var node;
218
+ var node,
219
+ _this = this;
153
220
  node = this.node(page_id, id);
154
221
  this.state = 'clicked';
155
- node.click();
156
- if (this.state !== 'loading') {
157
- this.state = 'default';
158
- return this.sendResponse(true);
159
- }
222
+ this.last_click = node.click();
223
+ return setTimeout(function() {
224
+ if (_this.state !== 'loading') {
225
+ _this.state = 'default';
226
+ return _this.sendResponse(_this.last_click);
227
+ }
228
+ }, 5);
160
229
  };
161
230
 
162
231
  Browser.prototype.drag = function(page_id, id, other_id) {
@@ -223,6 +292,32 @@ Poltergeist.Browser = (function() {
223
292
  return this.sendResponse(this.page.networkTraffic());
224
293
  };
225
294
 
295
+ Browser.prototype.set_headers = function(headers) {
296
+ if (headers['User-Agent']) {
297
+ this.page.setUserAgent(headers['User-Agent']);
298
+ }
299
+ this.page.setCustomHeaders(headers);
300
+ return this.sendResponse(true);
301
+ };
302
+
303
+ Browser.prototype.response_headers = function() {
304
+ return this.sendResponse(this.page.responseHeaders());
305
+ };
306
+
307
+ Browser.prototype.cookies = function() {
308
+ return this.sendResponse(this.page.cookies());
309
+ };
310
+
311
+ Browser.prototype.set_cookie = function(cookie) {
312
+ phantom.addCookie(cookie);
313
+ return this.sendResponse(true);
314
+ };
315
+
316
+ Browser.prototype.remove_cookie = function(name) {
317
+ this.page.deleteCookie(name);
318
+ return this.sendResponse(true);
319
+ };
320
+
226
321
  Browser.prototype.exit = function() {
227
322
  return phantom.exit();
228
323
  };
@@ -4,7 +4,7 @@ Poltergeist.Node = (function() {
4
4
  var name, _fn, _i, _len, _ref,
5
5
  _this = this;
6
6
 
7
- Node.DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'clickTest', 'scrollIntoView', 'isDOMEqual'];
7
+ Node.DELEGATES = ['text', 'getAttribute', 'value', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'clickTest', 'scrollIntoView', 'isDOMEqual', 'focusAndHighlight', 'blur'];
8
8
 
9
9
  function Node(page, id) {
10
10
  this.page = page;
@@ -47,7 +47,8 @@ Poltergeist.Node = (function() {
47
47
  pos = this.clickPosition();
48
48
  test = this.clickTest(pos.x, pos.y);
49
49
  if (test.status === 'success') {
50
- return this.page.sendEvent('click', pos.x, pos.y);
50
+ this.page.mouseEvent('click', pos.x, pos.y);
51
+ return pos;
51
52
  } else {
52
53
  throw new Poltergeist.ClickFailed(test.selector, pos);
53
54
  }
@@ -58,15 +59,20 @@ Poltergeist.Node = (function() {
58
59
  this.scrollIntoView();
59
60
  position = this.clickPosition();
60
61
  otherPosition = other.clickPosition();
61
- this.page.sendEvent('mousedown', position.x, position.y);
62
- this.page.sendEvent('mousemove', otherPosition.x, otherPosition.y);
63
- return this.page.sendEvent('mouseup', otherPosition.x, otherPosition.y);
62
+ this.page.mouseEvent('mousedown', position.x, position.y);
63
+ return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y);
64
64
  };
65
65
 
66
66
  Node.prototype.isEqual = function(other) {
67
67
  return this.page === other.page && this.isDOMEqual(other.id);
68
68
  };
69
69
 
70
+ Node.prototype.set = function(value) {
71
+ this.focusAndHighlight();
72
+ this.page.sendEvent('keypress', value.toString());
73
+ return this.blur();
74
+ };
75
+
70
76
  return Node;
71
77
 
72
78
  }).call(this);
@@ -4,22 +4,21 @@ Poltergeist.WebPage = (function() {
4
4
  var command, delegate, _fn, _fn1, _i, _j, _len, _len1, _ref, _ref1,
5
5
  _this = this;
6
6
 
7
- WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged'];
7
+ WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated'];
8
8
 
9
9
  WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render'];
10
10
 
11
- WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize'];
11
+ WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize'];
12
12
 
13
- function WebPage(width, height) {
13
+ function WebPage(_native) {
14
14
  var callback, _i, _len, _ref;
15
- this["native"] = require('webpage').create();
15
+ this["native"] = _native;
16
+ this["native"] || (this["native"] = require('webpage').create());
16
17
  this._source = "";
17
18
  this._errors = [];
18
19
  this._networkTraffic = {};
19
- this.setViewportSize({
20
- width: width,
21
- height: height
22
- });
20
+ this.frames = [];
21
+ this.sub_pages = {};
23
22
  _ref = WebPage.CALLBACKS;
24
23
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
25
24
  callback = _ref[_i];
@@ -65,8 +64,7 @@ Poltergeist.WebPage = (function() {
65
64
  if (this["native"].evaluate(function() {
66
65
  return typeof __poltergeist;
67
66
  }) === "undefined") {
68
- this["native"].injectJs("" + phantom.libraryPath + "/agent.js");
69
- return this.nodes = {};
67
+ return this["native"].injectJs("" + phantom.libraryPath + "/agent.js");
70
68
  }
71
69
  };
72
70
 
@@ -107,6 +105,10 @@ Poltergeist.WebPage = (function() {
107
105
 
108
106
  WebPage.prototype.onResourceRequestedNative = function(request) {
109
107
  this.lastRequestId = request.id;
108
+ if (request.url === this.redirectURL) {
109
+ this.redirectURL = null;
110
+ this.requestId = request.id;
111
+ }
110
112
  return this._networkTraffic[request.id] = {
111
113
  request: request,
112
114
  responseParts: []
@@ -114,12 +116,16 @@ Poltergeist.WebPage = (function() {
114
116
  };
115
117
 
116
118
  WebPage.prototype.onResourceReceivedNative = function(response) {
117
- this._networkTraffic[response.id].responseParts.push(response);
119
+ var _ref2;
120
+ if ((_ref2 = this._networkTraffic[response.id]) != null) {
121
+ _ref2.responseParts.push(response);
122
+ }
118
123
  if (this.requestId === response.id) {
119
124
  if (response.redirectURL) {
120
- return this.requestId = response.id;
125
+ return this.redirectURL = response.redirectURL;
121
126
  } else {
122
- return this._statusCode = response.status;
127
+ this._statusCode = response.status;
128
+ return this._responseHeaders = response.headers;
123
129
  }
124
130
  }
125
131
  };
@@ -129,7 +135,7 @@ Poltergeist.WebPage = (function() {
129
135
  };
130
136
 
131
137
  WebPage.prototype.content = function() {
132
- return this["native"].content;
138
+ return this["native"].frameContent;
133
139
  };
134
140
 
135
141
  WebPage.prototype.source = function() {
@@ -148,6 +154,23 @@ Poltergeist.WebPage = (function() {
148
154
  return this._statusCode;
149
155
  };
150
156
 
157
+ WebPage.prototype.responseHeaders = function() {
158
+ var headers;
159
+ headers = {};
160
+ this._responseHeaders.forEach(function(item) {
161
+ return headers[item.name] = item.value;
162
+ });
163
+ return headers;
164
+ };
165
+
166
+ WebPage.prototype.cookies = function() {
167
+ return this["native"].cookies;
168
+ };
169
+
170
+ WebPage.prototype.deleteCookie = function(name) {
171
+ return this["native"].deleteCookie(name);
172
+ };
173
+
151
174
  WebPage.prototype.viewportSize = function() {
152
175
  return this["native"].viewportSize;
153
176
  };
@@ -172,6 +195,43 @@ Poltergeist.WebPage = (function() {
172
195
  return this["native"].clipRect = rect;
173
196
  };
174
197
 
198
+ WebPage.prototype.setUserAgent = function(userAgent) {
199
+ return this["native"].settings.userAgent = userAgent;
200
+ };
201
+
202
+ WebPage.prototype.setCustomHeaders = function(headers) {
203
+ return this["native"].customHeaders = headers;
204
+ };
205
+
206
+ WebPage.prototype.pushFrame = function(name) {
207
+ var res;
208
+ this.frames.push(name);
209
+ res = this["native"].switchToFrame(name);
210
+ this.injectAgent();
211
+ return res;
212
+ };
213
+
214
+ WebPage.prototype.popFrame = function() {
215
+ this.frames.pop();
216
+ if (this.frames.length === 0) {
217
+ return this["native"].switchToMainFrame();
218
+ } else {
219
+ return this["native"].switchToFrame(this.frames[this.frames.length - 1]);
220
+ }
221
+ };
222
+
223
+ WebPage.prototype.getPage = function(name) {
224
+ var page;
225
+ if (this.sub_pages[name]) {
226
+ return this.sub_pages[name];
227
+ } else {
228
+ page = this["native"].getPage(name);
229
+ if (page) {
230
+ return this.sub_pages[name] = new Poltergeist.WebPage(page);
231
+ }
232
+ }
233
+ };
234
+
175
235
  WebPage.prototype.dimensions = function() {
176
236
  var scroll, viewport;
177
237
  scroll = this.scrollPosition();
@@ -206,8 +266,12 @@ Poltergeist.WebPage = (function() {
206
266
  };
207
267
 
208
268
  WebPage.prototype.get = function(id) {
209
- var _base;
210
- return (_base = this.nodes)[id] || (_base[id] = new Poltergeist.Node(this, id));
269
+ return new Poltergeist.Node(this, id);
270
+ };
271
+
272
+ WebPage.prototype.mouseEvent = function(name, x, y) {
273
+ this.sendEvent('mousemove', x, y);
274
+ return this.sendEvent(name, x, y);
211
275
  };
212
276
 
213
277
  WebPage.prototype.evaluate = function() {
@@ -1,10 +1,10 @@
1
1
  # Proxy object for forwarding method calls to the node object inside the page.
2
2
 
3
3
  class Poltergeist.Node
4
- @DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete',
4
+ @DELEGATES = ['text', 'getAttribute', 'value', 'setAttribute', 'isObsolete',
5
5
  'removeAttribute', 'isMultiple', 'select', 'tagName', 'find',
6
6
  'isVisible', 'position', 'trigger', 'parentId', 'clickTest',
7
- 'scrollIntoView', 'isDOMEqual']
7
+ 'scrollIntoView', 'isDOMEqual', 'focusAndHighlight', 'blur']
8
8
 
9
9
  constructor: (@page, @id) ->
10
10
 
@@ -35,7 +35,8 @@ class Poltergeist.Node
35
35
  test = this.clickTest(pos.x, pos.y)
36
36
 
37
37
  if test.status == 'success'
38
- @page.sendEvent('click', pos.x, pos.y)
38
+ @page.mouseEvent('click', pos.x, pos.y)
39
+ pos
39
40
  else
40
41
  throw new Poltergeist.ClickFailed(test.selector, pos)
41
42
 
@@ -45,9 +46,13 @@ class Poltergeist.Node
45
46
  position = this.clickPosition()
46
47
  otherPosition = other.clickPosition()
47
48
 
48
- @page.sendEvent('mousedown', position.x, position.y)
49
- @page.sendEvent('mousemove', otherPosition.x, otherPosition.y)
50
- @page.sendEvent('mouseup', otherPosition.x, otherPosition.y)
49
+ @page.mouseEvent('mousedown', position.x, position.y)
50
+ @page.mouseEvent('mouseup', otherPosition.x, otherPosition.y)
51
51
 
52
52
  isEqual: (other) ->
53
53
  @page == other.page && this.isDOMEqual(other.id)
54
+
55
+ set: (value) ->
56
+ this.focusAndHighlight()
57
+ @page.sendEvent('keypress', value.toString())
58
+ this.blur()
@@ -1,19 +1,20 @@
1
1
  class Poltergeist.WebPage
2
2
  @CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized',
3
3
  'onLoadStarted', 'onResourceRequested', 'onResourceReceived',
4
- 'onError', 'onNavigationRequested', 'onUrlChanged']
4
+ 'onError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated']
5
5
 
6
6
  @DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render']
7
7
 
8
- @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize']
8
+ @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize']
9
+
10
+ constructor: (@native) ->
11
+ @native or= require('webpage').create()
9
12
 
10
- constructor: (width, height) ->
11
- @native = require('webpage').create()
12
13
  @_source = ""
13
14
  @_errors = []
14
15
  @_networkTraffic = {}
15
-
16
- this.setViewportSize(width: width, height: height)
16
+ @frames = []
17
+ @sub_pages = {}
17
18
 
18
19
  for callback in WebPage.CALLBACKS
19
20
  this.bindCallback(callback)
@@ -38,7 +39,6 @@ class Poltergeist.WebPage
38
39
  injectAgent: ->
39
40
  if @native.evaluate(-> typeof __poltergeist) == "undefined"
40
41
  @native.injectJs("#{phantom.libraryPath}/agent.js")
41
- @nodes = {}
42
42
 
43
43
  onConsoleMessageNative: (message) ->
44
44
  if message == '__DOMContentLoaded'
@@ -67,25 +67,30 @@ class Poltergeist.WebPage
67
67
  onResourceRequestedNative: (request) ->
68
68
  @lastRequestId = request.id
69
69
 
70
+ if request.url == @redirectURL
71
+ @redirectURL = null
72
+ @requestId = request.id
73
+
70
74
  @_networkTraffic[request.id] = {
71
75
  request: request,
72
76
  responseParts: []
73
77
  }
74
78
 
75
79
  onResourceReceivedNative: (response) ->
76
- @_networkTraffic[response.id].responseParts.push(response)
80
+ @_networkTraffic[response.id]?.responseParts.push(response)
77
81
 
78
82
  if @requestId == response.id
79
83
  if response.redirectURL
80
- @requestId = response.id
84
+ @redirectURL = response.redirectURL
81
85
  else
82
- @_statusCode = response.status
86
+ @_statusCode = response.status
87
+ @_responseHeaders = response.headers
83
88
 
84
89
  networkTraffic: ->
85
90
  @_networkTraffic
86
91
 
87
92
  content: ->
88
- @native.content
93
+ @native.frameContent
89
94
 
90
95
  source: ->
91
96
  @_source
@@ -99,6 +104,18 @@ class Poltergeist.WebPage
99
104
  statusCode: ->
100
105
  @_statusCode
101
106
 
107
+ responseHeaders: ->
108
+ headers = {}
109
+ @_responseHeaders.forEach (item) ->
110
+ headers[item.name] = item.value
111
+ headers
112
+
113
+ cookies: ->
114
+ @native.cookies
115
+
116
+ deleteCookie: (name) ->
117
+ @native.deleteCookie(name)
118
+
102
119
  viewportSize: ->
103
120
  @native.viewportSize
104
121
 
@@ -117,6 +134,33 @@ class Poltergeist.WebPage
117
134
  setClipRect: (rect) ->
118
135
  @native.clipRect = rect
119
136
 
137
+ setUserAgent: (userAgent) ->
138
+ @native.settings.userAgent = userAgent
139
+
140
+ setCustomHeaders: (headers) ->
141
+ @native.customHeaders = headers
142
+
143
+ pushFrame: (name) ->
144
+ @frames.push(name)
145
+ res = @native.switchToFrame(name)
146
+ this.injectAgent()
147
+ res
148
+
149
+ popFrame: ->
150
+ @frames.pop()
151
+
152
+ if @frames.length == 0
153
+ @native.switchToMainFrame()
154
+ else
155
+ @native.switchToFrame(@frames[@frames.length - 1])
156
+
157
+ getPage: (name) ->
158
+ if @sub_pages[name]
159
+ @sub_pages[name]
160
+ else
161
+ page = @native.getPage(name)
162
+ @sub_pages[name] = new Poltergeist.WebPage(page) if page
163
+
120
164
  dimensions: ->
121
165
  scroll = this.scrollPosition()
122
166
  viewport = this.viewportSize()
@@ -144,7 +188,13 @@ class Poltergeist.WebPage
144
188
  dimensions
145
189
 
146
190
  get: (id) ->
147
- @nodes[id] or= new Poltergeist.Node(this, id)
191
+ new Poltergeist.Node(this, id)
192
+
193
+ # Before each mouse event we make sure that the mouse is moved to where the
194
+ # event will take place. This deals with e.g. :hover changes.
195
+ mouseEvent: (name, x, y) ->
196
+ this.sendEvent('mousemove', x, y)
197
+ this.sendEvent(name, x, y)
148
198
 
149
199
  evaluate: (fn, args...) ->
150
200
  JSON.parse @native.evaluate("function() { return PoltergeistAgent.stringify(#{this.stringifyCall(fn, args)}) }")