terminus 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/terminus +5 -5
- data/lib/capybara/driver/terminus.rb +24 -13
- data/lib/terminus.rb +21 -15
- data/lib/terminus/application.rb +6 -6
- data/lib/terminus/browser.rb +77 -60
- data/lib/terminus/client.rb +33 -16
- data/lib/terminus/client/browser.rb +10 -9
- data/lib/terminus/client/phantom.js +25 -3
- data/lib/terminus/client/phantomjs.rb +44 -7
- data/lib/terminus/connector.rb +2 -2
- data/lib/terminus/connector/server.rb +15 -15
- data/lib/terminus/connector/socket_handler.rb +11 -11
- data/lib/terminus/controller.rb +62 -26
- data/lib/terminus/headers.rb +25 -0
- data/lib/terminus/host.rb +6 -6
- data/lib/terminus/node.rb +36 -22
- data/lib/terminus/proxy.rb +81 -45
- data/lib/terminus/proxy/driver_body.rb +14 -15
- data/lib/terminus/proxy/external.rb +12 -6
- data/lib/terminus/proxy/rewrite.rb +7 -6
- data/lib/terminus/public/compiled/terminus-min.js +3 -3
- data/lib/terminus/public/compiled/terminus.js +225 -180
- data/lib/terminus/public/pathology.js +87 -87
- data/lib/terminus/public/terminus.js +138 -93
- data/lib/terminus/server.rb +7 -7
- data/lib/terminus/timeouts.rb +8 -8
- data/lib/terminus/views/bootstrap.erb +7 -7
- data/lib/terminus/views/index.erb +4 -4
- data/lib/terminus/views/infinite.html +4 -4
- data/spec/1.1/reports/android.txt +874 -0
- data/spec/{reports → 1.1/reports}/chrome.txt +72 -69
- data/spec/{reports → 1.1/reports}/firefox.txt +72 -69
- data/spec/{reports → 1.1/reports}/opera.txt +72 -69
- data/spec/{reports → 1.1/reports}/phantomjs.txt +72 -69
- data/spec/{reports → 1.1/reports}/safari.txt +72 -69
- data/spec/{spec_helper.rb → 1.1/spec_helper.rb} +5 -2
- data/spec/{terminus_driver_spec.rb → 1.1/terminus_driver_spec.rb} +2 -2
- data/spec/{terminus_session_spec.rb → 1.1/terminus_session_spec.rb} +2 -2
- data/spec/2.0/reports/android.txt +815 -0
- data/spec/2.0/reports/chrome.txt +806 -0
- data/spec/2.0/reports/firefox.txt +812 -0
- data/spec/2.0/reports/opera.txt +806 -0
- data/spec/2.0/reports/phantomjs.txt +803 -0
- data/spec/2.0/reports/safari.txt +806 -0
- data/spec/2.0/spec_helper.rb +21 -0
- data/spec/2.0/terminus_spec.rb +25 -0
- metadata +41 -32
- data/spec/reports/android.txt +0 -875
data/lib/terminus/client.rb
CHANGED
@@ -1,35 +1,52 @@
|
|
1
1
|
module Terminus
|
2
2
|
module Client
|
3
|
-
|
3
|
+
|
4
4
|
autoload :Browser, ROOT + '/terminus/client/browser'
|
5
5
|
autoload :PhantomJS, ROOT + '/terminus/client/phantomjs'
|
6
|
-
|
6
|
+
|
7
7
|
class Base
|
8
|
+
attr_reader :id
|
9
|
+
|
8
10
|
def self.start(options = {})
|
9
|
-
process = new(options)
|
10
|
-
process.start
|
11
|
-
process
|
11
|
+
@process = new(options)
|
12
|
+
@process.start
|
13
|
+
@process
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.debugger
|
17
|
+
@process.debugger
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.save_screenshot(path, options = {})
|
21
|
+
@process.save_screenshot(path, options)
|
12
22
|
end
|
13
|
-
|
23
|
+
|
14
24
|
def initialize(options)
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
25
|
+
@id = Faye.random
|
26
|
+
@options = options
|
27
|
+
@address = TCPServer.new(0).addr
|
28
|
+
@connector = Connector::Server.new(self)
|
29
|
+
@port = options[:port] || @address[1]
|
30
|
+
@terminus = Terminus.create(:port => @port)
|
31
|
+
@browser = ChildProcess.build(*browser_args)
|
19
32
|
end
|
20
|
-
|
33
|
+
|
34
|
+
def debug(*args)
|
35
|
+
p args if Terminus.debug
|
36
|
+
end
|
37
|
+
|
21
38
|
def start
|
22
39
|
Terminus.ensure_reactor_running
|
23
40
|
@terminus.run!
|
24
|
-
|
41
|
+
|
25
42
|
@browser.start
|
26
|
-
|
43
|
+
|
27
44
|
Terminus.port = @port
|
28
45
|
Terminus.browser = browser_selector
|
29
|
-
|
46
|
+
|
30
47
|
at_exit { stop }
|
31
48
|
end
|
32
|
-
|
49
|
+
|
33
50
|
def stop
|
34
51
|
@terminus.stop!
|
35
52
|
@browser.stop
|
@@ -37,7 +54,7 @@ module Terminus
|
|
37
54
|
rescue ChildProcess::TimeoutError
|
38
55
|
end
|
39
56
|
end
|
40
|
-
|
57
|
+
|
41
58
|
end
|
42
59
|
end
|
43
60
|
|
@@ -1,30 +1,31 @@
|
|
1
1
|
module Terminus
|
2
2
|
module Client
|
3
|
-
|
3
|
+
|
4
4
|
class Browser < Base
|
5
5
|
DEFAULT_COMMANDS = {
|
6
6
|
/(mingw|mswin|windows|cygwin)/i => ['cmd', '/C', 'start', '/b'],
|
7
7
|
/(darwin|mac os)/i => ['open'],
|
8
8
|
/(linux|bsd|aix|solaris)/i => ['xdg-open']
|
9
9
|
}
|
10
|
-
|
11
|
-
def browser_args
|
10
|
+
|
11
|
+
def browser_args
|
12
|
+
command = @options[:command]
|
12
13
|
return command + [dock_url] if command
|
13
|
-
|
14
|
+
|
14
15
|
os = RbConfig::CONFIG['host_os']
|
15
16
|
key = DEFAULT_COMMANDS.keys.find { |key| os =~ key }
|
16
17
|
DEFAULT_COMMANDS[key] + [dock_url]
|
17
18
|
end
|
18
|
-
|
19
|
+
|
19
20
|
def browser_selector
|
20
|
-
{:
|
21
|
+
{:raw_url => dock_url}
|
21
22
|
end
|
22
|
-
|
23
|
+
|
23
24
|
def dock_url
|
24
|
-
"http://#{@address[2]}:#{@port}"
|
25
|
+
"http://#{@address[2]}:#{@port}/"
|
25
26
|
end
|
26
27
|
end
|
27
|
-
|
28
|
+
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -1,6 +1,28 @@
|
|
1
|
-
var page
|
2
|
-
|
3
|
-
|
1
|
+
var page = new WebPage(),
|
2
|
+
width = phantom.args[0],
|
3
|
+
height = phantom.args[1],
|
4
|
+
host = phantom.args[2],
|
5
|
+
port = phantom.args[3],
|
6
|
+
socket = phantom.args[4];
|
4
7
|
|
8
|
+
var Commands = {
|
9
|
+
save_screenshot: function(path, options, callback) {
|
10
|
+
page.render(path);
|
11
|
+
callback(true);
|
12
|
+
}
|
13
|
+
};
|
14
|
+
|
15
|
+
var ws = new WebSocket('ws://' + host + ':' + socket + '/');
|
16
|
+
ws.onmessage = function(message) {
|
17
|
+
var args = JSON.parse(message.data),
|
18
|
+
method = args.shift();
|
19
|
+
|
20
|
+
args.push(function(result) {
|
21
|
+
ws.send(JSON.stringify([result]));
|
22
|
+
});
|
23
|
+
Commands[method].apply(Commands, args);
|
24
|
+
};
|
25
|
+
|
26
|
+
page.viewportSize = {width: parseInt(width, 10), height: parseInt(height, 10)};
|
5
27
|
page.open('http://' + host + ':' + port + '/');
|
6
28
|
|
@@ -1,20 +1,57 @@
|
|
1
1
|
module Terminus
|
2
2
|
module Client
|
3
|
-
|
3
|
+
|
4
4
|
class PhantomJS < Base
|
5
|
+
BROWSERS = %w[chromium chromium-browser google-chrome xdg-open open]
|
6
|
+
DEBUG_PATH = '/webkit/inspector/inspector.html?page=2'
|
5
7
|
DEFAULT_COMMAND = ['/usr/bin/env', 'phantomjs']
|
8
|
+
DEFAULT_WIDTH = 1024
|
9
|
+
DEFAULT_HEIGHT = 768
|
6
10
|
PHANTOM_CLIENT = File.expand_path('../phantom.js', __FILE__)
|
7
|
-
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
+
|
12
|
+
def initialize(*args)
|
13
|
+
@debug_port = TCPServer.new(0).addr[1]
|
14
|
+
@debugger = BROWSERS.map(&method(:detect_browser)).compact.first
|
15
|
+
super
|
11
16
|
end
|
12
|
-
|
17
|
+
|
18
|
+
def browser_args
|
19
|
+
command = @options[:command] || DEFAULT_COMMAND
|
20
|
+
width = @options[:width] || DEFAULT_WIDTH
|
21
|
+
height = @options[:height] || DEFAULT_HEIGHT
|
22
|
+
|
23
|
+
args = command.dup
|
24
|
+
args + ["--remote-debugger-port=#{@debug_port}", "--remote-debugger-autorun=yes",
|
25
|
+
PHANTOM_CLIENT, width.to_s, height.to_s,
|
26
|
+
@address[2], @port.to_s, @connector.port.to_s]
|
27
|
+
end
|
28
|
+
|
13
29
|
def browser_selector
|
14
30
|
{:name => 'PhantomJS'}
|
15
31
|
end
|
32
|
+
|
33
|
+
def debugger
|
34
|
+
raise ArgumentError, 'Could not find launchable browser' unless @debugger
|
35
|
+
url = "http://#{@address[2]}:#{@debug_port}#{DEBUG_PATH}"
|
36
|
+
process = ChildProcess.build(@debugger, url)
|
37
|
+
process.start
|
38
|
+
puts "Launched WebKit remote debugger at #{url}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def detect_browser(browser)
|
42
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
43
|
+
exe = File.join(path, browser)
|
44
|
+
return exe if File.executable?(exe)
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def save_screenshot(path, options = {})
|
50
|
+
message = Yajl::Encoder.encode(['save_screenshot', path, options])
|
51
|
+
@connector.request(message)
|
52
|
+
end
|
16
53
|
end
|
17
|
-
|
54
|
+
|
18
55
|
end
|
19
56
|
end
|
20
57
|
|
data/lib/terminus/connector.rb
CHANGED
@@ -23,11 +23,11 @@
|
|
23
23
|
|
24
24
|
module Terminus
|
25
25
|
module Connector
|
26
|
-
|
26
|
+
|
27
27
|
class Server
|
28
28
|
RECV_SIZE = 1024
|
29
29
|
BIND_TIMEOUT = 5
|
30
|
-
|
30
|
+
|
31
31
|
def initialize(browser, timeout = BIND_TIMEOUT)
|
32
32
|
@browser = browser
|
33
33
|
@skips = 0
|
@@ -35,7 +35,7 @@ module Terminus
|
|
35
35
|
@timeout = timeout
|
36
36
|
reset
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def reset
|
40
40
|
@closing = false
|
41
41
|
@env = nil
|
@@ -43,15 +43,15 @@ module Terminus
|
|
43
43
|
@parser = Http::Parser.new
|
44
44
|
@socket = nil
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def connected?
|
48
48
|
not @socket.nil?
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
def port
|
52
52
|
@server.addr[1]
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
def request(message)
|
56
56
|
@browser.debug(:send, @browser.id, message)
|
57
57
|
accept unless connected?
|
@@ -65,13 +65,13 @@ module Terminus
|
|
65
65
|
reset
|
66
66
|
nil
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
def drain_socket
|
70
70
|
@closing = true if @socket
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
private
|
74
|
-
|
74
|
+
|
75
75
|
def start_server
|
76
76
|
time = Time.now
|
77
77
|
TCPServer.open(0)
|
@@ -83,7 +83,7 @@ module Terminus
|
|
83
83
|
raise
|
84
84
|
end
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
def accept
|
88
88
|
@skips.times { @server.accept.close }
|
89
89
|
@socket = @server.accept
|
@@ -100,7 +100,7 @@ module Terminus
|
|
100
100
|
@browser.debug(:accept, @browser.id, @handler.url)
|
101
101
|
end
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
def env
|
105
105
|
@env ||= begin
|
106
106
|
env = {'REQUEST_METHOD' => @parser.http_method}
|
@@ -113,11 +113,11 @@ module Terminus
|
|
113
113
|
env
|
114
114
|
end
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
def receive
|
118
118
|
@browser.debug(:receive, @browser.id)
|
119
119
|
start = Time.now
|
120
|
-
|
120
|
+
|
121
121
|
until @handler.message?
|
122
122
|
raise Errno::EWOULDBLOCK if (Time.now - start) >= @timeout
|
123
123
|
IO.select([@socket], [], [], @timeout) or raise Errno::EWOULDBLOCK
|
@@ -128,7 +128,7 @@ module Terminus
|
|
128
128
|
end
|
129
129
|
@handler && @handler.next_message
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
def close
|
133
133
|
[server, socket].compact.each do |s|
|
134
134
|
s.close_read
|
@@ -136,7 +136,7 @@ module Terminus
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
end
|
141
141
|
end
|
142
142
|
|
@@ -23,50 +23,50 @@
|
|
23
23
|
|
24
24
|
module Terminus
|
25
25
|
module Connector
|
26
|
-
|
26
|
+
|
27
27
|
class SocketHandler
|
28
28
|
attr_reader :env
|
29
|
-
|
29
|
+
|
30
30
|
def initialize(server, env)
|
31
31
|
@server = server
|
32
32
|
@env = env
|
33
33
|
@parser = Faye::WebSocket.parser(env).new(self)
|
34
34
|
@messages = []
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
def url
|
38
38
|
"ws://#{env['HTTP_HOST']}/"
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
def handshake_response
|
42
42
|
@parser.handshake_response
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
def <<(data)
|
46
46
|
@parser.parse(data)
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
def encode(message)
|
50
50
|
@parser.frame(Faye::WebSocket.encode(message))
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
def receive(message)
|
54
54
|
@messages << message
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
def message?
|
58
58
|
@messages.any?
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
def next_message
|
62
62
|
@messages.shift
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
def close(*args)
|
66
66
|
@server.reset
|
67
67
|
end
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
data/lib/terminus/controller.rb
CHANGED
@@ -1,23 +1,25 @@
|
|
1
1
|
module Terminus
|
2
2
|
class Controller
|
3
|
-
|
3
|
+
|
4
4
|
include Timeouts
|
5
|
-
|
5
|
+
|
6
6
|
def initialize
|
7
7
|
@connected = false
|
8
8
|
@browsers = {}
|
9
9
|
@host_aliases = {}
|
10
|
+
@local_ports = []
|
11
|
+
@visited = Set.new
|
10
12
|
end
|
11
|
-
|
13
|
+
|
12
14
|
def browser(id = nil)
|
13
15
|
return @browser if id.nil?
|
14
16
|
@browsers[id] ||= Browser.new(self, id)
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
def browsers
|
18
20
|
@browsers.values
|
19
21
|
end
|
20
|
-
|
22
|
+
|
21
23
|
def browser=(params)
|
22
24
|
ensure_connection
|
23
25
|
return @browser = params if Browser === params
|
@@ -25,41 +27,46 @@ module Terminus
|
|
25
27
|
@browsers.values.find { |b| b === params }
|
26
28
|
end
|
27
29
|
end
|
28
|
-
|
30
|
+
|
29
31
|
def cookies
|
30
32
|
@cookies ||= CookieJar::Jar.new
|
31
33
|
end
|
32
|
-
|
34
|
+
|
33
35
|
def drop_browser(browser)
|
34
36
|
@browsers.delete(browser.id)
|
35
37
|
@browser = nil if @browser == browser
|
36
38
|
end
|
37
|
-
|
39
|
+
|
38
40
|
def ensure_browsers(n = 1)
|
39
41
|
ensure_connection
|
40
42
|
wait_with_timeout(:browsers) { @browsers.size >= n }
|
41
43
|
end
|
42
|
-
|
44
|
+
|
43
45
|
def messenger
|
44
46
|
Terminus.ensure_reactor_running
|
45
47
|
return @messenger if defined?(@messenger)
|
46
|
-
|
48
|
+
|
47
49
|
@messenger = Faye::Client.new(Terminus.endpoint)
|
48
|
-
|
50
|
+
|
49
51
|
@messenger.subscribe('/terminus/ping', &method(:accept_ping))
|
50
52
|
@messenger.subscribe('/terminus/results', &method(:accept_result))
|
51
53
|
@messenger
|
52
54
|
end
|
53
|
-
|
55
|
+
|
56
|
+
def register_local_port(port)
|
57
|
+
@local_ports << port
|
58
|
+
@host_aliases[Host.new(URI.parse("http://localhost:#{port}/"))] = Host.new(URI.parse("http://localhost:80/"))
|
59
|
+
end
|
60
|
+
|
54
61
|
def return_to_dock
|
55
62
|
@browsers.each { |id, b| b.return_to_dock }
|
56
63
|
end
|
57
|
-
|
64
|
+
|
58
65
|
def rewrite_local(url, dock_host)
|
59
66
|
uri = URI.parse(url)
|
60
67
|
uri.host = '127.0.0.1' if uri.host == dock_host
|
61
68
|
uri.path = '' if uri.path == '/'
|
62
|
-
|
69
|
+
|
63
70
|
# 1.8.7 does not have Hash#key, and 1.9.2 gives warnings for #index
|
64
71
|
remote_host = @host_aliases.respond_to?(:key) ?
|
65
72
|
@host_aliases.key(Host.new(uri)) :
|
@@ -70,41 +77,66 @@ module Terminus
|
|
70
77
|
uri.host = remote_host.host
|
71
78
|
uri.port = remote_host.port
|
72
79
|
end
|
73
|
-
uri.
|
80
|
+
uri.path = '/' if uri.path == ''
|
74
81
|
uri
|
75
82
|
end
|
76
|
-
|
83
|
+
|
77
84
|
def rewrite_remote(url, dock_host = nil)
|
85
|
+
protocol_relative = (url =~ /^\/\//)
|
86
|
+
url = "http:#{url}" if protocol_relative
|
87
|
+
|
78
88
|
uri = URI.parse(url)
|
79
|
-
return uri unless URI::HTTP === uri
|
89
|
+
return uri unless URI::HTTP === uri
|
90
|
+
|
91
|
+
if (uri.host =~ LOCALHOST or uri.host == dock_host)
|
92
|
+
if uri.port == 80
|
93
|
+
uri.port = @host_aliases.keys.find { |h| h.host =~ LOCALHOST }.port
|
94
|
+
end
|
95
|
+
|
96
|
+
if local_ports.include?(uri.port)
|
97
|
+
uri.host = dock_host
|
98
|
+
return uri
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
80
102
|
server = boot(uri)
|
81
|
-
uri.scheme = 'http'
|
103
|
+
uri.scheme = protocol_relative ? nil : 'http'
|
82
104
|
uri.host, uri.port = (dock_host || server.host), server.port
|
83
105
|
uri
|
84
106
|
rescue URI::InvalidURIError
|
85
107
|
url
|
86
108
|
end
|
87
|
-
|
109
|
+
|
88
110
|
def server_running?(server)
|
89
111
|
return false unless server.port
|
90
|
-
uri = URI.parse("http://#{server.host}:#{server.port}
|
112
|
+
uri = URI.parse("http://#{server.host}:#{server.port}#{PING_PATH}")
|
91
113
|
Net::HTTP.start(uri.host, uri.port) { |h| h.head(uri.path) }
|
92
114
|
true
|
93
115
|
rescue
|
94
116
|
false
|
95
117
|
end
|
96
|
-
|
118
|
+
|
119
|
+
def visit_url(url)
|
120
|
+
@visited.add(url)
|
121
|
+
end
|
122
|
+
|
123
|
+
def visited?(url)
|
124
|
+
visited = @visited.member?(url)
|
125
|
+
@visited.delete(url)
|
126
|
+
visited
|
127
|
+
end
|
128
|
+
|
97
129
|
private
|
98
|
-
|
130
|
+
|
99
131
|
def accept_ping(message)
|
100
132
|
browser(message['id']).ping!(message)
|
101
133
|
end
|
102
|
-
|
134
|
+
|
103
135
|
def accept_result(message)
|
104
136
|
browser = @browsers[message['id']]
|
105
137
|
browser.result!(message) if browser
|
106
138
|
end
|
107
|
-
|
139
|
+
|
108
140
|
def boot(remote_uri)
|
109
141
|
host = Host.new(remote_uri)
|
110
142
|
@host_aliases[host] ||= begin
|
@@ -114,13 +146,17 @@ module Terminus
|
|
114
146
|
Host.new(server)
|
115
147
|
end
|
116
148
|
end
|
117
|
-
|
149
|
+
|
118
150
|
def ensure_connection
|
119
151
|
return if @connected
|
120
152
|
messenger.connect { @connected = true }
|
121
153
|
wait_with_timeout(:connection) { @connected }
|
122
154
|
end
|
123
|
-
|
155
|
+
|
156
|
+
def local_ports
|
157
|
+
[Terminus.port] + @local_ports + @host_aliases.values.map { |h| h.port }
|
158
|
+
end
|
159
|
+
|
124
160
|
end
|
125
161
|
end
|
126
162
|
|