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
@@ -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