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.
- 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/bin/terminus
CHANGED
@@ -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
|
-
|
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
|
data/lib/terminus.rb
CHANGED
@@ -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
|
-
:
|
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
|
data/lib/terminus/application.rb
CHANGED
@@ -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
|
|
data/lib/terminus/browser.rb
CHANGED
@@ -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
|
|