terminus 0.4.0 → 0.5.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.
Files changed (48) hide show
  1. data/bin/terminus +5 -5
  2. data/lib/capybara/driver/terminus.rb +24 -13
  3. data/lib/terminus.rb +21 -15
  4. data/lib/terminus/application.rb +6 -6
  5. data/lib/terminus/browser.rb +77 -60
  6. data/lib/terminus/client.rb +33 -16
  7. data/lib/terminus/client/browser.rb +10 -9
  8. data/lib/terminus/client/phantom.js +25 -3
  9. data/lib/terminus/client/phantomjs.rb +44 -7
  10. data/lib/terminus/connector.rb +2 -2
  11. data/lib/terminus/connector/server.rb +15 -15
  12. data/lib/terminus/connector/socket_handler.rb +11 -11
  13. data/lib/terminus/controller.rb +62 -26
  14. data/lib/terminus/headers.rb +25 -0
  15. data/lib/terminus/host.rb +6 -6
  16. data/lib/terminus/node.rb +36 -22
  17. data/lib/terminus/proxy.rb +81 -45
  18. data/lib/terminus/proxy/driver_body.rb +14 -15
  19. data/lib/terminus/proxy/external.rb +12 -6
  20. data/lib/terminus/proxy/rewrite.rb +7 -6
  21. data/lib/terminus/public/compiled/terminus-min.js +3 -3
  22. data/lib/terminus/public/compiled/terminus.js +225 -180
  23. data/lib/terminus/public/pathology.js +87 -87
  24. data/lib/terminus/public/terminus.js +138 -93
  25. data/lib/terminus/server.rb +7 -7
  26. data/lib/terminus/timeouts.rb +8 -8
  27. data/lib/terminus/views/bootstrap.erb +7 -7
  28. data/lib/terminus/views/index.erb +4 -4
  29. data/lib/terminus/views/infinite.html +4 -4
  30. data/spec/1.1/reports/android.txt +874 -0
  31. data/spec/{reports → 1.1/reports}/chrome.txt +72 -69
  32. data/spec/{reports → 1.1/reports}/firefox.txt +72 -69
  33. data/spec/{reports → 1.1/reports}/opera.txt +72 -69
  34. data/spec/{reports → 1.1/reports}/phantomjs.txt +72 -69
  35. data/spec/{reports → 1.1/reports}/safari.txt +72 -69
  36. data/spec/{spec_helper.rb → 1.1/spec_helper.rb} +5 -2
  37. data/spec/{terminus_driver_spec.rb → 1.1/terminus_driver_spec.rb} +2 -2
  38. data/spec/{terminus_session_spec.rb → 1.1/terminus_session_spec.rb} +2 -2
  39. data/spec/2.0/reports/android.txt +815 -0
  40. data/spec/2.0/reports/chrome.txt +806 -0
  41. data/spec/2.0/reports/firefox.txt +812 -0
  42. data/spec/2.0/reports/opera.txt +806 -0
  43. data/spec/2.0/reports/phantomjs.txt +803 -0
  44. data/spec/2.0/reports/safari.txt +806 -0
  45. data/spec/2.0/spec_helper.rb +21 -0
  46. data/spec/2.0/terminus_spec.rb +25 -0
  47. metadata +41 -32
  48. data/spec/reports/android.txt +0 -875
@@ -1,28 +1,28 @@
1
1
  Terminus = {
2
2
  isIE: /\bMSIE\b/.test(navigator.userAgent),
3
-
3
+
4
4
  connect: function(host, port) {
5
5
  if (this._bayeux) return;
6
-
6
+
7
7
  this._host = host;
8
8
  this._pageId = Faye.random();
9
9
  this._id = window.name = window.name || document.name || Faye.random();
10
10
  this._id = this._id.split('|')[0];
11
-
11
+
12
12
  var iframes = document.getElementsByTagName('iframe'), i = iframes.length;
13
13
  while (i--)
14
14
  iframes[i].contentDocument.name = iframes[i].id;
15
-
15
+
16
16
  this.Registry.initialize();
17
17
  this.Worker.initialize();
18
18
  this.AjaxMonitor.initialize();
19
-
19
+
20
20
  Faye.Event.on(window, 'beforeunload', function() { Terminus.disabled = true });
21
-
21
+
22
22
  var endpoint = 'http://' + host + ':' + port + '/messaging',
23
23
  bayeux = this._bayeux = new Faye.Client(endpoint),
24
24
  self = this;
25
-
25
+
26
26
  bayeux.addExtension({
27
27
  outgoing: function(message, callback) {
28
28
  message.href = window.location.href;
@@ -30,16 +30,16 @@ Terminus = {
30
30
  callback(message);
31
31
  }
32
32
  });
33
-
33
+
34
34
  this.getId(function(id) {
35
35
  var url = window.name.split('|')[1];
36
-
36
+
37
37
  if (!url)
38
38
  bayeux.subscribe('/terminus/sockets/' + id, function(message) {
39
39
  window.name += '|' + message.url;
40
40
  this.openSocket(message.url);
41
41
  }, this);
42
-
42
+
43
43
  var sub = bayeux.subscribe('/terminus/clients/' + id, this.handleMessage, this);
44
44
  sub.callback(function() {
45
45
  this.ping();
@@ -47,7 +47,7 @@ Terminus = {
47
47
  }, this);
48
48
  }, this);
49
49
  },
50
-
50
+
51
51
  browserDetails: function(callback, context) {
52
52
  this.getId(function(id) {
53
53
  callback.call(context, {
@@ -61,11 +61,11 @@ Terminus = {
61
61
  });
62
62
  }, this);
63
63
  },
64
-
64
+
65
65
  getId: function(callback, context) {
66
66
  var id = this._id;
67
67
  if (this.isIE) return callback.call(context, id);
68
-
68
+
69
69
  if (opener && opener.Terminus) {
70
70
  opener.Terminus.getId(function(prefix) {
71
71
  callback.call(context, prefix + '/' + id);
@@ -82,14 +82,14 @@ Terminus = {
82
82
  callback.call(context, id);
83
83
  }
84
84
  },
85
-
85
+
86
86
  openSocket: function(endpoint) {
87
87
  if (this.disabled || this._socket) return;
88
-
88
+
89
89
  var self = this,
90
90
  WS = window.MozWebSocket || window.WebSocket,
91
91
  ws = new WS(endpoint);
92
-
92
+
93
93
  ws.onopen = function() {
94
94
  self._socket = ws;
95
95
  up = true;
@@ -106,17 +106,17 @@ Terminus = {
106
106
  self.handleMessage(JSON.parse(event.data));
107
107
  };
108
108
  },
109
-
109
+
110
110
  ping: function() {
111
111
  if (this.disabled) return;
112
-
112
+
113
113
  this.browserDetails(function(details) {
114
114
  this._bayeux.publish('/terminus/ping', details);
115
115
  var self = this;
116
116
  setTimeout(function() { self.ping() }, 3000);
117
117
  }, this);
118
118
  },
119
-
119
+
120
120
  handleMessage: function(message) {
121
121
  var command = message.command,
122
122
  method = command.shift(),
@@ -124,24 +124,24 @@ Terminus = {
124
124
  worker = this.Worker,
125
125
  posted = false,
126
126
  self = this;
127
-
127
+
128
128
  command.push(function(result) {
129
129
  if (posted) return;
130
130
  self.postResult(message.commandId, result);
131
131
  posted = true;
132
132
  });
133
-
133
+
134
134
  worker.monitor = true;
135
135
  driver[method].apply(driver, command);
136
136
  worker.monitor = false;
137
137
  },
138
-
138
+
139
139
  postResult: function(commandId, result) {
140
140
  if (this.disabled || !commandId) return;
141
-
141
+
142
142
  if (this._socket)
143
143
  return this._socket.send(JSON.stringify({value: result}));
144
-
144
+
145
145
  this.getId(function(id) {
146
146
  this._bayeux.publish('/terminus/results', {
147
147
  id: id,
@@ -150,118 +150,154 @@ Terminus = {
150
150
  });
151
151
  }, this);
152
152
  },
153
-
153
+
154
154
  getAttribute: function(node, name) {
155
155
  return Terminus.isIE ? (node.getAttributeNode(name) || {}).nodeValue || false
156
156
  : node.getAttribute(name);
157
157
  },
158
-
158
+
159
+ hideNodes: function(root, list) {
160
+ if (!root) return list;
161
+ list = list || [];
162
+
163
+ var isScript = (root.tagName || '').toLowerCase() === 'script',
164
+ isHidden = (root.style || {}).display === 'none';
165
+
166
+ if (isScript || isHidden) {
167
+ var parent = root.parentNode, next = root.nextSibling;
168
+ if (!isScript) list.push([root, parent, next]);
169
+ if (parent) parent.removeChild(root);
170
+ } else {
171
+ var children = root.childNodes || [];
172
+ for (var i = 0, n = children.length; i < n; i++) {
173
+ this.hideNodes(children[i], list);
174
+ }
175
+ }
176
+ return list;
177
+ },
178
+
179
+ showNodes: function(hidden) {
180
+ var hide, node, parent, next;
181
+ for (var i = 0, n = hidden.length; i < n; i++) {
182
+ hide = hidden[i];
183
+ node = hide[0];
184
+ parent = hide[1];
185
+ next = hide[2];
186
+
187
+ if (!parent) continue;
188
+ if (next) parent.insertBefore(node, next);
189
+ else parent.appendChild(node);
190
+ }
191
+ },
192
+
159
193
  Driver: {
160
194
  _node: function(id) {
161
195
  return Terminus.Registry.get(id);
162
196
  },
163
-
197
+
164
198
  attribute: function(nodeId, name, callback) {
165
199
  var node = this._node(nodeId);
166
200
  if (!node) return callback(null);
167
-
201
+
168
202
  if (!Terminus.isIE && (name === 'checked' || name === 'selected')) {
169
203
  callback(!!node[name]);
204
+ } else if (node.tagName.toLowerCase() === 'textarea' && name === 'type') {
205
+ callback('textarea');
170
206
  } else {
171
207
  callback(Terminus.getAttribute(node, name));
172
208
  }
173
209
  },
174
-
210
+
175
211
  set_attribute: function(nodeId, name, value, callback) {
176
212
  var node = this._node(nodeId);
177
213
  if (!node) return callback(null);
178
214
  node.setAttribute(name, value);
179
215
  callback(true);
180
216
  },
181
-
217
+
182
218
  body: function(callback) {
183
219
  var html = document.getElementsByTagName('html')[0];
184
220
  callback(html.outerHTML ||
185
221
  '<html>\n' + html.innerHTML + '\n</html>\n');
186
222
  },
187
-
223
+
188
224
  clear_cookies: function(callback) {
189
225
  var cookies = document.cookie.split(';'), name;
190
-
226
+
191
227
  var expiry = new Date();
192
228
  expiry.setTime(expiry.getTime() - 24*60*60*1000);
193
-
229
+
194
230
  for (var i = 0, n = cookies.length; i < n; i++) {
195
231
  name = cookies[i].split('=')[0];
196
232
  document.cookie = name + '=; expires=' + expiry.toGMTString() + '; path=/';
197
233
  }
198
234
  callback(true);
199
235
  },
200
-
236
+
201
237
  click: function(nodeId, options, callback) {
202
238
  var element = this._node(nodeId),
203
239
  timeout = options.resynchronization_timeout;
204
-
240
+
205
241
  if (!element) return callback(true);
206
-
242
+
207
243
  Syn.trigger('click', {}, element);
208
-
244
+
209
245
  if (options.resynchronize === false) return callback(true);
210
-
246
+
211
247
  if (timeout)
212
248
  Terminus.Worker._setTimeout.call(window, function() {
213
249
  callback('failed to resynchronize, ajax request timed out');
214
250
  }, 1000 * timeout);
215
-
251
+
216
252
  Terminus.Worker.callback(function() {
217
253
  callback(true);
218
254
  });
219
255
  },
220
-
256
+
221
257
  current_url: function(callback) {
222
258
  Terminus.browserDetails(function(details) {
223
259
  callback(details.url);
224
260
  });
225
261
  },
226
-
262
+
227
263
  drag: function(options, callback) {
228
264
  var draggable = this._node(options.from),
229
265
  droppable = this._node(options.to);
230
-
266
+
231
267
  if (!draggable || !droppable) return callback(null);
232
-
268
+
233
269
  Syn.drag({to: droppable}, draggable, function() {
234
270
  callback(true);
235
271
  });
236
272
  },
237
-
273
+
238
274
  evaluate: function(expression, callback) {
239
275
  callback(eval(expression));
240
276
  },
241
-
277
+
242
278
  execute: function(expression, callback) {
243
279
  eval(expression);
244
280
  callback(true);
245
281
  },
246
-
282
+
247
283
  find: function(xpath, nodeId, callback) {
248
284
  var root = nodeId ? this._node(nodeId) : document;
249
285
  if (!root) return callback([]);
250
-
286
+
251
287
  var result = document.evaluate(xpath, root, null, XPathResult.ANY_TYPE, null),
252
288
  list = [],
253
289
  element;
254
-
290
+
255
291
  while (element = result.iterateNext())
256
292
  list.push(Terminus.Registry.put(element));
257
-
293
+
258
294
  return callback(list);
259
295
  },
260
-
296
+
261
297
  is_visible: function(nodeId, callback) {
262
298
  var node = this._node(nodeId);
263
299
  if (!node) return callback(null);
264
-
300
+
265
301
  while (node.tagName && node.tagName.toLowerCase() !== 'body') {
266
302
  if (node.style.display === 'none' || node.type === 'hidden')
267
303
  return callback(false);
@@ -269,7 +305,7 @@ Terminus = {
269
305
  }
270
306
  callback(true);
271
307
  },
272
-
308
+
273
309
  select: function(nodeId, callback) {
274
310
  var option = this._node(nodeId);
275
311
  if (!option) return callback(null);
@@ -277,17 +313,17 @@ Terminus = {
277
313
  Syn.trigger('change', {}, option.parentNode);
278
314
  callback(true);
279
315
  },
280
-
316
+
281
317
  set: function(nodeId, value, callback) {
282
318
  var field = this._node(nodeId),
283
319
  max = Terminus.getAttribute(field, 'maxlength');
284
-
320
+
285
321
  if (!field) return callback(null);
286
322
  if (field.type === 'file') return callback('not_allowed');
287
-
323
+
288
324
  Syn.trigger('focus', {}, field);
289
325
  Syn.trigger('click', {}, field);
290
-
326
+
291
327
  switch (typeof value) {
292
328
  case 'string':
293
329
  if (max) value = value.substr(0, parseInt(max));
@@ -300,33 +336,38 @@ Terminus = {
300
336
  Syn.trigger('change', {}, field);
301
337
  callback(true);
302
338
  },
303
-
339
+
304
340
  tag_name: function(nodeId, callback) {
305
341
  var node = this._node(nodeId);
306
342
  if (!node) return callback(null);
307
343
  callback(node.tagName.toLowerCase());
308
344
  },
309
-
345
+
310
346
  text: function(nodeId, callback) {
311
347
  var node = this._node(nodeId);
312
348
  if (!node) return callback(null);
313
-
314
- var text = node.textContent || node.innerText || '',
315
- scripts = node.getElementsByTagName('script'),
316
- i = scripts.length;
317
-
318
- while (i--) text = text.replace(scripts[i].textContent || scripts[i].innerText, '');
319
- text = text.replace(/^\s*|\s*$/g, '');
349
+
350
+ var hidden = Terminus.hideNodes(node),
351
+ title = document.title;
352
+
353
+ document.title = '';
354
+
355
+ var text = node.textContent || node.innerText || '';
356
+
357
+ document.title = title;
358
+ Terminus.showNodes(hidden);
359
+
360
+ text = text.replace(/^\s*|\s*$/g, '').replace(/\s+/g, ' ');
320
361
  callback(text);
321
362
  },
322
-
363
+
323
364
  trigger: function(nodeId, eventType, callback) {
324
365
  var node = this._node(nodeId);
325
366
  if (!node) return callback(null);
326
367
  Syn.trigger(eventType, {}, node);
327
368
  callback(true);
328
369
  },
329
-
370
+
330
371
  unselect: function(nodeId, callback) {
331
372
  var option = this._node(nodeId);
332
373
  if (!option) return callback(null);
@@ -335,35 +376,35 @@ Terminus = {
335
376
  Syn.trigger('change', {}, option.parentNode);
336
377
  callback(true);
337
378
  },
338
-
379
+
339
380
  value: function(nodeId, callback) {
340
381
  var node = this._node(nodeId);
341
382
  if (!node) return callback(null);
342
-
383
+
343
384
  if (node.tagName.toLowerCase() !== 'select' || !node.multiple)
344
385
  return callback(node.value);
345
-
386
+
346
387
  var options = node.children,
347
388
  values = [];
348
-
389
+
349
390
  for (var i = 0, n = options.length; i < n; i++) {
350
391
  if (options[i].selected) values.push(options[i].value);
351
392
  }
352
393
  callback(values);
353
394
  },
354
-
395
+
355
396
  visit: function(url, callback) {
356
397
  window.location.href = url;
357
398
  callback(url);
358
399
  }
359
400
  },
360
-
401
+
361
402
  Registry: {
362
403
  initialize: function() {
363
404
  this._namespace = new Faye.Namespace();
364
405
  this._elements = {};
365
406
  },
366
-
407
+
367
408
  get: function(id) {
368
409
  var node = this._elements[id], root = node;
369
410
  while (root && root.tagName !== 'BODY' && root.tagName !== 'HTML')
@@ -371,22 +412,26 @@ Terminus = {
371
412
  if (!root) return null;
372
413
  return node;
373
414
  },
374
-
415
+
375
416
  put: function(element) {
376
- var id = this._namespace.generate();
417
+ var id = element['data-terminus-id'];
418
+ if (!id) {
419
+ id = this._namespace.generate();
420
+ element['data-terminus-id'] = id;
421
+ }
377
422
  this._elements[id] = element;
378
423
  return id;
379
424
  }
380
425
  },
381
-
426
+
382
427
  Worker: {
383
428
  initialize: function() {
384
429
  this._callbacks = [];
385
430
  this._pending = 0;
386
-
431
+
387
432
  if (!Terminus.isIE) this._wrapTimeouts();
388
433
  },
389
-
434
+
390
435
  callback: function(callback, scope) {
391
436
  if (this._pending === 0) {
392
437
  if (this._setTimeout)
@@ -397,16 +442,16 @@ Terminus = {
397
442
  this._callbacks.push([callback, scope]);
398
443
  }
399
444
  },
400
-
445
+
401
446
  suspend: function() {
402
447
  this._pending += 1;
403
448
  },
404
-
449
+
405
450
  resume: function() {
406
451
  if (this._pending === 0) return;
407
452
  this._pending -= 1;
408
453
  if (this._pending !== 0) return;
409
-
454
+
410
455
  var callback;
411
456
  for (var i = 0, n = this._callbacks.length; i < n; i++) {
412
457
  callback = this._callbacks[i];
@@ -414,19 +459,19 @@ Terminus = {
414
459
  }
415
460
  this._callbacks = [];
416
461
  },
417
-
462
+
418
463
  _wrapTimeouts: function() {
419
464
  var timeout = window.setTimeout,
420
465
  clear = window.clearTimeout,
421
466
  timeouts = {},
422
467
  self = this;
423
-
468
+
424
469
  var finish = function(id) {
425
470
  if (!timeouts.hasOwnProperty(id)) return;
426
471
  delete timeouts[id];
427
472
  self.resume();
428
473
  };
429
-
474
+
430
475
  window.setTimeout = function(callback, delay) {
431
476
  var id = timeout.call(window, function() {
432
477
  try {
@@ -438,35 +483,35 @@ Terminus = {
438
483
  finish(id);
439
484
  }
440
485
  }, delay);
441
-
486
+
442
487
  if (self.monitor) {
443
488
  timeouts[id] = true;
444
489
  self.suspend();
445
490
  }
446
491
  return id;
447
492
  };
448
-
493
+
449
494
  window.clearTimeout = function(id) {
450
495
  finish(id);
451
496
  return clear(id);
452
497
  };
453
-
498
+
454
499
  this._setTimeout = timeout;
455
500
  }
456
501
  },
457
-
502
+
458
503
  AjaxMonitor: {
459
504
  initialize: function() {
460
505
  if (window.jQuery) this._patchJquery();
461
506
  },
462
-
507
+
463
508
  _patchJquery: function() {
464
509
  var ajax = jQuery.ajax;
465
510
  jQuery.ajax = function(url, settings) {
466
511
  var options = ((typeof url === 'string') ? settings : url) || {},
467
512
  complete = options.complete,
468
513
  monitor = Terminus.Worker.monitor;
469
-
514
+
470
515
  options.complete = function() {
471
516
  var result;
472
517
  try {
@@ -476,9 +521,9 @@ Terminus = {
476
521
  }
477
522
  return result;
478
523
  };
479
-
524
+
480
525
  if (monitor) Terminus.Worker.suspend();
481
-
526
+
482
527
  if (typeof url === 'string')
483
528
  return ajax.call(jQuery, url, options);
484
529
  else