poltergeist 0.6.0 → 0.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.
- 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])
|