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
@@ -7,23 +7,23 @@ require File.expand_path('../../lib/terminus', __FILE__)
7
7
  spec = Oyster.spec do
8
8
  name "terminus -- Control web browsers with Ruby"
9
9
  synopsis "terminus [--port PORT]"
10
-
10
+
11
11
  integer :port, :default => Terminus::DEFAULT_PORT
12
12
  end
13
13
 
14
14
  begin
15
15
  options = spec.parse
16
16
  app = Terminus.create(options)
17
-
17
+
18
18
  trap("INT") { app.stop! ; exit }
19
-
19
+
20
20
  Terminus.port = options[:port]
21
-
21
+
22
22
  puts "Terminus server running on port #{Terminus.port}"
23
23
  puts "Press CTRL-C to exit"
24
24
  puts ""
25
25
  app.run!
26
-
26
+
27
27
  rescue Oyster::HelpRendered
28
28
  end
29
29
 
@@ -1,55 +1,66 @@
1
1
  class Capybara::Driver::Terminus < Capybara::Driver::Base
2
2
  attr_reader :options
3
-
3
+
4
4
  NULL_APP = lambda do |env|
5
5
  [200, {'Content-Type' => 'text/html'}, ['']]
6
6
  end
7
-
7
+
8
8
  def initialize(app = nil, options = {})
9
9
  @app = Terminus::Proxy[app || NULL_APP]
10
10
  @options = options
11
11
  @rack_server = Capybara::Server.new(@app)
12
-
12
+
13
13
  @rack_server.boot
14
14
  sleep(0.1) until Terminus.server_running?(@rack_server)
15
+ Terminus.register_local_port(@rack_server.port)
15
16
  end
16
-
17
+
17
18
  def find(xpath)
18
19
  browser.find(xpath, self)
19
20
  end
20
-
21
+
21
22
  def invalid_element_errors
22
23
  [::Terminus::ObsoleteElementError]
23
24
  end
24
-
25
+
26
+ def needs_server?
27
+ true
28
+ end
29
+
25
30
  def visit(path)
26
- browser.visit(@rack_server.url(path))
31
+ h, s = Capybara.app_host, @rack_server
32
+ path = "#{h || "http://#{s.host}:#{s.port}"}#{path}" unless path =~ /^https?:\/\//
33
+ browser.visit(path)
27
34
  end
28
-
35
+
29
36
  def wait?
30
37
  true
31
38
  end
32
-
39
+
33
40
  extend Forwardable
34
41
  def_delegators :browser, :body,
35
42
  :current_url,
43
+ :debugger,
36
44
  :evaluate_script,
37
45
  :execute_script,
46
+ :html,
38
47
  :reset!,
39
48
  :response_headers,
49
+ :save_screenshot,
40
50
  :source,
41
51
  :status_code
42
-
52
+
43
53
  def within_window(name)
44
54
  current_browser = browser
45
55
  Terminus.browser = browser.id + '/' + name
46
- yield
56
+ result = yield
47
57
  Terminus.browser = current_browser
58
+ result
48
59
  end
49
60
  alias :within_frame :within_window
50
-
61
+
51
62
  private
52
-
63
+
53
64
  def browser
54
65
  Terminus.ensure_browsers
55
66
  Terminus.browser
@@ -3,6 +3,7 @@ require 'forwardable'
3
3
  require 'http/parser'
4
4
  require 'net/http'
5
5
  require 'rbconfig'
6
+ require 'readline'
6
7
  require 'socket'
7
8
  require 'stringio'
8
9
  require 'uri'
@@ -20,70 +21,75 @@ module Terminus
20
21
  DEFAULT_HOST = 'localhost'
21
22
  DEFAULT_PORT = 7004
22
23
  LOCALHOST = /^(localhost|0\.0\.0\.0|127\.0\.0\.1)$/
24
+ PING_PATH = '/favicon.ico'
23
25
  RETRY_LIMIT = 3
24
-
26
+
25
27
  ROOT = File.expand_path('..', __FILE__)
26
28
  autoload :Application, ROOT + '/terminus/application'
27
29
  autoload :Browser, ROOT + '/terminus/browser'
28
30
  autoload :Client, ROOT + '/terminus/client'
29
31
  autoload :Connector, ROOT + '/terminus/connector'
30
32
  autoload :Controller, ROOT + '/terminus/controller'
33
+ autoload :Headers, ROOT + '/terminus/headers'
31
34
  autoload :Host, ROOT + '/terminus/host'
32
35
  autoload :Node, ROOT + '/terminus/node'
33
36
  autoload :Proxy, ROOT + '/terminus/proxy'
34
37
  autoload :Server, ROOT + '/terminus/server'
35
38
  autoload :Timeouts, ROOT + '/terminus/timeouts'
36
-
39
+
37
40
  require ROOT + '/capybara/driver/terminus'
38
-
41
+
39
42
  class ObsoleteElementError < StandardError
40
43
  end
41
-
44
+
42
45
  class << self
43
46
  attr_accessor :debug, :sockets
44
-
47
+
45
48
  def create(options = {})
46
49
  Server.new(options)
47
50
  end
48
-
51
+
49
52
  def endpoint(host = DEFAULT_HOST)
50
53
  "http://#{host}:#{port}#{FAYE_MOUNT}"
51
54
  end
52
-
55
+
53
56
  def ensure_reactor_running
54
57
  Thread.new { EM.run unless EM.reactor_running? }
55
58
  Thread.pass until EM.reactor_running?
56
59
  end
57
-
60
+
58
61
  def port
59
62
  @port || DEFAULT_PORT
60
63
  end
61
-
64
+
62
65
  def port=(port)
63
66
  @port = port.to_i
64
67
  end
65
-
68
+
66
69
  def start_browser(options = {})
67
70
  Client::Browser.start(options)
68
71
  end
69
-
72
+
70
73
  def start_phantomjs(options = {})
71
74
  Client::PhantomJS.start(options)
72
75
  end
73
-
76
+
74
77
  extend Forwardable
75
78
  def_delegators :controller, :browser,
76
79
  :browsers,
77
80
  :browser=,
78
81
  :cookies,
79
82
  :ensure_browsers,
83
+ :register_local_port,
80
84
  :return_to_dock,
81
85
  :rewrite_local,
82
86
  :rewrite_remote,
83
- :server_running?
84
-
87
+ :save_error,
88
+ :server_running?,
89
+ :visited?
90
+
85
91
  private
86
-
92
+
87
93
  def controller
88
94
  @controller ||= Controller.new
89
95
  end
@@ -3,27 +3,27 @@ require 'sinatra'
3
3
 
4
4
  module Terminus
5
5
  class Application < Sinatra::Base
6
-
6
+
7
7
  ROOT = File.expand_path('../..', __FILE__)
8
-
8
+
9
9
  set :static, true
10
10
  set :root, ROOT + '/terminus'
11
-
11
+
12
12
  helpers do
13
13
  def bootstrap
14
14
  Packr.pack(erb(:bootstrap), :shrink_vars => true)
15
15
  end
16
16
  end
17
-
17
+
18
18
  get '/' do
19
19
  erb :index
20
20
  end
21
-
21
+
22
22
  get '/bootstrap.js' do
23
23
  headers 'Content-Type' => 'text/javascript'
24
24
  bootstrap
25
25
  end
26
-
26
+
27
27
  end
28
28
  end
29
29
 
@@ -1,13 +1,13 @@
1
1
  module Terminus
2
2
  class Browser
3
-
3
+
4
4
  include Timeouts
5
5
  attr_reader :connector
6
6
  attr_writer :sockets
7
-
7
+
8
8
  extend Forwardable
9
9
  def_delegators :@user_agent, :os, :version
10
-
10
+
11
11
  def initialize(controller, id)
12
12
  @controller = controller
13
13
  @attributes = {'id' => id}
@@ -15,22 +15,22 @@ module Terminus
15
15
  @frames = Set.new
16
16
  @namespace = Faye::Namespace.new
17
17
  @results = {}
18
-
18
+
19
19
  add_timeout(:dead, Timeouts::TIMEOUT) { drop_dead! }
20
20
  end
21
-
21
+
22
22
  def ===(params)
23
23
  return docked? if params == :docked
24
24
  return params == id if String === params
25
25
  return false if @parent
26
26
  return false unless @user_agent
27
-
27
+
28
28
  params.all? do |name, value|
29
29
  property = __send__(name)
30
30
  value === property
31
31
  end
32
32
  end
33
-
33
+
34
34
  def ask(command, retries = RETRY_LIMIT)
35
35
  debug(:ask, id, command)
36
36
  value = if @connector
@@ -54,146 +54,163 @@ module Terminus
54
54
  raise e if retries == 1
55
55
  ask(command, retries - 1)
56
56
  end
57
-
58
- def body
59
- ask([:body])
60
- end
61
-
57
+
62
58
  def current_path
63
59
  URI.parse(current_url).path
64
60
  end
65
-
61
+
66
62
  def current_url
67
63
  url = @attributes['url']
68
64
  return '' unless url
69
- return url unless @connector
70
65
  rewrite_local(ask([:current_url]))
71
66
  end
72
-
67
+
73
68
  def debug(*args)
74
69
  p args if Terminus.debug
75
70
  end
76
-
71
+
72
+ def debugger
73
+ Client::PhantomJS.debugger if name == 'PhantomJS'
74
+ Readline.readline('Driver paused, press ENTER to continue')
75
+ end
76
+
77
77
  def docked?
78
78
  @docked
79
79
  end
80
-
80
+
81
81
  def evaluate_script(expression)
82
82
  ask([:evaluate, expression])
83
83
  end
84
-
84
+
85
85
  def execute_script(expression)
86
86
  @connector ? ask([:execute, expression]) : tell([:execute, expression])
87
87
  nil
88
88
  end
89
-
89
+
90
90
  def find(xpath, driver = nil)
91
91
  ask([:find, xpath, false]).map { |id| Node.new(self, id, driver) }
92
92
  end
93
-
93
+
94
94
  def frame!(frame_browser)
95
95
  @frames.add(frame_browser)
96
96
  end
97
-
97
+
98
98
  def frames
99
99
  @frames.to_a
100
100
  end
101
-
101
+
102
+ def html
103
+ ask([:body])
104
+ end
105
+ alias :body :html
106
+
102
107
  def id
103
108
  @attributes['id']
104
109
  end
105
-
110
+
106
111
  def infinite_redirect?
107
112
  return @infinite_redirect unless @connector
108
113
  evaluate_script('!!window.TERMINUS_INFINITE_REDIRECT')
109
114
  end
110
-
115
+
111
116
  def name
112
117
  return 'PhantomJS' if @user_agent.to_str =~ /\bPhantomJS\b/
113
118
  @user_agent.browser
114
119
  end
115
-
120
+
116
121
  def page_id
117
122
  @attributes['page']
118
123
  end
119
-
124
+
120
125
  def ping!(message)
121
126
  debug(:ping, id)
122
127
  debug(:recv, message)
123
-
128
+
124
129
  remove_timeout(:dead)
125
130
  add_timeout(:dead, Timeouts::TIMEOUT) { drop_dead! }
126
-
131
+
132
+ @attributes['raw_url'] = message['url']
127
133
  message['url'] = rewrite_local(message['url'])
128
-
134
+
129
135
  @attributes = @attributes.merge(message)
130
136
  @user_agent = UserAgent.parse(message['ua'])
131
137
  detect_dock_host
132
-
138
+
133
139
  @infinite_redirect = message['infinite']
134
-
140
+
135
141
  if id =~ /\//
136
142
  @parent = Terminus.browser(id.gsub(/\/[^\/]+$/, ''))
137
143
  @parent.frame!(self) unless @parent == self
138
144
  end
139
-
145
+
140
146
  start_connector if message['sockets'] and sockets?
141
-
147
+
142
148
  @ping = true
143
149
  end
144
-
150
+
151
+ def raw_url
152
+ @attributes['raw_url']
153
+ end
154
+
145
155
  def reset!
146
156
  if url = @attributes['url']
147
157
  uri = URI.parse(url)
148
158
  visit("http://#{uri.host}:#{uri.port}/")
149
159
  end
150
160
  ask([:clear_cookies])
161
+
151
162
  @attributes.delete('url')
152
163
  end
153
-
164
+
154
165
  def response_headers
155
- evaluate_script('TERMINUS_HEADERS')
166
+ Headers.new(evaluate_script('TERMINUS_HEADERS'))
156
167
  end
157
-
168
+
158
169
  def result!(message)
159
170
  debug(:result, id, message['commandId'], message['result'])
160
171
  @results[message['commandId']] = {:value => message['result']}
161
172
  end
162
-
173
+
163
174
  def result(id)
164
175
  return nil unless @results.has_key?(id)
165
176
  @results.delete(id)
166
177
  end
167
-
178
+
168
179
  def return_to_dock
169
180
  return unless @dock_host
170
181
  visit("http://#{@dock_host}:#{Terminus.port}/")
171
182
  end
172
-
183
+
184
+ def save_screenshot(path, options = {})
185
+ raise Capybara::NotSupportedByDriverError.new unless name == 'PhantomJS'
186
+ Client::PhantomJS.save_screenshot(path, options)
187
+ end
188
+
173
189
  def sockets?
174
190
  @sockets.nil? ? Terminus.sockets != false : @sockets
175
191
  end
176
-
192
+
177
193
  def source
178
194
  evaluate_script('TERMINUS_SOURCE')
179
195
  end
180
-
196
+
181
197
  def status_code
182
198
  evaluate_script('TERMINUS_STATUS')
183
199
  end
184
-
200
+
185
201
  def tell(command)
186
202
  command_id = @namespace.generate
187
203
  debug(:tell, id, command, command_id)
188
204
  messenger.publish(command_channel, 'command' => command, 'commandId' => command_id)
189
205
  command_id
190
206
  end
191
-
207
+
192
208
  def visit(url, retries = RETRY_LIMIT)
193
209
  close_frames!
194
210
  uri = @controller.rewrite_remote(url, @dock_host)
195
211
  uri.host = @dock_host if uri.host =~ LOCALHOST
196
-
212
+ @controller.visit_url(uri.to_s)
213
+
197
214
  if @connector
198
215
  ask([:visit, uri.to_s], false)
199
216
  @connector.drain_socket
@@ -202,29 +219,29 @@ module Terminus
202
219
  tell([:visit, uri.to_s])
203
220
  wait_for_ping
204
221
  end
205
-
222
+
206
223
  if infinite_redirect?
207
224
  @infinite_redirect = nil
208
225
  raise Capybara::InfiniteRedirectError
209
226
  end
210
-
227
+
211
228
  rescue Timeouts::TimeoutError => e
212
229
  raise e if retries.zero?
213
230
  visit(url, retries - 1)
214
231
  end
215
-
232
+
216
233
  def wait_for_ping
217
234
  @ping = false
218
235
  wait_with_timeout(:ping) { @ping or @dead }
219
236
  end
220
-
237
+
221
238
  def to_s
222
239
  "<#{self.class.name} #{name} #{version} (#{os})>"
223
240
  end
224
241
  alias :inspect :to_s
225
-
242
+
226
243
  protected
227
-
244
+
228
245
  def drop_dead!
229
246
  remove_timeout(:dead)
230
247
  close_frames!
@@ -232,32 +249,32 @@ module Terminus
232
249
  @dead = true
233
250
  @controller.drop_browser(self)
234
251
  end
235
-
252
+
236
253
  private
237
-
254
+
238
255
  def command_channel
239
256
  "/terminus/clients/#{id}"
240
257
  end
241
-
258
+
242
259
  def socket_channel
243
260
  "/terminus/sockets/#{id}"
244
261
  end
245
-
262
+
246
263
  def close_frames!
247
264
  @frames.each { |frame| frame.drop_dead! }
248
265
  @frames = Set.new
249
266
  end
250
-
267
+
251
268
  def detect_dock_host
252
269
  uri = URI.parse(@attributes['url'])
253
270
  @docked = (uri.port == Terminus.port)
254
271
  @dock_host = @attributes['host']
255
272
  end
256
-
273
+
257
274
  def rewrite_local(url)
258
275
  @controller.rewrite_local(url.to_s, @dock_host).to_s
259
276
  end
260
-
277
+
261
278
  def start_connector
262
279
  return if @connector or @dock_host.nil? or Terminus.browser != self
263
280
  @connector = Connector::Server.new(self)
@@ -265,11 +282,11 @@ module Terminus
265
282
  debug(:connect, id, url)
266
283
  messenger.publish(socket_channel, 'url' => url)
267
284
  end
268
-
285
+
269
286
  def messenger
270
287
  @controller.messenger
271
288
  end
272
-
289
+
273
290
  end
274
291
  end
275
292