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,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
- @address = TCPServer.new(0).addr
16
- @port = options[:port] || @address[1]
17
- @terminus = Terminus.create(:port => @port)
18
- @browser = ChildProcess.build(*browser_args(options[:command]))
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(command)
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
- {:current_url => dock_url}
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 = new WebPage(),
2
- host = phantom.args[0],
3
- port = phantom.args[1];
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 browser_args(command)
9
- args = (command || DEFAULT_COMMAND).dup
10
- args + [PHANTOM_CLIENT, @address[2], @port.to_s]
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
 
@@ -1,9 +1,9 @@
1
1
  module Terminus
2
2
  module Connector
3
-
3
+
4
4
  autoload :Server, ROOT + '/terminus/connector/server'
5
5
  autoload :SocketHandler, ROOT + '/terminus/connector/socket_handler'
6
-
6
+
7
7
  end
8
8
  end
9
9
 
@@ -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
 
@@ -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.host = dock_host if dock_host and uri.host =~ LOCALHOST
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 and uri.host !~ LOCALHOST and uri.host != dock_host
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