terminus 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +71 -20
- data/bin/terminus +21 -9
- data/lib/capybara/driver/terminus.rb +47 -0
- data/lib/public/loader.js +1 -0
- data/lib/public/style.css +7 -2
- data/lib/public/syn.js +2355 -0
- data/lib/public/terminus.js +294 -1
- data/lib/terminus.rb +50 -6
- data/lib/terminus/application.rb +9 -4
- data/lib/terminus/browser.rb +140 -0
- data/lib/terminus/controller.rb +78 -0
- data/lib/terminus/node.rb +68 -0
- data/lib/terminus/server.rb +1 -6
- data/lib/terminus/timeouts.rb +27 -0
- data/lib/views/bookmarklet.erb +14 -4
- data/lib/views/index.erb +11 -8
- data/spec/spec_helper.rb +22 -0
- data/spec/terminus_driver_spec.rb +20 -0
- data/spec/terminus_session_spec.rb +32 -0
- metadata +105 -42
- data/lib/public/js.packages.js +0 -1
@@ -0,0 +1,78 @@
|
|
1
|
+
module Terminus
|
2
|
+
class Controller
|
3
|
+
|
4
|
+
include Timeouts
|
5
|
+
attr_accessor :dock_host
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@connected = false
|
9
|
+
@browsers = {}
|
10
|
+
@ping_callbacks = []
|
11
|
+
trap('INT') { exit }
|
12
|
+
end
|
13
|
+
|
14
|
+
def browser(id = nil)
|
15
|
+
return @browser if id.nil?
|
16
|
+
@browsers[id] ||= Browser.new(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def browser=(params)
|
20
|
+
@browser = case params
|
21
|
+
when :docked then @browsers.values.find { |b| b.docked? }
|
22
|
+
when Browser then params
|
23
|
+
when Hash then
|
24
|
+
wait_with_timeout(:named_window) do
|
25
|
+
@browsers.values.find { |b| b.name == params[:name] }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def drop_browser(browser)
|
31
|
+
@browsers.delete(browser.id)
|
32
|
+
@browser = nil if @browser == browser
|
33
|
+
end
|
34
|
+
|
35
|
+
def ensure_browser
|
36
|
+
ensure_connection
|
37
|
+
wait_with_timeout(:browser) { @browser }
|
38
|
+
end
|
39
|
+
|
40
|
+
def ensure_docked_browser
|
41
|
+
ensure_connection
|
42
|
+
wait_with_timeout(:docked_browser) { @browsers.any? { |id,b| b.docked? } }
|
43
|
+
end
|
44
|
+
|
45
|
+
def messenger
|
46
|
+
Terminus.ensure_reactor_running
|
47
|
+
return @messenger if defined?(@messenger)
|
48
|
+
|
49
|
+
@messenger = Faye::Client.new(Terminus.endpoint)
|
50
|
+
|
51
|
+
@messenger.subscribe('/terminus/ping', &method(:accept_ping))
|
52
|
+
@messenger.subscribe('/terminus/results', &method(:accept_result))
|
53
|
+
@messenger
|
54
|
+
end
|
55
|
+
|
56
|
+
def return_to_dock
|
57
|
+
@browsers.each { |id, b| b.return_to_dock }
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def accept_ping(message)
|
63
|
+
browser(message['id']).ping!(message)
|
64
|
+
end
|
65
|
+
|
66
|
+
def accept_result(message)
|
67
|
+
browser(message['id']).result!(message)
|
68
|
+
end
|
69
|
+
|
70
|
+
def ensure_connection
|
71
|
+
return if @connected
|
72
|
+
messenger.connect { @connected = true }
|
73
|
+
wait_with_timeout(:connection) { @connected }
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Terminus
|
2
|
+
class Node
|
3
|
+
|
4
|
+
attr_reader :id
|
5
|
+
|
6
|
+
def initialize(browser, id)
|
7
|
+
@browser, @id = browser, id
|
8
|
+
end
|
9
|
+
|
10
|
+
def click
|
11
|
+
page = @browser.page_id
|
12
|
+
command = @browser.tell([:click, @id])
|
13
|
+
|
14
|
+
@browser.wait_with_timeout(:click_response) do
|
15
|
+
@browser.result(command) || (@browser.page_id != page)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def drag_to(node)
|
20
|
+
@browser.ask([:drag, {:from => @id, :to => node.id}])
|
21
|
+
end
|
22
|
+
|
23
|
+
def find(xpath)
|
24
|
+
@browser.ask([:find, xpath, @id]).map { |id| Node.new(@browser, id) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def select
|
28
|
+
@browser.ask([:select, @id])
|
29
|
+
end
|
30
|
+
|
31
|
+
def set(value)
|
32
|
+
result = @browser.ask([:set, @id, value])
|
33
|
+
raise Capybara::NotSupportedByDriverError.new if result == 'not_allowed'
|
34
|
+
end
|
35
|
+
|
36
|
+
def trigger(event_type)
|
37
|
+
@browser.ask([:trigger, @id, event_type])
|
38
|
+
end
|
39
|
+
|
40
|
+
def unselect
|
41
|
+
allowed = @browser.ask([:unselect, @id])
|
42
|
+
raise Capybara::UnselectNotAllowed.new unless allowed
|
43
|
+
end
|
44
|
+
|
45
|
+
alias :select_option :select
|
46
|
+
alias :unselect_option :unselect
|
47
|
+
|
48
|
+
SYNC_DSL_METHODS = [ [:[], :attribute],
|
49
|
+
:tag_name,
|
50
|
+
:text,
|
51
|
+
:value,
|
52
|
+
[:visible?, :is_visible]
|
53
|
+
]
|
54
|
+
|
55
|
+
SYNC_DSL_METHODS.each do |method|
|
56
|
+
if Array === method
|
57
|
+
name, command = *method
|
58
|
+
else
|
59
|
+
name = command = method
|
60
|
+
end
|
61
|
+
define_method(name) do |*args|
|
62
|
+
@browser.ask([command, @id, *args])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
data/lib/terminus/server.rb
CHANGED
@@ -16,7 +16,7 @@ module Terminus
|
|
16
16
|
def run!
|
17
17
|
return if running?
|
18
18
|
handler = Rack::Handler.get('thin')
|
19
|
-
ensure_reactor_running
|
19
|
+
Terminus.ensure_reactor_running
|
20
20
|
handler.run(app, :Port => @options[:port]) { |s| @server = s }
|
21
21
|
end
|
22
22
|
|
@@ -38,11 +38,6 @@ module Terminus
|
|
38
38
|
app.get_client
|
39
39
|
end
|
40
40
|
|
41
|
-
def ensure_reactor_running!
|
42
|
-
Thread.new { EM.run unless EM.reactor_running? }
|
43
|
-
while not EM.reactor_running?; end
|
44
|
-
end
|
45
|
-
|
46
41
|
end
|
47
42
|
end
|
48
43
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Terminus
|
2
|
+
module Timeouts
|
3
|
+
|
4
|
+
class TimeoutError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
include Faye::Timeouts
|
8
|
+
TIMEOUT = 10
|
9
|
+
|
10
|
+
def wait_with_timeout(name, duration = TIMEOUT, &predicate)
|
11
|
+
result, time_out = nil, false
|
12
|
+
add_timeout(name, duration) { time_out = true }
|
13
|
+
|
14
|
+
while !result and !time_out
|
15
|
+
result = predicate.call
|
16
|
+
sleep 0.1
|
17
|
+
end
|
18
|
+
|
19
|
+
raise TimeoutError.new("Waited #{duration}s but could not get a #{name}") if time_out
|
20
|
+
remove_timeout(name)
|
21
|
+
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
data/lib/views/bookmarklet.erb
CHANGED
@@ -2,6 +2,11 @@
|
|
2
2
|
var faye = '<%= ::Terminus::FAYE_MOUNT %>',
|
3
3
|
host = '<%= host %>';
|
4
4
|
|
5
|
+
var html = document.getElementsByTagName('html')[0],
|
6
|
+
source = '<html>\n' + html.innerHTML + '\n</html>\n';
|
7
|
+
|
8
|
+
JSCLASS_PATH = host + '/js.class/';
|
9
|
+
|
5
10
|
var withPackageManager = function(callback) {
|
6
11
|
if (window.JS && JS.Packages) return callback();
|
7
12
|
|
@@ -9,7 +14,7 @@
|
|
9
14
|
head = document.getElementsByTagName('head')[0];
|
10
15
|
|
11
16
|
script.type = 'text/javascript';
|
12
|
-
script.src = host + '/
|
17
|
+
script.src = host + '/loader.js';
|
13
18
|
|
14
19
|
script.onload = script.onreadystatechange = function() {
|
15
20
|
var state = script.readyState;
|
@@ -25,14 +30,19 @@
|
|
25
30
|
withPackageManager(function() {
|
26
31
|
JS.Packages(function() {
|
27
32
|
this.file(host + faye + '/client.js')
|
28
|
-
.provides('Faye', 'Faye.Client')
|
33
|
+
.provides('Faye', 'Faye.Client')
|
34
|
+
.setup(function() { Faye.Client.prototype.MAX_DELAY = 0 });
|
35
|
+
|
36
|
+
this.file(host + '/syn.js')
|
37
|
+
.provides('Syn');
|
29
38
|
|
30
39
|
this.file(host + '/terminus.js')
|
31
|
-
.requires('Faye.Client')
|
40
|
+
.requires('Faye.Client', 'document.evaluate', 'Syn')
|
32
41
|
.provides('Terminus');
|
33
42
|
});
|
34
43
|
|
35
|
-
require('Terminus', function() {
|
44
|
+
JS.require('Terminus', function() {
|
45
|
+
Terminus.originalSource = source;
|
36
46
|
Terminus.connect(host + faye);
|
37
47
|
});
|
38
48
|
});
|
data/lib/views/index.erb
CHANGED
@@ -7,21 +7,24 @@
|
|
7
7
|
<title>Terminus</title>
|
8
8
|
</head>
|
9
9
|
<body>
|
10
|
+
<%= Terminus.driver_script request.host %>
|
10
11
|
|
11
12
|
<div class="container">
|
12
13
|
<h1><a href="javascript:<%= bookmarklet %>">Terminus</a>.</h1>
|
13
14
|
|
14
|
-
<p>
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
<p>This is the holding page. Browsers visiting this page are
|
16
|
+
connected to your Terminus server and can be taken over and
|
17
|
+
controlled by your Ruby programs.</p>
|
18
|
+
|
19
|
+
<p>That big pink title up there is a bookmarklet. Drag it up
|
20
|
+
to your bookmarks bar and use it to connect any page on the
|
21
|
+
web to your Terminus console.</p>
|
19
22
|
</div>
|
20
23
|
|
21
24
|
<div class="footer">
|
22
|
-
<p>Copyright © 2010
|
23
|
-
|
24
|
-
|
25
|
+
<p>Copyright © 2010 <a href="http://jcoglan.com">James Coglan</a>.<br>
|
26
|
+
Released under the MIT license.<br>
|
27
|
+
Source code hosted by <a href="http://github.com/jcoglan/terminus">GitHub</a>.</p>
|
25
28
|
</div>
|
26
29
|
|
27
30
|
</body>
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
root = File.dirname(__FILE__) + '/../'
|
2
|
+
|
3
|
+
require root + 'vendor/capybara/spec/spec_helper'
|
4
|
+
require root + 'lib/terminus'
|
5
|
+
|
6
|
+
class TestApp
|
7
|
+
template :layout do
|
8
|
+
<<-HTML
|
9
|
+
<html>
|
10
|
+
<head>
|
11
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
12
|
+
<title>Capybara Driver Test</title>
|
13
|
+
</head>
|
14
|
+
<body>
|
15
|
+
<%= yield %>
|
16
|
+
<%= Terminus.driver_script request.host %>
|
17
|
+
</body>
|
18
|
+
</html>
|
19
|
+
HTML
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Capybara::Driver::Terminus do
|
4
|
+
before do
|
5
|
+
@driver = Capybara::Driver::Terminus.new(TestApp)
|
6
|
+
Terminus.ensure_docked_browser
|
7
|
+
Terminus.browser = :docked
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Terminus.return_to_dock
|
12
|
+
end
|
13
|
+
|
14
|
+
it_should_behave_like "driver"
|
15
|
+
it_should_behave_like "driver with javascript support"
|
16
|
+
it_should_behave_like "driver without status code support"
|
17
|
+
it_should_behave_like "driver with support for window switching"
|
18
|
+
it_should_behave_like "driver with cookies support"
|
19
|
+
it_should_behave_like "driver with infinite redirect detection"
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Capybara::Session do
|
4
|
+
context 'with terminus driver' do
|
5
|
+
before do
|
6
|
+
@session = Capybara::Session.new(:terminus, TestApp)
|
7
|
+
Terminus.ensure_docked_browser
|
8
|
+
Terminus.browser = :docked
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
Terminus.return_to_dock
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#driver' do
|
16
|
+
it "should be a terminus driver" do
|
17
|
+
@session.driver.should be_an_instance_of(Capybara::Driver::Terminus)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#mode' do
|
22
|
+
it "should remember the mode" do
|
23
|
+
@session.mode.should == :terminus
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it_should_behave_like "session"
|
28
|
+
it_should_behave_like "session without headers support"
|
29
|
+
it_should_behave_like "session with javascript support"
|
30
|
+
it_should_behave_like "session without status code support"
|
31
|
+
end
|
32
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terminus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- James Coglan
|
@@ -9,81 +14,125 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-10-24 00:00:00 +01:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: rack
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
20
25
|
requirements:
|
21
26
|
- - ">="
|
22
27
|
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
23
31
|
version: "1.0"
|
24
|
-
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
25
34
|
- !ruby/object:Gem::Dependency
|
26
35
|
name: thin
|
27
|
-
|
28
|
-
|
29
|
-
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
30
39
|
requirements:
|
31
40
|
- - ">="
|
32
41
|
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 2
|
33
45
|
version: "1.2"
|
34
|
-
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
35
48
|
- !ruby/object:Gem::Dependency
|
36
49
|
name: eventmachine
|
37
|
-
|
38
|
-
|
39
|
-
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
40
53
|
requirements:
|
41
54
|
- - ">="
|
42
55
|
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
- 12
|
43
59
|
version: "0.12"
|
44
|
-
|
60
|
+
type: :runtime
|
61
|
+
version_requirements: *id003
|
45
62
|
- !ruby/object:Gem::Dependency
|
46
63
|
name: faye
|
64
|
+
prerelease: false
|
65
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
- 5
|
73
|
+
- 2
|
74
|
+
version: 0.5.2
|
47
75
|
type: :runtime
|
48
|
-
|
49
|
-
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: capybara
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
50
82
|
requirements:
|
51
|
-
- -
|
83
|
+
- - ">="
|
52
84
|
- !ruby/object:Gem::Version
|
53
|
-
|
54
|
-
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
- 4
|
88
|
+
- 0
|
89
|
+
version: 0.4.0
|
90
|
+
type: :runtime
|
91
|
+
version_requirements: *id005
|
55
92
|
- !ruby/object:Gem::Dependency
|
56
93
|
name: sinatra
|
57
|
-
|
58
|
-
|
59
|
-
|
94
|
+
prerelease: false
|
95
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
60
97
|
requirements:
|
61
98
|
- - ">="
|
62
99
|
- !ruby/object:Gem::Version
|
100
|
+
segments:
|
101
|
+
- 1
|
102
|
+
- 0
|
63
103
|
version: "1.0"
|
64
|
-
|
104
|
+
type: :runtime
|
105
|
+
version_requirements: *id006
|
65
106
|
- !ruby/object:Gem::Dependency
|
66
107
|
name: packr
|
67
|
-
|
68
|
-
|
69
|
-
|
108
|
+
prerelease: false
|
109
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
70
111
|
requirements:
|
71
112
|
- - ">="
|
72
113
|
- !ruby/object:Gem::Version
|
114
|
+
segments:
|
115
|
+
- 3
|
116
|
+
- 1
|
73
117
|
version: "3.1"
|
74
|
-
|
118
|
+
type: :runtime
|
119
|
+
version_requirements: *id007
|
75
120
|
- !ruby/object:Gem::Dependency
|
76
121
|
name: oyster
|
77
|
-
|
78
|
-
|
79
|
-
|
122
|
+
prerelease: false
|
123
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
80
125
|
requirements:
|
81
126
|
- - ">="
|
82
127
|
- !ruby/object:Gem::Version
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
- 9
|
83
131
|
version: "0.9"
|
84
|
-
|
132
|
+
type: :runtime
|
133
|
+
version_requirements: *id008
|
85
134
|
description:
|
86
|
-
email: jcoglan@
|
135
|
+
email: jcoglan@gmail.com
|
87
136
|
executables:
|
88
137
|
- terminus
|
89
138
|
extensions: []
|
@@ -92,14 +141,24 @@ extra_rdoc_files:
|
|
92
141
|
- README.rdoc
|
93
142
|
files:
|
94
143
|
- README.rdoc
|
95
|
-
- lib/terminus.rb
|
96
|
-
- lib/public/style.css
|
97
|
-
- lib/public/terminus.js
|
98
|
-
- lib/public/js.packages.js
|
99
|
-
- lib/views/bookmarklet.erb
|
100
144
|
- lib/views/index.erb
|
101
|
-
- lib/
|
145
|
+
- lib/views/bookmarklet.erb
|
146
|
+
- lib/public/loader.js
|
147
|
+
- lib/public/syn.js
|
148
|
+
- lib/public/terminus.js
|
149
|
+
- lib/public/style.css
|
150
|
+
- lib/terminus.rb
|
151
|
+
- lib/terminus/node.rb
|
152
|
+
- lib/terminus/timeouts.rb
|
153
|
+
- lib/terminus/browser.rb
|
102
154
|
- lib/terminus/application.rb
|
155
|
+
- lib/terminus/server.rb
|
156
|
+
- lib/terminus/controller.rb
|
157
|
+
- lib/capybara/driver/terminus.rb
|
158
|
+
- spec/terminus_session_spec.rb
|
159
|
+
- spec/terminus_driver_spec.rb
|
160
|
+
- spec/spec_helper.rb
|
161
|
+
- bin/terminus
|
103
162
|
has_rdoc: true
|
104
163
|
homepage: http://github.com/jcoglan/terminus
|
105
164
|
licenses: []
|
@@ -111,23 +170,27 @@ rdoc_options:
|
|
111
170
|
require_paths:
|
112
171
|
- lib
|
113
172
|
required_ruby_version: !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
114
174
|
requirements:
|
115
175
|
- - ">="
|
116
176
|
- !ruby/object:Gem::Version
|
177
|
+
segments:
|
178
|
+
- 0
|
117
179
|
version: "0"
|
118
|
-
version:
|
119
180
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
120
182
|
requirements:
|
121
183
|
- - ">="
|
122
184
|
- !ruby/object:Gem::Version
|
185
|
+
segments:
|
186
|
+
- 0
|
123
187
|
version: "0"
|
124
|
-
version:
|
125
188
|
requirements: []
|
126
189
|
|
127
190
|
rubyforge_project:
|
128
|
-
rubygems_version: 1.3.
|
191
|
+
rubygems_version: 1.3.7
|
129
192
|
signing_key:
|
130
193
|
specification_version: 3
|
131
|
-
summary:
|
194
|
+
summary: Capybara driver for cross-browser testing
|
132
195
|
test_files: []
|
133
196
|
|