poltergeist 0.1.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/CHANGELOG.md +0 -0
- data/LICENSE +20 -0
- data/README.md +85 -0
- data/lib/capybara/poltergeist/browser.rb +130 -0
- data/lib/capybara/poltergeist/client/agent.coffee +175 -0
- data/lib/capybara/poltergeist/client/browser.coffee +121 -0
- data/lib/capybara/poltergeist/client/compiled/agent.js +198 -0
- data/lib/capybara/poltergeist/client/compiled/browser.js +117 -0
- data/lib/capybara/poltergeist/client/compiled/connection.js +20 -0
- data/lib/capybara/poltergeist/client/compiled/main.js +38 -0
- data/lib/capybara/poltergeist/client/compiled/node.js +74 -0
- data/lib/capybara/poltergeist/client/compiled/web_page.js +136 -0
- data/lib/capybara/poltergeist/client/connection.coffee +11 -0
- data/lib/capybara/poltergeist/client/main.coffee +27 -0
- data/lib/capybara/poltergeist/client/node.coffee +56 -0
- data/lib/capybara/poltergeist/client/web_page.coffee +102 -0
- data/lib/capybara/poltergeist/client.rb +57 -0
- data/lib/capybara/poltergeist/driver.rb +85 -0
- data/lib/capybara/poltergeist/errors.rb +26 -0
- data/lib/capybara/poltergeist/node.rb +92 -0
- data/lib/capybara/poltergeist/server.rb +35 -0
- data/lib/capybara/poltergeist/server_manager.rb +118 -0
- data/lib/capybara/poltergeist/version.rb +5 -0
- data/lib/capybara/poltergeist.rb +20 -0
- metadata +102 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
var PoltergeistAgent;
|
|
2
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
3
|
+
PoltergeistAgent = (function() {
|
|
4
|
+
function PoltergeistAgent() {
|
|
5
|
+
this.elements = [];
|
|
6
|
+
this.nodes = {};
|
|
7
|
+
this.windows = [];
|
|
8
|
+
this.pushWindow(window);
|
|
9
|
+
}
|
|
10
|
+
PoltergeistAgent.prototype.pushWindow = function(new_window) {
|
|
11
|
+
this.windows.push(new_window);
|
|
12
|
+
this.window = new_window;
|
|
13
|
+
return this.document = this.window.document;
|
|
14
|
+
};
|
|
15
|
+
PoltergeistAgent.prototype.popWindow = function() {
|
|
16
|
+
this.windows.pop();
|
|
17
|
+
this.window = this.windows[this.windows.length - 1];
|
|
18
|
+
return this.document = this.window.document;
|
|
19
|
+
};
|
|
20
|
+
PoltergeistAgent.prototype.pushFrame = function(id) {
|
|
21
|
+
return this.pushWindow(this.document.getElementById(id).contentWindow);
|
|
22
|
+
};
|
|
23
|
+
PoltergeistAgent.prototype.popFrame = function() {
|
|
24
|
+
return this.popWindow();
|
|
25
|
+
};
|
|
26
|
+
PoltergeistAgent.prototype.currentUrl = function() {
|
|
27
|
+
return window.location.toString();
|
|
28
|
+
};
|
|
29
|
+
PoltergeistAgent.prototype.find = function(selector, id) {
|
|
30
|
+
var context, i, ids, results, _ref;
|
|
31
|
+
context = id != null ? this.elements[id] : this.document;
|
|
32
|
+
results = this.document.evaluate(selector, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
33
|
+
ids = [];
|
|
34
|
+
for (i = 0, _ref = results.snapshotLength; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
|
|
35
|
+
ids.push(this.register(results.snapshotItem(i)));
|
|
36
|
+
}
|
|
37
|
+
return ids;
|
|
38
|
+
};
|
|
39
|
+
PoltergeistAgent.prototype.register = function(element) {
|
|
40
|
+
this.elements.push(element);
|
|
41
|
+
return this.elements.length - 1;
|
|
42
|
+
};
|
|
43
|
+
PoltergeistAgent.prototype.documentSize = function() {
|
|
44
|
+
return {
|
|
45
|
+
height: this.document.documentElement.scrollHeight,
|
|
46
|
+
width: this.document.documentElement.scrollWidth
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
PoltergeistAgent.prototype.get = function(id) {
|
|
50
|
+
var _base;
|
|
51
|
+
return (_base = this.nodes)[id] || (_base[id] = new PoltergeistAgent.Node(this, this.elements[id]));
|
|
52
|
+
};
|
|
53
|
+
PoltergeistAgent.prototype.nodeCall = function(id, name, arguments) {
|
|
54
|
+
var node;
|
|
55
|
+
node = this.get(id);
|
|
56
|
+
return node[name].apply(node, arguments);
|
|
57
|
+
};
|
|
58
|
+
return PoltergeistAgent;
|
|
59
|
+
})();
|
|
60
|
+
PoltergeistAgent.Node = (function() {
|
|
61
|
+
Node.EVENTS = {
|
|
62
|
+
FOCUS: ['blur', 'focus', 'focusin', 'focusout'],
|
|
63
|
+
MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup']
|
|
64
|
+
};
|
|
65
|
+
function Node(agent, element) {
|
|
66
|
+
this.agent = agent;
|
|
67
|
+
this.element = element;
|
|
68
|
+
}
|
|
69
|
+
Node.prototype.parentId = function() {
|
|
70
|
+
return this.agent.register(this.element.parentNode);
|
|
71
|
+
};
|
|
72
|
+
Node.prototype.isObsolete = function() {
|
|
73
|
+
var obsolete;
|
|
74
|
+
obsolete = __bind(function(element) {
|
|
75
|
+
if (element.parentNode != null) {
|
|
76
|
+
if (element.parentNode === this.agent.document) {
|
|
77
|
+
return false;
|
|
78
|
+
} else {
|
|
79
|
+
return obsolete(element.parentNode);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}, this);
|
|
85
|
+
return obsolete(this.element);
|
|
86
|
+
};
|
|
87
|
+
Node.prototype.changed = function() {
|
|
88
|
+
var event;
|
|
89
|
+
event = document.createEvent('HTMLEvents');
|
|
90
|
+
event.initEvent("change", true, false);
|
|
91
|
+
return this.element.dispatchEvent(event);
|
|
92
|
+
};
|
|
93
|
+
Node.prototype.text = function() {
|
|
94
|
+
return this.element.textContent;
|
|
95
|
+
};
|
|
96
|
+
Node.prototype.getAttribute = function(name) {
|
|
97
|
+
if (name === 'checked' || name === 'selected') {
|
|
98
|
+
return this.element[name];
|
|
99
|
+
} else {
|
|
100
|
+
return this.element.getAttribute(name);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
Node.prototype.value = function() {
|
|
104
|
+
var option, _i, _len, _ref, _results;
|
|
105
|
+
if (this.element.tagName === 'SELECT' && this.element.multiple) {
|
|
106
|
+
_ref = this.element.children;
|
|
107
|
+
_results = [];
|
|
108
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
109
|
+
option = _ref[_i];
|
|
110
|
+
if (option.selected) {
|
|
111
|
+
_results.push(option.value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return _results;
|
|
115
|
+
} else {
|
|
116
|
+
return this.element.value;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
Node.prototype.set = function(value) {
|
|
120
|
+
if (this.element.maxLength >= 0) {
|
|
121
|
+
value = value.substr(0, this.element.maxLength);
|
|
122
|
+
}
|
|
123
|
+
this.element.value = value;
|
|
124
|
+
return this.changed();
|
|
125
|
+
};
|
|
126
|
+
Node.prototype.isMultiple = function() {
|
|
127
|
+
return this.element.multiple;
|
|
128
|
+
};
|
|
129
|
+
Node.prototype.setAttribute = function(name, value) {
|
|
130
|
+
return this.element.setAttribute(name, value);
|
|
131
|
+
};
|
|
132
|
+
Node.prototype.removeAttribute = function(name) {
|
|
133
|
+
return this.element.removeAttribute(name);
|
|
134
|
+
};
|
|
135
|
+
Node.prototype.select = function(value) {
|
|
136
|
+
if (value === false && !this.element.parentNode.multiple) {
|
|
137
|
+
return false;
|
|
138
|
+
} else {
|
|
139
|
+
this.element.selected = value;
|
|
140
|
+
this.changed();
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
Node.prototype.tagName = function() {
|
|
145
|
+
return this.element.tagName;
|
|
146
|
+
};
|
|
147
|
+
Node.prototype.elementVisible = function(element) {};
|
|
148
|
+
Node.prototype.isVisible = function(id) {
|
|
149
|
+
var visible;
|
|
150
|
+
visible = function(element) {
|
|
151
|
+
if (this.window.getComputedStyle(element).display === 'none') {
|
|
152
|
+
return false;
|
|
153
|
+
} else if (element.parentElement) {
|
|
154
|
+
return visible(element.parentElement);
|
|
155
|
+
} else {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
return visible(this.element);
|
|
160
|
+
};
|
|
161
|
+
Node.prototype.position = function(id) {
|
|
162
|
+
var pos;
|
|
163
|
+
pos = function(element) {
|
|
164
|
+
var parentPos, x, y;
|
|
165
|
+
x = element.offsetLeft;
|
|
166
|
+
y = element.offsetTop;
|
|
167
|
+
if (element.offsetParent) {
|
|
168
|
+
parentPos = pos(element.offsetParent);
|
|
169
|
+
x += parentPos.x;
|
|
170
|
+
y += parentPos.y;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
x: x,
|
|
174
|
+
y: y
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
return pos(this.element);
|
|
178
|
+
};
|
|
179
|
+
Node.prototype.trigger = function(name) {
|
|
180
|
+
var event;
|
|
181
|
+
if (Node.EVENTS.MOUSE.indexOf(name) !== -1) {
|
|
182
|
+
event = document.createEvent('MouseEvent');
|
|
183
|
+
event.initMouseEvent(name, true, true, this.agent.window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
|
184
|
+
} else if (Node.EVENTS.FOCUS.indexOf(name) !== -1) {
|
|
185
|
+
event = document.createEvent('HTMLEvents');
|
|
186
|
+
event.initEvent(name, true, true);
|
|
187
|
+
} else {
|
|
188
|
+
throw "Unknown event";
|
|
189
|
+
}
|
|
190
|
+
return this.element.dispatchEvent(event);
|
|
191
|
+
};
|
|
192
|
+
return Node;
|
|
193
|
+
})();
|
|
194
|
+
window.__poltergeist = new PoltergeistAgent;
|
|
195
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
196
|
+
return console.log('__DOMContentLoaded');
|
|
197
|
+
});
|
|
198
|
+
true;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
2
|
+
Poltergeist.Browser = (function() {
|
|
3
|
+
function Browser(owner) {
|
|
4
|
+
this.owner = owner;
|
|
5
|
+
this.awaiting_response = false;
|
|
6
|
+
this.resetPage();
|
|
7
|
+
}
|
|
8
|
+
Browser.prototype.resetPage = function() {
|
|
9
|
+
if (this.page != null) {
|
|
10
|
+
this.page.release();
|
|
11
|
+
}
|
|
12
|
+
this.page = new Poltergeist.WebPage;
|
|
13
|
+
return this.page.onLoadFinished = __bind(function(status) {
|
|
14
|
+
if (this.awaiting_response) {
|
|
15
|
+
this.owner.sendResponse(status);
|
|
16
|
+
return this.awaiting_response = false;
|
|
17
|
+
}
|
|
18
|
+
}, this);
|
|
19
|
+
};
|
|
20
|
+
Browser.prototype.visit = function(url) {
|
|
21
|
+
this.awaiting_response = true;
|
|
22
|
+
return this.page.open(url);
|
|
23
|
+
};
|
|
24
|
+
Browser.prototype.current_url = function() {
|
|
25
|
+
return this.owner.sendResponse(this.page.currentUrl());
|
|
26
|
+
};
|
|
27
|
+
Browser.prototype.body = function() {
|
|
28
|
+
return this.owner.sendResponse(this.page.content());
|
|
29
|
+
};
|
|
30
|
+
Browser.prototype.source = function() {
|
|
31
|
+
return this.owner.sendResponse(this.page.source());
|
|
32
|
+
};
|
|
33
|
+
Browser.prototype.find = function(selector, id) {
|
|
34
|
+
return this.owner.sendResponse(this.page.find(selector, id));
|
|
35
|
+
};
|
|
36
|
+
Browser.prototype.text = function(id) {
|
|
37
|
+
return this.owner.sendResponse(this.page.get(id).text());
|
|
38
|
+
};
|
|
39
|
+
Browser.prototype.attribute = function(id, name) {
|
|
40
|
+
return this.owner.sendResponse(this.page.get(id).getAttribute(name));
|
|
41
|
+
};
|
|
42
|
+
Browser.prototype.value = function(id) {
|
|
43
|
+
return this.owner.sendResponse(this.page.get(id).value());
|
|
44
|
+
};
|
|
45
|
+
Browser.prototype.set = function(id, value) {
|
|
46
|
+
this.page.get(id).set(value);
|
|
47
|
+
return this.owner.sendResponse(true);
|
|
48
|
+
};
|
|
49
|
+
Browser.prototype.select_file = function(id, value) {
|
|
50
|
+
var element, multiple;
|
|
51
|
+
element = this.page.get(id);
|
|
52
|
+
multiple = element.isMultiple();
|
|
53
|
+
if (multiple) {
|
|
54
|
+
element.removeAttribute('multiple');
|
|
55
|
+
}
|
|
56
|
+
element.setAttribute('_poltergeist_selected', '');
|
|
57
|
+
this.page.uploadFile('[_poltergeist_selected]', value);
|
|
58
|
+
element.removeAttribute('_poltergeist_selected');
|
|
59
|
+
if (multiple) {
|
|
60
|
+
element.setAttribute('multiple', 'multiple');
|
|
61
|
+
}
|
|
62
|
+
return this.owner.sendResponse(true);
|
|
63
|
+
};
|
|
64
|
+
Browser.prototype.select = function(id, value) {
|
|
65
|
+
return this.owner.sendResponse(this.page.get(id).select(value));
|
|
66
|
+
};
|
|
67
|
+
Browser.prototype.tag_name = function(id) {
|
|
68
|
+
return this.owner.sendResponse(this.page.get(id).tagName());
|
|
69
|
+
};
|
|
70
|
+
Browser.prototype.visible = function(id) {
|
|
71
|
+
return this.owner.sendResponse(this.page.get(id).isVisible());
|
|
72
|
+
};
|
|
73
|
+
Browser.prototype.evaluate = function(script) {
|
|
74
|
+
return this.owner.sendResponse(this.page.evaluate("function() { return " + script + " }"));
|
|
75
|
+
};
|
|
76
|
+
Browser.prototype.execute = function(script) {
|
|
77
|
+
this.page.execute("function() { return " + script + " }");
|
|
78
|
+
return this.owner.sendResponse(true);
|
|
79
|
+
};
|
|
80
|
+
Browser.prototype.push_frame = function(id) {
|
|
81
|
+
this.page.pushFrame(id);
|
|
82
|
+
return this.owner.sendResponse(true);
|
|
83
|
+
};
|
|
84
|
+
Browser.prototype.pop_frame = function() {
|
|
85
|
+
this.page.popFrame();
|
|
86
|
+
return this.owner.sendResponse(true);
|
|
87
|
+
};
|
|
88
|
+
Browser.prototype.click = function(id) {
|
|
89
|
+
this.page.onLoadStarted = __bind(function() {
|
|
90
|
+
return this.awaiting_response = true;
|
|
91
|
+
}, this);
|
|
92
|
+
this.page.get(id).click();
|
|
93
|
+
return setTimeout(__bind(function() {
|
|
94
|
+
this.page.onLoadStarted = null;
|
|
95
|
+
if (!this.awaiting_response) {
|
|
96
|
+
return this.owner.sendResponse(true);
|
|
97
|
+
}
|
|
98
|
+
}, this), 0);
|
|
99
|
+
};
|
|
100
|
+
Browser.prototype.drag = function(id, other_id) {
|
|
101
|
+
this.page.get(id).dragTo(this.page.get(other_id));
|
|
102
|
+
return this.owner.sendResponse(true);
|
|
103
|
+
};
|
|
104
|
+
Browser.prototype.trigger = function(id, event) {
|
|
105
|
+
this.page.get(id).trigger(event);
|
|
106
|
+
return this.owner.sendResponse(event);
|
|
107
|
+
};
|
|
108
|
+
Browser.prototype.reset = function() {
|
|
109
|
+
this.resetPage();
|
|
110
|
+
return this.owner.sendResponse(true);
|
|
111
|
+
};
|
|
112
|
+
Browser.prototype.render = function(path) {
|
|
113
|
+
this.page.render(path);
|
|
114
|
+
return this.owner.sendResponse(true);
|
|
115
|
+
};
|
|
116
|
+
return Browser;
|
|
117
|
+
})();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
2
|
+
Poltergeist.Connection = (function() {
|
|
3
|
+
function Connection(owner, port) {
|
|
4
|
+
this.owner = owner;
|
|
5
|
+
this.port = port;
|
|
6
|
+
this.commandReceived = __bind(this.commandReceived, this);
|
|
7
|
+
this.socket = new WebSocket("ws://127.0.0.1:" + this.port + "/");
|
|
8
|
+
this.socket.onmessage = this.commandReceived;
|
|
9
|
+
this.socket.onclose = function() {
|
|
10
|
+
return phantom.exit();
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
Connection.prototype.commandReceived = function(message) {
|
|
14
|
+
return this.owner.runCommand(JSON.parse(message.data));
|
|
15
|
+
};
|
|
16
|
+
Connection.prototype.send = function(message) {
|
|
17
|
+
return this.socket.send(JSON.stringify(message));
|
|
18
|
+
};
|
|
19
|
+
return Connection;
|
|
20
|
+
})();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var Poltergeist;
|
|
2
|
+
if (phantom.version.major < 1 || phantom.version.minor < 3) {
|
|
3
|
+
console.log("Poltergeist requires a PhantomJS version of at least 1.3");
|
|
4
|
+
phantom.exit(1);
|
|
5
|
+
}
|
|
6
|
+
Poltergeist = (function() {
|
|
7
|
+
function Poltergeist(port) {
|
|
8
|
+
this.browser = new Poltergeist.Browser(this);
|
|
9
|
+
this.connection = new Poltergeist.Connection(this, port);
|
|
10
|
+
}
|
|
11
|
+
Poltergeist.prototype.runCommand = function(command) {
|
|
12
|
+
try {
|
|
13
|
+
return this.browser[command.name].apply(this.browser, command.args);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
return this.connection.send({
|
|
16
|
+
error: error.toString()
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
Poltergeist.prototype.sendResponse = function(response) {
|
|
21
|
+
return this.connection.send({
|
|
22
|
+
response: response
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
return Poltergeist;
|
|
26
|
+
})();
|
|
27
|
+
Poltergeist.ObsoleteNode = (function() {
|
|
28
|
+
function ObsoleteNode() {}
|
|
29
|
+
ObsoleteNode.prototype.toString = function() {
|
|
30
|
+
return "Poltergeist.ObsoleteNode";
|
|
31
|
+
};
|
|
32
|
+
return ObsoleteNode;
|
|
33
|
+
})();
|
|
34
|
+
phantom.injectJs('web_page.js');
|
|
35
|
+
phantom.injectJs('node.js');
|
|
36
|
+
phantom.injectJs('connection.js');
|
|
37
|
+
phantom.injectJs('browser.js');
|
|
38
|
+
new Poltergeist(phantom.args[0]);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
var __slice = Array.prototype.slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
2
|
+
Poltergeist.Node = (function() {
|
|
3
|
+
var name, _fn, _i, _len, _ref;
|
|
4
|
+
Node.DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'isVisible', 'position', 'trigger', 'parentId'];
|
|
5
|
+
function Node(page, id) {
|
|
6
|
+
this.page = page;
|
|
7
|
+
this.id = id;
|
|
8
|
+
}
|
|
9
|
+
Node.prototype.parent = function() {
|
|
10
|
+
return new Poltergeist.Node(this.page, this.parentId());
|
|
11
|
+
};
|
|
12
|
+
Node.prototype.isObsolete = function() {
|
|
13
|
+
return this.page.nodeCall(this.id, 'isObsolete');
|
|
14
|
+
};
|
|
15
|
+
_ref = Node.DELEGATES;
|
|
16
|
+
_fn = __bind(function(name) {
|
|
17
|
+
return this.prototype[name] = function() {
|
|
18
|
+
var arguments, _ref2;
|
|
19
|
+
_ref2 = arguments, arguments = 1 <= _ref2.length ? __slice.call(_ref2, 0) : [];
|
|
20
|
+
if (this.isObsolete()) {
|
|
21
|
+
throw new Poltergeist.ObsoleteNode;
|
|
22
|
+
} else {
|
|
23
|
+
return this.page.nodeCall(this.id, name, arguments);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}, Node);
|
|
27
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
28
|
+
name = _ref[_i];
|
|
29
|
+
_fn(name);
|
|
30
|
+
}
|
|
31
|
+
Node.prototype.scrollIntoView = function() {
|
|
32
|
+
var pos, scroll, size, viewport, _ref2, _ref3;
|
|
33
|
+
viewport = this.page.viewport();
|
|
34
|
+
size = this.page.documentSize();
|
|
35
|
+
pos = this.position();
|
|
36
|
+
scroll = {
|
|
37
|
+
left: viewport.left,
|
|
38
|
+
top: viewport.top
|
|
39
|
+
};
|
|
40
|
+
if (!((viewport.left <= (_ref2 = pos.x) && _ref2 < viewport.right))) {
|
|
41
|
+
scroll.left = Math.min(pos.x, size.width - viewport.width);
|
|
42
|
+
}
|
|
43
|
+
if (!((viewport.top <= (_ref3 = pos.y) && _ref3 < viewport.bottom))) {
|
|
44
|
+
scroll.top = Math.min(pos.y, size.height - viewport.height);
|
|
45
|
+
}
|
|
46
|
+
if (scroll.left !== viewport.left || scroll.top !== viewport.top) {
|
|
47
|
+
this.page.setScrollPosition(scroll);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
position: this.relativePosition(pos, scroll),
|
|
51
|
+
scroll: scroll
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
Node.prototype.relativePosition = function(position, scroll) {
|
|
55
|
+
return {
|
|
56
|
+
x: position.x - scroll.left,
|
|
57
|
+
y: position.y - scroll.top
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
Node.prototype.click = function() {
|
|
61
|
+
var position;
|
|
62
|
+
position = this.scrollIntoView().position;
|
|
63
|
+
return this.page.sendEvent('click', position.x, position.y);
|
|
64
|
+
};
|
|
65
|
+
Node.prototype.dragTo = function(other) {
|
|
66
|
+
var otherPosition, position, scroll, _ref2;
|
|
67
|
+
_ref2 = this.scrollIntoView(), position = _ref2.position, scroll = _ref2.scroll;
|
|
68
|
+
otherPosition = this.relativePosition(other.position(), scroll);
|
|
69
|
+
this.page.sendEvent('mousedown', position.x, position.y);
|
|
70
|
+
this.page.sendEvent('mousemove', otherPosition.x, otherPosition.y);
|
|
71
|
+
return this.page.sendEvent('mouseup', otherPosition.x, otherPosition.y);
|
|
72
|
+
};
|
|
73
|
+
return Node;
|
|
74
|
+
}).call(this);
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
var __slice = Array.prototype.slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
|
2
|
+
Poltergeist.WebPage = (function() {
|
|
3
|
+
var command, delegate, _fn, _fn2, _i, _j, _len, _len2, _ref, _ref2;
|
|
4
|
+
WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived'];
|
|
5
|
+
WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render'];
|
|
6
|
+
WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize'];
|
|
7
|
+
function WebPage() {
|
|
8
|
+
var callback, _i, _len, _ref;
|
|
9
|
+
this["native"] = require('webpage').create();
|
|
10
|
+
this.nodes = {};
|
|
11
|
+
this._source = "";
|
|
12
|
+
_ref = WebPage.CALLBACKS;
|
|
13
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
14
|
+
callback = _ref[_i];
|
|
15
|
+
this.bindCallback(callback);
|
|
16
|
+
}
|
|
17
|
+
this.injectAgent();
|
|
18
|
+
}
|
|
19
|
+
_ref = WebPage.COMMANDS;
|
|
20
|
+
_fn = __bind(function(command) {
|
|
21
|
+
return this.prototype[command] = function() {
|
|
22
|
+
var arguments, _ref2;
|
|
23
|
+
_ref2 = arguments, arguments = 1 <= _ref2.length ? __slice.call(_ref2, 0) : [];
|
|
24
|
+
return this.runCommand(command, arguments);
|
|
25
|
+
};
|
|
26
|
+
}, WebPage);
|
|
27
|
+
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
28
|
+
command = _ref[_i];
|
|
29
|
+
_fn(command);
|
|
30
|
+
}
|
|
31
|
+
_ref2 = WebPage.DELEGATES;
|
|
32
|
+
_fn2 = __bind(function(delegate) {
|
|
33
|
+
return this.prototype[delegate] = function() {
|
|
34
|
+
return this["native"][delegate].apply(this["native"], arguments);
|
|
35
|
+
};
|
|
36
|
+
}, WebPage);
|
|
37
|
+
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
|
|
38
|
+
delegate = _ref2[_j];
|
|
39
|
+
_fn2(delegate);
|
|
40
|
+
}
|
|
41
|
+
WebPage.prototype.onInitializedNative = function() {
|
|
42
|
+
this._source = null;
|
|
43
|
+
this.injectAgent();
|
|
44
|
+
return this.setScrollPosition({
|
|
45
|
+
left: 0,
|
|
46
|
+
top: 0
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
WebPage.prototype.injectAgent = function() {
|
|
50
|
+
if (this.evaluate(function() {
|
|
51
|
+
return typeof __poltergeist;
|
|
52
|
+
}) === "undefined") {
|
|
53
|
+
return this["native"].injectJs('agent.js');
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
WebPage.prototype.onConsoleMessageNative = function(message) {
|
|
57
|
+
if (message === '__DOMContentLoaded') {
|
|
58
|
+
this._source = this["native"].content;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
WebPage.prototype.onLoadFinishedNative = function() {
|
|
63
|
+
return this._source || (this._source = this["native"].content);
|
|
64
|
+
};
|
|
65
|
+
WebPage.prototype.onConsoleMessage = function(message) {
|
|
66
|
+
return console.log(message);
|
|
67
|
+
};
|
|
68
|
+
WebPage.prototype.content = function() {
|
|
69
|
+
return this["native"].content;
|
|
70
|
+
};
|
|
71
|
+
WebPage.prototype.source = function() {
|
|
72
|
+
return this._source;
|
|
73
|
+
};
|
|
74
|
+
WebPage.prototype.viewportSize = function() {
|
|
75
|
+
return this["native"].viewportSize;
|
|
76
|
+
};
|
|
77
|
+
WebPage.prototype.scrollPosition = function() {
|
|
78
|
+
return this["native"].scrollPosition;
|
|
79
|
+
};
|
|
80
|
+
WebPage.prototype.setScrollPosition = function(pos) {
|
|
81
|
+
return this["native"].scrollPosition = pos;
|
|
82
|
+
};
|
|
83
|
+
WebPage.prototype.viewport = function() {
|
|
84
|
+
var scroll, size;
|
|
85
|
+
scroll = this.scrollPosition();
|
|
86
|
+
size = this.viewportSize();
|
|
87
|
+
return {
|
|
88
|
+
top: scroll.top,
|
|
89
|
+
bottom: scroll.top + size.height,
|
|
90
|
+
left: scroll.left,
|
|
91
|
+
right: scroll.left + size.width,
|
|
92
|
+
width: size.width,
|
|
93
|
+
height: size.height
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
WebPage.prototype.get = function(id) {
|
|
97
|
+
var _base;
|
|
98
|
+
return (_base = this.nodes)[id] || (_base[id] = new Poltergeist.Node(this, id));
|
|
99
|
+
};
|
|
100
|
+
WebPage.prototype.evaluate = function() {
|
|
101
|
+
var args, fn;
|
|
102
|
+
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
|
103
|
+
return this["native"].evaluate("function() { return " + (this.stringifyCall(fn, args)) + " }");
|
|
104
|
+
};
|
|
105
|
+
WebPage.prototype.execute = function() {
|
|
106
|
+
var args, fn;
|
|
107
|
+
fn = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
|
108
|
+
return this["native"].evaluate("function() { " + (this.stringifyCall(fn, args)) + " }");
|
|
109
|
+
};
|
|
110
|
+
WebPage.prototype.stringifyCall = function(fn, args) {
|
|
111
|
+
if (args.length === 0) {
|
|
112
|
+
return "(" + (fn.toString()) + ")()";
|
|
113
|
+
} else {
|
|
114
|
+
return "(" + (fn.toString()) + ").apply(this, JSON.parse(" + (JSON.stringify(JSON.stringify(args))) + "))";
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
WebPage.prototype.bindCallback = function(name) {
|
|
118
|
+
var that;
|
|
119
|
+
that = this;
|
|
120
|
+
return this["native"][name] = function() {
|
|
121
|
+
var result;
|
|
122
|
+
if (that[name + 'Native'] != null) {
|
|
123
|
+
result = that[name + 'Native'].apply(that, arguments);
|
|
124
|
+
}
|
|
125
|
+
if (result !== false && (that[name] != null)) {
|
|
126
|
+
return that[name].apply(that, arguments);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
WebPage.prototype.runCommand = function(name, arguments) {
|
|
131
|
+
return this.evaluate(function(name, arguments) {
|
|
132
|
+
return __poltergeist[name].apply(__poltergeist, arguments);
|
|
133
|
+
}, name, arguments);
|
|
134
|
+
};
|
|
135
|
+
return WebPage;
|
|
136
|
+
}).call(this);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class Poltergeist.Connection
|
|
2
|
+
constructor: (@owner, @port) ->
|
|
3
|
+
@socket = new WebSocket "ws://127.0.0.1:#{@port}/"
|
|
4
|
+
@socket.onmessage = this.commandReceived
|
|
5
|
+
@socket.onclose = -> phantom.exit()
|
|
6
|
+
|
|
7
|
+
commandReceived: (message) =>
|
|
8
|
+
@owner.runCommand(JSON.parse(message.data))
|
|
9
|
+
|
|
10
|
+
send: (message) ->
|
|
11
|
+
@socket.send(JSON.stringify(message))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
if phantom.version.major < 1 || phantom.version.minor < 3
|
|
2
|
+
console.log "Poltergeist requires a PhantomJS version of at least 1.3"
|
|
3
|
+
phantom.exit(1)
|
|
4
|
+
|
|
5
|
+
class Poltergeist
|
|
6
|
+
constructor: (port) ->
|
|
7
|
+
@browser = new Poltergeist.Browser(this)
|
|
8
|
+
@connection = new Poltergeist.Connection(this, port)
|
|
9
|
+
|
|
10
|
+
runCommand: (command) ->
|
|
11
|
+
try
|
|
12
|
+
@browser[command.name].apply(@browser, command.args)
|
|
13
|
+
catch error
|
|
14
|
+
@connection.send({ error: error.toString() })
|
|
15
|
+
|
|
16
|
+
sendResponse: (response) ->
|
|
17
|
+
@connection.send({ response: response })
|
|
18
|
+
|
|
19
|
+
class Poltergeist.ObsoleteNode
|
|
20
|
+
toString: -> "Poltergeist.ObsoleteNode"
|
|
21
|
+
|
|
22
|
+
phantom.injectJs('web_page.js')
|
|
23
|
+
phantom.injectJs('node.js')
|
|
24
|
+
phantom.injectJs('connection.js')
|
|
25
|
+
phantom.injectJs('browser.js')
|
|
26
|
+
|
|
27
|
+
new Poltergeist(phantom.args[0])
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Proxy object for forwarding method calls to the node object inside the page.
|
|
2
|
+
|
|
3
|
+
class Poltergeist.Node
|
|
4
|
+
@DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'removeAttribute',
|
|
5
|
+
'isMultiple', 'select', 'tagName', 'isVisible', 'position', 'trigger', 'parentId']
|
|
6
|
+
|
|
7
|
+
constructor: (@page, @id) ->
|
|
8
|
+
|
|
9
|
+
parent: ->
|
|
10
|
+
new Poltergeist.Node(@page, this.parentId())
|
|
11
|
+
|
|
12
|
+
isObsolete: ->
|
|
13
|
+
@page.nodeCall(@id, 'isObsolete')
|
|
14
|
+
|
|
15
|
+
for name in @DELEGATES
|
|
16
|
+
do (name) =>
|
|
17
|
+
this.prototype[name] = (arguments...) ->
|
|
18
|
+
if this.isObsolete()
|
|
19
|
+
throw new Poltergeist.ObsoleteNode
|
|
20
|
+
else
|
|
21
|
+
@page.nodeCall(@id, name, arguments)
|
|
22
|
+
|
|
23
|
+
scrollIntoView: ->
|
|
24
|
+
viewport = @page.viewport()
|
|
25
|
+
size = @page.documentSize()
|
|
26
|
+
pos = this.position()
|
|
27
|
+
|
|
28
|
+
scroll = { left: viewport.left, top: viewport.top }
|
|
29
|
+
|
|
30
|
+
unless viewport.left <= pos.x < viewport.right
|
|
31
|
+
scroll.left = Math.min(pos.x, size.width - viewport.width)
|
|
32
|
+
|
|
33
|
+
unless viewport.top <= pos.y < viewport.bottom
|
|
34
|
+
scroll.top = Math.min(pos.y, size.height - viewport.height)
|
|
35
|
+
|
|
36
|
+
if scroll.left != viewport.left || scroll.top != viewport.top
|
|
37
|
+
@page.setScrollPosition(scroll)
|
|
38
|
+
|
|
39
|
+
position: this.relativePosition(pos, scroll),
|
|
40
|
+
scroll: scroll
|
|
41
|
+
|
|
42
|
+
relativePosition: (position, scroll) ->
|
|
43
|
+
x: position.x - scroll.left
|
|
44
|
+
y: position.y - scroll.top
|
|
45
|
+
|
|
46
|
+
click: ->
|
|
47
|
+
position = this.scrollIntoView().position
|
|
48
|
+
@page.sendEvent('click', position.x, position.y)
|
|
49
|
+
|
|
50
|
+
dragTo: (other) ->
|
|
51
|
+
{ position, scroll } = this.scrollIntoView()
|
|
52
|
+
otherPosition = this.relativePosition(other.position(), scroll)
|
|
53
|
+
|
|
54
|
+
@page.sendEvent('mousedown', position.x, position.y)
|
|
55
|
+
@page.sendEvent('mousemove', otherPosition.x, otherPosition.y)
|
|
56
|
+
@page.sendEvent('mouseup', otherPosition.x, otherPosition.y)
|