terminus 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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