terminus 0.3.0 → 0.4.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.rdoc +25 -19
- data/bin/terminus +1 -1
- data/lib/capybara/driver/terminus.rb +28 -17
- data/lib/terminus.rb +29 -15
- data/lib/terminus/application.rb +5 -11
- data/lib/terminus/browser.rb +97 -44
- data/lib/terminus/client.rb +43 -0
- data/lib/terminus/client/browser.rb +30 -0
- data/lib/terminus/client/phantom.js +6 -0
- data/lib/terminus/client/phantomjs.rb +20 -0
- data/lib/terminus/connector.rb +9 -0
- data/lib/terminus/connector/server.rb +142 -0
- data/lib/terminus/connector/socket_handler.rb +72 -0
- data/lib/terminus/controller.rb +22 -5
- data/lib/terminus/host.rb +7 -2
- data/lib/terminus/node.rb +11 -5
- data/lib/terminus/proxy.rb +35 -1
- data/lib/terminus/proxy/driver_body.rb +26 -6
- data/lib/terminus/proxy/external.rb +9 -0
- data/lib/terminus/proxy/rewrite.rb +4 -2
- data/lib/terminus/public/compiled/terminus-min.js +3 -0
- data/lib/terminus/public/compiled/terminus.js +5270 -0
- data/lib/terminus/public/loader.js +1 -1
- data/lib/terminus/public/pathology.js +3174 -0
- data/lib/terminus/public/syn/browsers.js +2 -2
- data/lib/terminus/public/syn/drag/drag.js +3 -4
- data/lib/terminus/public/syn/key.js +130 -111
- data/lib/terminus/public/syn/mouse.js +2 -2
- data/lib/terminus/public/syn/synthetic.js +45 -34
- data/lib/terminus/public/terminus.js +183 -70
- data/lib/terminus/server.rb +6 -0
- data/lib/terminus/timeouts.rb +4 -2
- data/lib/terminus/views/bootstrap.erb +12 -27
- data/lib/terminus/views/index.erb +1 -1
- data/spec/reports/android.txt +875 -0
- data/spec/reports/chrome.txt +137 -8
- data/spec/reports/firefox.txt +137 -9
- data/spec/reports/opera.txt +142 -13
- data/spec/reports/phantomjs.txt +871 -0
- data/spec/reports/safari.txt +137 -8
- data/spec/spec_helper.rb +19 -17
- data/spec/terminus_driver_spec.rb +8 -6
- data/spec/terminus_session_spec.rb +4 -4
- metadata +209 -117
@@ -1,11 +1,17 @@
|
|
1
1
|
Terminus = {
|
2
2
|
isIE: /\bMSIE\b/.test(navigator.userAgent),
|
3
3
|
|
4
|
-
connect: function(
|
5
|
-
if (this.
|
4
|
+
connect: function(host, port) {
|
5
|
+
if (this._bayeux) return;
|
6
6
|
|
7
|
+
this._host = host;
|
7
8
|
this._pageId = Faye.random();
|
8
|
-
this._id = window.name = window.name || Faye.random();
|
9
|
+
this._id = window.name = window.name || document.name || Faye.random();
|
10
|
+
this._id = this._id.split('|')[0];
|
11
|
+
|
12
|
+
var iframes = document.getElementsByTagName('iframe'), i = iframes.length;
|
13
|
+
while (i--)
|
14
|
+
iframes[i].contentDocument.name = iframes[i].id;
|
9
15
|
|
10
16
|
this.Registry.initialize();
|
11
17
|
this.Worker.initialize();
|
@@ -13,67 +19,141 @@ Terminus = {
|
|
13
19
|
|
14
20
|
Faye.Event.on(window, 'beforeunload', function() { Terminus.disabled = true });
|
15
21
|
|
16
|
-
var
|
22
|
+
var endpoint = 'http://' + host + ':' + port + '/messaging',
|
23
|
+
bayeux = this._bayeux = new Faye.Client(endpoint),
|
24
|
+
self = this;
|
25
|
+
|
26
|
+
bayeux.addExtension({
|
27
|
+
outgoing: function(message, callback) {
|
28
|
+
message.href = window.location.href;
|
29
|
+
if (message.connectionType === 'websocket') self._socketCapable = true;
|
30
|
+
callback(message);
|
31
|
+
}
|
32
|
+
});
|
17
33
|
|
18
|
-
|
19
|
-
var
|
20
|
-
method = command.shift(),
|
21
|
-
driver = this.Driver,
|
22
|
-
worker = this.Worker,
|
23
|
-
posted = false,
|
24
|
-
self = this;
|
34
|
+
this.getId(function(id) {
|
35
|
+
var url = window.name.split('|')[1];
|
25
36
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
37
|
+
if (!url)
|
38
|
+
bayeux.subscribe('/terminus/sockets/' + id, function(message) {
|
39
|
+
window.name += '|' + message.url;
|
40
|
+
this.openSocket(message.url);
|
41
|
+
}, this);
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
|
43
|
+
var sub = bayeux.subscribe('/terminus/clients/' + id, this.handleMessage, this);
|
44
|
+
sub.callback(function() {
|
45
|
+
this.ping();
|
46
|
+
if (url) this.openSocket(url);
|
47
|
+
}, this);
|
35
48
|
}, this);
|
36
|
-
|
37
|
-
subscription.callback(this.ping, this);
|
38
49
|
},
|
39
50
|
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
browserDetails: function(callback, context) {
|
52
|
+
this.getId(function(id) {
|
53
|
+
callback.call(context, {
|
54
|
+
host: this._host,
|
55
|
+
id: id,
|
56
|
+
infinite: !!window.TERMINUS_INFINITE_REDIRECT,
|
57
|
+
page: this._pageId,
|
58
|
+
sockets: this._socketCapable,
|
59
|
+
ua: navigator.userAgent,
|
60
|
+
url: window.location.href
|
61
|
+
});
|
62
|
+
}, this);
|
43
63
|
},
|
44
64
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
}
|
65
|
+
getId: function(callback, context) {
|
66
|
+
var id = this._id;
|
67
|
+
if (this.isIE) return callback.call(context, id);
|
68
|
+
|
69
|
+
if (opener && opener.Terminus) {
|
70
|
+
opener.Terminus.getId(function(prefix) {
|
71
|
+
callback.call(context, prefix + '/' + id);
|
72
|
+
});
|
73
|
+
} else if (parent && parent !== window) {
|
74
|
+
var getParentId = function() {
|
75
|
+
if (!parent.Terminus) return setTimeout(getParentId, 100);
|
76
|
+
parent.Terminus.getId(function(prefix) {
|
77
|
+
callback.call(context, prefix + '/' + id);
|
78
|
+
});
|
79
|
+
};
|
80
|
+
getParentId();
|
81
|
+
} else {
|
82
|
+
callback.call(context, id);
|
83
|
+
}
|
54
84
|
},
|
55
85
|
|
56
|
-
|
57
|
-
|
58
|
-
|
86
|
+
openSocket: function(endpoint) {
|
87
|
+
if (this.disabled || this._socket) return;
|
88
|
+
|
89
|
+
var self = this,
|
90
|
+
WS = window.MozWebSocket || window.WebSocket,
|
91
|
+
ws = new WS(endpoint);
|
92
|
+
|
93
|
+
ws.onopen = function() {
|
94
|
+
self._socket = ws;
|
95
|
+
up = true;
|
96
|
+
};
|
97
|
+
ws.onclose = function() {
|
98
|
+
var up = !!self._socket;
|
99
|
+
self._socket = null;
|
100
|
+
if (up)
|
101
|
+
self.openSocket(endpoint);
|
102
|
+
else
|
103
|
+
window.name = window.name.split('|')[0];
|
104
|
+
};
|
105
|
+
ws.onmessage = function(event) {
|
106
|
+
self.handleMessage(JSON.parse(event.data));
|
107
|
+
};
|
59
108
|
},
|
60
109
|
|
61
110
|
ping: function() {
|
62
111
|
if (this.disabled) return;
|
63
112
|
|
64
|
-
this.
|
65
|
-
|
66
|
-
|
113
|
+
this.browserDetails(function(details) {
|
114
|
+
this._bayeux.publish('/terminus/ping', details);
|
115
|
+
var self = this;
|
116
|
+
setTimeout(function() { self.ping() }, 3000);
|
117
|
+
}, this);
|
118
|
+
},
|
119
|
+
|
120
|
+
handleMessage: function(message) {
|
121
|
+
var command = message.command,
|
122
|
+
method = command.shift(),
|
123
|
+
driver = this.Driver,
|
124
|
+
worker = this.Worker,
|
125
|
+
posted = false,
|
126
|
+
self = this;
|
127
|
+
|
128
|
+
command.push(function(result) {
|
129
|
+
if (posted) return;
|
130
|
+
self.postResult(message.commandId, result);
|
131
|
+
posted = true;
|
132
|
+
});
|
133
|
+
|
134
|
+
worker.monitor = true;
|
135
|
+
driver[method].apply(driver, command);
|
136
|
+
worker.monitor = false;
|
67
137
|
},
|
68
138
|
|
69
139
|
postResult: function(commandId, result) {
|
70
140
|
if (this.disabled || !commandId) return;
|
71
141
|
|
72
|
-
this.
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
142
|
+
if (this._socket)
|
143
|
+
return this._socket.send(JSON.stringify({value: result}));
|
144
|
+
|
145
|
+
this.getId(function(id) {
|
146
|
+
this._bayeux.publish('/terminus/results', {
|
147
|
+
id: id,
|
148
|
+
commandId: commandId,
|
149
|
+
result: result
|
150
|
+
});
|
151
|
+
}, this);
|
152
|
+
},
|
153
|
+
|
154
|
+
getAttribute: function(node, name) {
|
155
|
+
return Terminus.isIE ? (node.getAttributeNode(name) || {}).nodeValue || false
|
156
|
+
: node.getAttribute(name);
|
77
157
|
},
|
78
158
|
|
79
159
|
Driver: {
|
@@ -83,15 +163,18 @@ Terminus = {
|
|
83
163
|
|
84
164
|
attribute: function(nodeId, name, callback) {
|
85
165
|
var node = this._node(nodeId);
|
166
|
+
if (!node) return callback(null);
|
86
167
|
|
87
|
-
if (name === 'checked' || name === 'selected')
|
88
|
-
|
89
|
-
|
90
|
-
|
168
|
+
if (!Terminus.isIE && (name === 'checked' || name === 'selected')) {
|
169
|
+
callback(!!node[name]);
|
170
|
+
} else {
|
171
|
+
callback(Terminus.getAttribute(node, name));
|
172
|
+
}
|
91
173
|
},
|
92
174
|
|
93
175
|
set_attribute: function(nodeId, name, value, callback) {
|
94
176
|
var node = this._node(nodeId);
|
177
|
+
if (!node) return callback(null);
|
95
178
|
node.setAttribute(name, value);
|
96
179
|
callback(true);
|
97
180
|
},
|
@@ -112,29 +195,44 @@ Terminus = {
|
|
112
195
|
name = cookies[i].split('=')[0];
|
113
196
|
document.cookie = name + '=; expires=' + expiry.toGMTString() + '; path=/';
|
114
197
|
}
|
115
|
-
callback();
|
198
|
+
callback(true);
|
116
199
|
},
|
117
200
|
|
118
201
|
click: function(nodeId, options, callback) {
|
119
202
|
var element = this._node(nodeId),
|
120
203
|
timeout = options.resynchronization_timeout;
|
121
204
|
|
205
|
+
if (!element) return callback(true);
|
206
|
+
|
122
207
|
Syn.trigger('click', {}, element);
|
123
208
|
|
124
|
-
if (options.resynchronize === false) return callback();
|
209
|
+
if (options.resynchronize === false) return callback(true);
|
125
210
|
|
126
211
|
if (timeout)
|
127
212
|
Terminus.Worker._setTimeout.call(window, function() {
|
128
213
|
callback('failed to resynchronize, ajax request timed out');
|
129
214
|
}, 1000 * timeout);
|
130
215
|
|
131
|
-
Terminus.Worker.callback(
|
216
|
+
Terminus.Worker.callback(function() {
|
217
|
+
callback(true);
|
218
|
+
});
|
219
|
+
},
|
220
|
+
|
221
|
+
current_url: function(callback) {
|
222
|
+
Terminus.browserDetails(function(details) {
|
223
|
+
callback(details.url);
|
224
|
+
});
|
132
225
|
},
|
133
226
|
|
134
227
|
drag: function(options, callback) {
|
135
228
|
var draggable = this._node(options.from),
|
136
229
|
droppable = this._node(options.to);
|
137
|
-
|
230
|
+
|
231
|
+
if (!draggable || !droppable) return callback(null);
|
232
|
+
|
233
|
+
Syn.drag({to: droppable}, draggable, function() {
|
234
|
+
callback(true);
|
235
|
+
});
|
138
236
|
},
|
139
237
|
|
140
238
|
evaluate: function(expression, callback) {
|
@@ -143,11 +241,12 @@ Terminus = {
|
|
143
241
|
|
144
242
|
execute: function(expression, callback) {
|
145
243
|
eval(expression);
|
146
|
-
callback();
|
244
|
+
callback(true);
|
147
245
|
},
|
148
246
|
|
149
247
|
find: function(xpath, nodeId, callback) {
|
150
|
-
var root = this._node(nodeId)
|
248
|
+
var root = nodeId ? this._node(nodeId) : document;
|
249
|
+
if (!root) return callback([]);
|
151
250
|
|
152
251
|
var result = document.evaluate(xpath, root, null, XPathResult.ANY_TYPE, null),
|
153
252
|
list = [],
|
@@ -159,13 +258,9 @@ Terminus = {
|
|
159
258
|
return callback(list);
|
160
259
|
},
|
161
260
|
|
162
|
-
frame_src: function(name, callback) {
|
163
|
-
var frame = document.getElementById(name);
|
164
|
-
callback(frame.src);
|
165
|
-
},
|
166
|
-
|
167
261
|
is_visible: function(nodeId, callback) {
|
168
262
|
var node = this._node(nodeId);
|
263
|
+
if (!node) return callback(null);
|
169
264
|
|
170
265
|
while (node.tagName && node.tagName.toLowerCase() !== 'body') {
|
171
266
|
if (node.style.display === 'none' || node.type === 'hidden')
|
@@ -177,6 +272,7 @@ Terminus = {
|
|
177
272
|
|
178
273
|
select: function(nodeId, callback) {
|
179
274
|
var option = this._node(nodeId);
|
275
|
+
if (!option) return callback(null);
|
180
276
|
option.selected = true;
|
181
277
|
Syn.trigger('change', {}, option.parentNode);
|
182
278
|
callback(true);
|
@@ -186,6 +282,7 @@ Terminus = {
|
|
186
282
|
var field = this._node(nodeId),
|
187
283
|
max = Terminus.getAttribute(field, 'maxlength');
|
188
284
|
|
285
|
+
if (!field) return callback(null);
|
189
286
|
if (field.type === 'file') return callback('not_allowed');
|
190
287
|
|
191
288
|
Syn.trigger('focus', {}, field);
|
@@ -201,16 +298,20 @@ Terminus = {
|
|
201
298
|
break;
|
202
299
|
}
|
203
300
|
Syn.trigger('change', {}, field);
|
204
|
-
callback();
|
301
|
+
callback(true);
|
205
302
|
},
|
206
303
|
|
207
304
|
tag_name: function(nodeId, callback) {
|
208
|
-
|
305
|
+
var node = this._node(nodeId);
|
306
|
+
if (!node) return callback(null);
|
307
|
+
callback(node.tagName.toLowerCase());
|
209
308
|
},
|
210
309
|
|
211
310
|
text: function(nodeId, callback) {
|
212
|
-
var node
|
213
|
-
|
311
|
+
var node = this._node(nodeId);
|
312
|
+
if (!node) return callback(null);
|
313
|
+
|
314
|
+
var text = node.textContent || node.innerText || '',
|
214
315
|
scripts = node.getElementsByTagName('script'),
|
215
316
|
i = scripts.length;
|
216
317
|
|
@@ -221,12 +322,14 @@ Terminus = {
|
|
221
322
|
|
222
323
|
trigger: function(nodeId, eventType, callback) {
|
223
324
|
var node = this._node(nodeId);
|
325
|
+
if (!node) return callback(null);
|
224
326
|
Syn.trigger(eventType, {}, node);
|
225
|
-
callback();
|
327
|
+
callback(true);
|
226
328
|
},
|
227
329
|
|
228
330
|
unselect: function(nodeId, callback) {
|
229
331
|
var option = this._node(nodeId);
|
332
|
+
if (!option) return callback(null);
|
230
333
|
if (!option.parentNode.multiple) return callback(false);
|
231
334
|
option.selected = false;
|
232
335
|
Syn.trigger('change', {}, option.parentNode);
|
@@ -235,6 +338,8 @@ Terminus = {
|
|
235
338
|
|
236
339
|
value: function(nodeId, callback) {
|
237
340
|
var node = this._node(nodeId);
|
341
|
+
if (!node) return callback(null);
|
342
|
+
|
238
343
|
if (node.tagName.toLowerCase() !== 'select' || !node.multiple)
|
239
344
|
return callback(node.value);
|
240
345
|
|
@@ -249,7 +354,7 @@ Terminus = {
|
|
249
354
|
|
250
355
|
visit: function(url, callback) {
|
251
356
|
window.location.href = url;
|
252
|
-
callback();
|
357
|
+
callback(url);
|
253
358
|
}
|
254
359
|
},
|
255
360
|
|
@@ -260,7 +365,11 @@ Terminus = {
|
|
260
365
|
},
|
261
366
|
|
262
367
|
get: function(id) {
|
263
|
-
|
368
|
+
var node = this._elements[id], root = node;
|
369
|
+
while (root && root.tagName !== 'BODY' && root.tagName !== 'HTML')
|
370
|
+
root = root.parentNode;
|
371
|
+
if (!root) return null;
|
372
|
+
return node;
|
264
373
|
},
|
265
374
|
|
266
375
|
put: function(element) {
|
@@ -275,14 +384,18 @@ Terminus = {
|
|
275
384
|
this._callbacks = [];
|
276
385
|
this._pending = 0;
|
277
386
|
|
278
|
-
this._wrapTimeouts();
|
387
|
+
if (!Terminus.isIE) this._wrapTimeouts();
|
279
388
|
},
|
280
389
|
|
281
390
|
callback: function(callback, scope) {
|
282
|
-
if (this._pending === 0)
|
283
|
-
this._setTimeout
|
284
|
-
|
391
|
+
if (this._pending === 0) {
|
392
|
+
if (this._setTimeout)
|
393
|
+
this._setTimeout.call(window, function() { callback.call(scope) }, 0);
|
394
|
+
else
|
395
|
+
setTimeout(function() { callback.call(scope) }, 0);
|
396
|
+
} else {
|
285
397
|
this._callbacks.push([callback, scope]);
|
398
|
+
}
|
286
399
|
},
|
287
400
|
|
288
401
|
suspend: function() {
|
data/lib/terminus/server.rb
CHANGED
data/lib/terminus/timeouts.rb
CHANGED
@@ -8,12 +8,14 @@ module Terminus
|
|
8
8
|
TIMEOUT = 30
|
9
9
|
|
10
10
|
def wait_with_timeout(name, duration = TIMEOUT, &predicate)
|
11
|
-
result, time_out =
|
11
|
+
result, time_out = predicate.call, false
|
12
|
+
return result if result
|
13
|
+
|
12
14
|
add_timeout(name, duration) { time_out = true }
|
13
15
|
|
14
16
|
while !result and !time_out
|
15
17
|
result = predicate.call
|
16
|
-
sleep
|
18
|
+
sleep(0.001)
|
17
19
|
end
|
18
20
|
|
19
21
|
raise TimeoutError.new("Waited #{duration}s but could not get a #{name}") if time_out
|
@@ -1,9 +1,7 @@
|
|
1
1
|
(function() {
|
2
|
-
var faye
|
3
|
-
host
|
4
|
-
|
5
|
-
JSCLASS_PATH = host + '/js.class/';
|
6
|
-
SYN_FILES = ['synthetic', 'mouse', 'browsers', 'key', 'drag/drag'];
|
2
|
+
var faye = '<%= ::Terminus::FAYE_MOUNT %>',
|
3
|
+
host = '<%= env['SERVER_NAME'] %>',
|
4
|
+
origin = 'http://' + host + ':' + <%= Terminus.port %>;
|
7
5
|
|
8
6
|
var withPackageManager = function(callback) {
|
9
7
|
if (window.JS && JS.Packages) return callback();
|
@@ -12,13 +10,12 @@
|
|
12
10
|
head = document.getElementsByTagName('head')[0];
|
13
11
|
|
14
12
|
script.type = 'text/javascript';
|
15
|
-
script.src =
|
13
|
+
script.src = origin + '/loader.js';
|
16
14
|
|
17
15
|
script.onload = script.onreadystatechange = function() {
|
18
16
|
var state = script.readyState;
|
19
17
|
if (!state || state === 'loaded' || state === 'complete') {
|
20
18
|
script.onload = script.onreadystatechange = null;
|
21
|
-
head.removeChild(script);
|
22
19
|
callback();
|
23
20
|
}
|
24
21
|
};
|
@@ -26,29 +23,17 @@
|
|
26
23
|
};
|
27
24
|
|
28
25
|
withPackageManager(function() {
|
26
|
+
JS.cacheBust = true;
|
27
|
+
|
29
28
|
JS.Packages(function() { with(this) {
|
30
|
-
|
31
|
-
|
32
|
-
}).provides('Faye', 'Faye.Client');
|
33
|
-
|
34
|
-
loader(function(callback) {
|
35
|
-
if (!window.steal) window.steal = {then: function(fn) { fn() }};
|
36
|
-
var inject = function() {
|
37
|
-
var synFile = SYN_FILES.shift();
|
38
|
-
if (!synFile) return callback();
|
39
|
-
load(host + '/syn/' + synFile + '.js', inject);
|
40
|
-
};
|
41
|
-
inject();
|
42
|
-
|
43
|
-
}).provides('Syn');
|
44
|
-
|
45
|
-
file(host + '/terminus.js')
|
46
|
-
.requires('Faye.Client', 'document.evaluate', 'Syn')
|
47
|
-
.provides('Terminus');
|
29
|
+
file(origin + faye + '/client.js').provides('Faye', 'Faye.Client');
|
30
|
+
file(origin + '/compiled/terminus-min.js').requires('Faye.Client').provides('Terminus');
|
48
31
|
}});
|
49
32
|
|
50
|
-
JS.require('
|
51
|
-
|
33
|
+
JS.require('Faye.Client', function() {
|
34
|
+
JS.require('Terminus', function() {
|
35
|
+
Terminus.connect(host, <%= ::Terminus.port %>);
|
36
|
+
});
|
52
37
|
});
|
53
38
|
});
|
54
39
|
})();
|