poltergeist 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +77 -40
- data/lib/capybara/poltergeist.rb +11 -11
- data/lib/capybara/poltergeist/browser.rb +35 -12
- data/lib/capybara/poltergeist/client.rb +9 -12
- data/lib/capybara/poltergeist/client/agent.coffee +99 -6
- data/lib/capybara/poltergeist/client/browser.coffee +57 -79
- data/lib/capybara/poltergeist/client/compiled/agent.js +179 -19
- data/lib/capybara/poltergeist/client/compiled/browser.js +109 -81
- data/lib/capybara/poltergeist/client/compiled/connection.js +8 -1
- data/lib/capybara/poltergeist/client/compiled/main.js +75 -34
- data/lib/capybara/poltergeist/client/compiled/node.js +32 -39
- data/lib/capybara/poltergeist/client/compiled/web_page.js +118 -41
- data/lib/capybara/poltergeist/client/main.coffee +11 -23
- data/lib/capybara/poltergeist/client/node.coffee +16 -33
- data/lib/capybara/poltergeist/client/web_page.coffee +57 -26
- data/lib/capybara/poltergeist/driver.rb +36 -6
- data/lib/capybara/poltergeist/errors.rb +4 -15
- data/lib/capybara/poltergeist/json.rb +25 -0
- data/lib/capybara/poltergeist/network_traffic.rb +6 -0
- data/lib/capybara/poltergeist/network_traffic/request.rb +26 -0
- data/lib/capybara/poltergeist/network_traffic/response.rb +40 -0
- data/lib/capybara/poltergeist/node.rb +10 -6
- data/lib/capybara/poltergeist/version.rb +1 -1
- data/lib/capybara/poltergeist/web_socket_server.rb +3 -0
- metadata +112 -23
@@ -1,54 +1,37 @@
|
|
1
|
-
var __slice =
|
1
|
+
var __slice = [].slice;
|
2
|
+
|
2
3
|
Poltergeist.Node = (function() {
|
3
|
-
var name, _fn, _i, _len, _ref
|
4
|
-
|
4
|
+
var name, _fn, _i, _len, _ref,
|
5
|
+
_this = this;
|
6
|
+
|
7
|
+
Node.DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'clickTest', 'scrollIntoView', 'isDOMEqual'];
|
8
|
+
|
5
9
|
function Node(page, id) {
|
6
10
|
this.page = page;
|
7
11
|
this.id = id;
|
8
12
|
}
|
13
|
+
|
9
14
|
Node.prototype.parent = function() {
|
10
15
|
return new Poltergeist.Node(this.page, this.parentId());
|
11
16
|
};
|
17
|
+
|
12
18
|
_ref = Node.DELEGATES;
|
13
|
-
_fn =
|
14
|
-
return
|
15
|
-
var
|
16
|
-
|
17
|
-
return this.page.nodeCall(this.id, name,
|
19
|
+
_fn = function(name) {
|
20
|
+
return Node.prototype[name] = function() {
|
21
|
+
var args;
|
22
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
23
|
+
return this.page.nodeCall(this.id, name, args);
|
18
24
|
};
|
19
|
-
}
|
25
|
+
};
|
20
26
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
21
27
|
name = _ref[_i];
|
22
28
|
_fn(name);
|
23
29
|
}
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
}
|
29
|
-
dimensions = this.page.validatedDimensions();
|
30
|
-
document = dimensions.document;
|
31
|
-
viewport = dimensions.viewport;
|
30
|
+
|
31
|
+
Node.prototype.clickPosition = function() {
|
32
|
+
var middle, pos, viewport;
|
33
|
+
viewport = this.page.viewportSize();
|
32
34
|
pos = this.position();
|
33
|
-
scroll = {
|
34
|
-
left: dimensions.left,
|
35
|
-
top: dimensions.top
|
36
|
-
};
|
37
|
-
adjust = function(coord, measurement) {
|
38
|
-
if (pos[coord] < 0) {
|
39
|
-
return scroll[coord] = Math.max(0, scroll[coord] + pos[coord] - (viewport[measurement] / 2));
|
40
|
-
} else if (pos[coord] >= viewport[measurement]) {
|
41
|
-
return scroll[coord] = Math.min(document[measurement] - viewport[measurement], scroll[coord] + pos[coord] - viewport[measurement] + (viewport[measurement] / 2));
|
42
|
-
}
|
43
|
-
};
|
44
|
-
if (scrollIntoView) {
|
45
|
-
adjust('left', 'width');
|
46
|
-
adjust('top', 'height');
|
47
|
-
if (scroll.left !== dimensions.left || scroll.top !== dimensions.top) {
|
48
|
-
this.page.setScrollPosition(scroll);
|
49
|
-
pos = this.position();
|
50
|
-
}
|
51
|
-
}
|
52
35
|
middle = function(start, end, size) {
|
53
36
|
return start + ((Math.min(end, size) - start) / 2);
|
54
37
|
};
|
@@ -57,23 +40,33 @@ Poltergeist.Node = (function() {
|
|
57
40
|
y: middle(pos.top, pos.bottom, viewport.height)
|
58
41
|
};
|
59
42
|
};
|
43
|
+
|
60
44
|
Node.prototype.click = function() {
|
61
45
|
var pos, test;
|
46
|
+
this.scrollIntoView();
|
62
47
|
pos = this.clickPosition();
|
63
48
|
test = this.clickTest(pos.x, pos.y);
|
64
49
|
if (test.status === 'success') {
|
65
50
|
return this.page.sendEvent('click', pos.x, pos.y);
|
66
51
|
} else {
|
67
|
-
|
52
|
+
throw new Poltergeist.ClickFailed(test.selector, pos);
|
68
53
|
}
|
69
54
|
};
|
55
|
+
|
70
56
|
Node.prototype.dragTo = function(other) {
|
71
57
|
var otherPosition, position;
|
58
|
+
this.scrollIntoView();
|
72
59
|
position = this.clickPosition();
|
73
|
-
otherPosition = other.clickPosition(
|
60
|
+
otherPosition = other.clickPosition();
|
74
61
|
this.page.sendEvent('mousedown', position.x, position.y);
|
75
62
|
this.page.sendEvent('mousemove', otherPosition.x, otherPosition.y);
|
76
63
|
return this.page.sendEvent('mouseup', otherPosition.x, otherPosition.y);
|
77
64
|
};
|
65
|
+
|
66
|
+
Node.prototype.isEqual = function(other) {
|
67
|
+
return this.page === other.page && this.isDOMEqual(other.id);
|
68
|
+
};
|
69
|
+
|
78
70
|
return Node;
|
79
|
-
|
71
|
+
|
72
|
+
}).call(this);
|
@@ -1,17 +1,24 @@
|
|
1
|
-
var __slice =
|
1
|
+
var __slice = [].slice;
|
2
|
+
|
2
3
|
Poltergeist.WebPage = (function() {
|
3
|
-
var command, delegate, _fn,
|
4
|
-
|
4
|
+
var command, delegate, _fn, _fn1, _i, _j, _len, _len1, _ref, _ref1,
|
5
|
+
_this = this;
|
6
|
+
|
7
|
+
WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError', 'onNavigationRequested', 'onUrlChanged'];
|
8
|
+
|
5
9
|
WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render'];
|
10
|
+
|
6
11
|
WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize'];
|
7
|
-
|
12
|
+
|
13
|
+
function WebPage(width, height) {
|
8
14
|
var callback, _i, _len, _ref;
|
9
15
|
this["native"] = require('webpage').create();
|
10
16
|
this._source = "";
|
11
17
|
this._errors = [];
|
18
|
+
this._networkTraffic = {};
|
12
19
|
this.setViewportSize({
|
13
|
-
width:
|
14
|
-
height:
|
20
|
+
width: width,
|
21
|
+
height: height
|
15
22
|
});
|
16
23
|
_ref = WebPage.CALLBACKS;
|
17
24
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
@@ -20,28 +27,31 @@ Poltergeist.WebPage = (function() {
|
|
20
27
|
}
|
21
28
|
this.injectAgent();
|
22
29
|
}
|
30
|
+
|
23
31
|
_ref = WebPage.COMMANDS;
|
24
|
-
_fn =
|
25
|
-
return
|
26
|
-
var
|
27
|
-
|
28
|
-
return this.runCommand(command,
|
32
|
+
_fn = function(command) {
|
33
|
+
return WebPage.prototype[command] = function() {
|
34
|
+
var args;
|
35
|
+
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
|
36
|
+
return this.runCommand(command, args);
|
29
37
|
};
|
30
|
-
}
|
38
|
+
};
|
31
39
|
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
32
40
|
command = _ref[_i];
|
33
41
|
_fn(command);
|
34
42
|
}
|
35
|
-
|
36
|
-
|
37
|
-
|
43
|
+
|
44
|
+
_ref1 = WebPage.DELEGATES;
|
45
|
+
_fn1 = function(delegate) {
|
46
|
+
return WebPage.prototype[delegate] = function() {
|
38
47
|
return this["native"][delegate].apply(this["native"], arguments);
|
39
48
|
};
|
40
|
-
}
|
41
|
-
for (_j = 0,
|
42
|
-
delegate =
|
43
|
-
|
49
|
+
};
|
50
|
+
for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
|
51
|
+
delegate = _ref1[_j];
|
52
|
+
_fn1(delegate);
|
44
53
|
}
|
54
|
+
|
45
55
|
WebPage.prototype.onInitializedNative = function() {
|
46
56
|
this._source = null;
|
47
57
|
this.injectAgent();
|
@@ -50,64 +60,118 @@ Poltergeist.WebPage = (function() {
|
|
50
60
|
top: 0
|
51
61
|
});
|
52
62
|
};
|
63
|
+
|
53
64
|
WebPage.prototype.injectAgent = function() {
|
54
|
-
if (this.evaluate(function() {
|
65
|
+
if (this["native"].evaluate(function() {
|
55
66
|
return typeof __poltergeist;
|
56
67
|
}) === "undefined") {
|
57
68
|
this["native"].injectJs("" + phantom.libraryPath + "/agent.js");
|
58
69
|
return this.nodes = {};
|
59
70
|
}
|
60
71
|
};
|
72
|
+
|
61
73
|
WebPage.prototype.onConsoleMessageNative = function(message) {
|
62
74
|
if (message === '__DOMContentLoaded') {
|
63
75
|
this._source = this["native"].content;
|
64
76
|
return false;
|
65
77
|
}
|
66
78
|
};
|
79
|
+
|
80
|
+
WebPage.prototype.onLoadStartedNative = function() {
|
81
|
+
return this.requestId = this.lastRequestId;
|
82
|
+
};
|
83
|
+
|
67
84
|
WebPage.prototype.onLoadFinishedNative = function() {
|
68
85
|
return this._source || (this._source = this["native"].content);
|
69
86
|
};
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
}
|
87
|
+
|
88
|
+
WebPage.prototype.onConsoleMessage = function(message) {
|
89
|
+
return console.log(message);
|
74
90
|
};
|
91
|
+
|
75
92
|
WebPage.prototype.onErrorNative = function(message, stack) {
|
93
|
+
var stackString;
|
94
|
+
stackString = message;
|
95
|
+
stack.forEach(function(frame) {
|
96
|
+
stackString += "\n";
|
97
|
+
stackString += " at " + frame.file + ":" + frame.line;
|
98
|
+
if (frame["function"] && frame["function"] !== '') {
|
99
|
+
return stackString += " in " + frame["function"];
|
100
|
+
}
|
101
|
+
});
|
76
102
|
return this._errors.push({
|
77
103
|
message: message,
|
78
|
-
stack:
|
104
|
+
stack: stackString
|
79
105
|
});
|
80
106
|
};
|
107
|
+
|
108
|
+
WebPage.prototype.onResourceRequestedNative = function(request) {
|
109
|
+
this.lastRequestId = request.id;
|
110
|
+
return this._networkTraffic[request.id] = {
|
111
|
+
request: request,
|
112
|
+
responseParts: []
|
113
|
+
};
|
114
|
+
};
|
115
|
+
|
116
|
+
WebPage.prototype.onResourceReceivedNative = function(response) {
|
117
|
+
this._networkTraffic[response.id].responseParts.push(response);
|
118
|
+
if (this.requestId === response.id) {
|
119
|
+
if (response.redirectURL) {
|
120
|
+
return this.requestId = response.id;
|
121
|
+
} else {
|
122
|
+
return this._statusCode = response.status;
|
123
|
+
}
|
124
|
+
}
|
125
|
+
};
|
126
|
+
|
127
|
+
WebPage.prototype.networkTraffic = function() {
|
128
|
+
return this._networkTraffic;
|
129
|
+
};
|
130
|
+
|
81
131
|
WebPage.prototype.content = function() {
|
82
132
|
return this["native"].content;
|
83
133
|
};
|
134
|
+
|
84
135
|
WebPage.prototype.source = function() {
|
85
136
|
return this._source;
|
86
137
|
};
|
138
|
+
|
87
139
|
WebPage.prototype.errors = function() {
|
88
140
|
return this._errors;
|
89
141
|
};
|
142
|
+
|
90
143
|
WebPage.prototype.clearErrors = function() {
|
91
144
|
return this._errors = [];
|
92
145
|
};
|
146
|
+
|
147
|
+
WebPage.prototype.statusCode = function() {
|
148
|
+
return this._statusCode;
|
149
|
+
};
|
150
|
+
|
93
151
|
WebPage.prototype.viewportSize = function() {
|
94
152
|
return this["native"].viewportSize;
|
95
153
|
};
|
154
|
+
|
96
155
|
WebPage.prototype.setViewportSize = function(size) {
|
97
156
|
return this["native"].viewportSize = size;
|
98
157
|
};
|
158
|
+
|
99
159
|
WebPage.prototype.scrollPosition = function() {
|
100
160
|
return this["native"].scrollPosition;
|
101
161
|
};
|
162
|
+
|
102
163
|
WebPage.prototype.setScrollPosition = function(pos) {
|
103
164
|
return this["native"].scrollPosition = pos;
|
104
165
|
};
|
166
|
+
|
105
167
|
WebPage.prototype.clipRect = function() {
|
106
168
|
return this["native"].clipRect;
|
107
169
|
};
|
170
|
+
|
108
171
|
WebPage.prototype.setClipRect = function(rect) {
|
109
172
|
return this["native"].clipRect = rect;
|
110
173
|
};
|
174
|
+
|
111
175
|
WebPage.prototype.dimensions = function() {
|
112
176
|
var scroll, viewport;
|
113
177
|
scroll = this.scrollPosition();
|
@@ -121,12 +185,11 @@ Poltergeist.WebPage = (function() {
|
|
121
185
|
document: this.documentSize()
|
122
186
|
};
|
123
187
|
};
|
188
|
+
|
124
189
|
WebPage.prototype.validatedDimensions = function() {
|
125
|
-
var dimensions, document
|
190
|
+
var dimensions, document;
|
126
191
|
dimensions = this.dimensions();
|
127
192
|
document = dimensions.document;
|
128
|
-
orig_left = dimensions.left;
|
129
|
-
orig_top = dimensions.top;
|
130
193
|
if (dimensions.right > document.width) {
|
131
194
|
dimensions.left = Math.max(0, dimensions.left - (dimensions.right - document.width));
|
132
195
|
dimensions.right = document.width;
|
@@ -135,28 +198,30 @@ Poltergeist.WebPage = (function() {
|
|
135
198
|
dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - document.height));
|
136
199
|
dimensions.bottom = document.height;
|
137
200
|
}
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
});
|
143
|
-
}
|
201
|
+
this.setScrollPosition({
|
202
|
+
left: dimensions.left,
|
203
|
+
top: dimensions.top
|
204
|
+
});
|
144
205
|
return dimensions;
|
145
206
|
};
|
207
|
+
|
146
208
|
WebPage.prototype.get = function(id) {
|
147
209
|
var _base;
|
148
210
|
return (_base = this.nodes)[id] || (_base[id] = new Poltergeist.Node(this, id));
|
149
211
|
};
|
212
|
+
|
150
213
|
WebPage.prototype.evaluate = function() {
|
151
214
|
var args, fn;
|
152
215
|
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
153
|
-
return JSON.parse(this["native"].evaluate("function() { return
|
216
|
+
return JSON.parse(this["native"].evaluate("function() { return PoltergeistAgent.stringify(" + (this.stringifyCall(fn, args)) + ") }"));
|
154
217
|
};
|
218
|
+
|
155
219
|
WebPage.prototype.execute = function() {
|
156
220
|
var args, fn;
|
157
221
|
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
158
222
|
return this["native"].evaluate("function() { " + (this.stringifyCall(fn, args)) + " }");
|
159
223
|
};
|
224
|
+
|
160
225
|
WebPage.prototype.stringifyCall = function(fn, args) {
|
161
226
|
if (args.length === 0) {
|
162
227
|
return "(" + (fn.toString()) + ")()";
|
@@ -164,6 +229,7 @@ Poltergeist.WebPage = (function() {
|
|
164
229
|
return "(" + (fn.toString()) + ").apply(this, JSON.parse(" + (JSON.stringify(JSON.stringify(args))) + "))";
|
165
230
|
}
|
166
231
|
};
|
232
|
+
|
167
233
|
WebPage.prototype.bindCallback = function(name) {
|
168
234
|
var that;
|
169
235
|
that = this;
|
@@ -177,12 +243,23 @@ Poltergeist.WebPage = (function() {
|
|
177
243
|
}
|
178
244
|
};
|
179
245
|
};
|
180
|
-
|
246
|
+
|
247
|
+
WebPage.prototype.runCommand = function(name, args) {
|
181
248
|
var result;
|
182
|
-
result = this.evaluate(function(name,
|
183
|
-
return __poltergeist.externalCall(name,
|
184
|
-
}, name,
|
185
|
-
|
249
|
+
result = this.evaluate(function(name, args) {
|
250
|
+
return __poltergeist.externalCall(name, args);
|
251
|
+
}, name, args);
|
252
|
+
if (result.error != null) {
|
253
|
+
if (result.error.message === 'PoltergeistAgent.ObsoleteNode') {
|
254
|
+
throw new Poltergeist.ObsoleteNode;
|
255
|
+
} else {
|
256
|
+
throw new Poltergeist.JavascriptError([result.error]);
|
257
|
+
}
|
258
|
+
} else {
|
259
|
+
return result.value;
|
260
|
+
}
|
186
261
|
};
|
262
|
+
|
187
263
|
return WebPage;
|
188
|
-
|
264
|
+
|
265
|
+
}).call(this);
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Poltergeist
|
2
|
-
constructor: (port) ->
|
3
|
-
@browser = new Poltergeist.Browser(this)
|
2
|
+
constructor: (port, width, height) ->
|
3
|
+
@browser = new Poltergeist.Browser(this, width, height)
|
4
4
|
@connection = new Poltergeist.Connection(this, port)
|
5
5
|
|
6
6
|
# The QtWebKit bridge doesn't seem to like Function.prototype.bind
|
@@ -11,7 +11,14 @@ class Poltergeist
|
|
11
11
|
|
12
12
|
runCommand: (command) ->
|
13
13
|
@running = true
|
14
|
-
|
14
|
+
|
15
|
+
try
|
16
|
+
@browser[command.name].apply(@browser, command.args)
|
17
|
+
catch error
|
18
|
+
if error instanceof Poltergeist.Error
|
19
|
+
this.sendError(error)
|
20
|
+
else
|
21
|
+
this.sendError(new Poltergeist.BrowserError(error.toString(), error.stack))
|
15
22
|
|
16
23
|
sendResponse: (response) ->
|
17
24
|
this.send(response: response)
|
@@ -23,25 +30,6 @@ class Poltergeist
|
|
23
30
|
args: error.args && error.args() || [error.toString()]
|
24
31
|
)
|
25
32
|
|
26
|
-
# This is a bit of a mess. We can't wrap the runCommand code in
|
27
|
-
# a try ... catch, because that will prevent stack traces being
|
28
|
-
# reported, and there is no error.stack property.
|
29
|
-
#
|
30
|
-
# And thrown errors get toString() called, which becomes the
|
31
|
-
# value of the message variable here. So we can basically only
|
32
|
-
# use strings as exceptions at the moment, which means we throw
|
33
|
-
# exceptions with extra data and then retrieve it here.
|
34
|
-
#
|
35
|
-
# The solution will be for PhantomJS to support an e.stack
|
36
|
-
# property on exceptions.
|
37
|
-
#
|
38
|
-
# See http://code.google.com/p/phantomjs/issues/detail?id=166.
|
39
|
-
onError: (message, stack) =>
|
40
|
-
if message == 'PoltergeistAgent.ObsoleteNode'
|
41
|
-
this.sendError(new Poltergeist.ObsoleteNode)
|
42
|
-
else
|
43
|
-
this.sendError(new Poltergeist.BrowserError(message, stack))
|
44
|
-
|
45
33
|
send: (data) ->
|
46
34
|
# Prevents more than one response being sent for a single
|
47
35
|
# command. This can happen in some scenarios where an error
|
@@ -84,4 +72,4 @@ phantom.injectJs("#{phantom.libraryPath}/node.js")
|
|
84
72
|
phantom.injectJs("#{phantom.libraryPath}/connection.js")
|
85
73
|
phantom.injectJs("#{phantom.libraryPath}/browser.js")
|
86
74
|
|
87
|
-
new Poltergeist(phantom.args[0])
|
75
|
+
new Poltergeist(phantom.args[0], phantom.args[1], phantom.args[2])
|