terminus 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,25 @@
|
|
1
|
+
module Terminus
|
2
|
+
class Headers
|
3
|
+
|
4
|
+
def initialize(values)
|
5
|
+
@hash = {}
|
6
|
+
values.each do |key, value|
|
7
|
+
@hash[normalize_key(key)] = value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
@hash[normalize_key(key)]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def normalize_key(key)
|
18
|
+
key.downcase.
|
19
|
+
gsub(/^http_/, '').
|
20
|
+
gsub(/_/, '-')
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/lib/terminus/host.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
module Terminus
|
2
2
|
class Host
|
3
|
-
|
3
|
+
|
4
4
|
attr_reader :host, :port
|
5
|
-
|
5
|
+
|
6
6
|
def initialize(uri)
|
7
7
|
@scheme = uri.scheme if uri.respond_to?(:scheme)
|
8
8
|
@host = uri.host
|
9
9
|
@port = uri.port
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def scheme
|
13
13
|
@scheme || 'http'
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def eql?(other)
|
17
17
|
host == other.host and
|
18
18
|
port == other.port
|
19
19
|
end
|
20
20
|
alias :== :eql?
|
21
|
-
|
21
|
+
|
22
22
|
def hash
|
23
23
|
[host, port].hash
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
end
|
27
27
|
end
|
data/lib/terminus/node.rb
CHANGED
@@ -1,74 +1,88 @@
|
|
1
1
|
module Terminus
|
2
2
|
class Node
|
3
|
-
|
3
|
+
|
4
4
|
attr_reader :id
|
5
|
-
|
5
|
+
|
6
6
|
def initialize(browser, id, driver = nil)
|
7
7
|
@browser, @id, @driver = browser, id, driver
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def checked?
|
11
11
|
!!self['checked']
|
12
12
|
end
|
13
|
-
|
14
|
-
def selected?
|
15
|
-
!!self['selected']
|
16
|
-
end
|
17
|
-
|
13
|
+
|
18
14
|
def click
|
19
15
|
page = @browser.page_id
|
20
16
|
options = @driver ? @driver.options : {}
|
21
|
-
|
17
|
+
|
22
18
|
value = if @browser.connector
|
23
19
|
@browser.ask([:click, @id, options], false)
|
24
20
|
else
|
25
21
|
command = @browser.tell([:click, @id, options])
|
26
|
-
|
22
|
+
|
27
23
|
result = @browser.wait_with_timeout(:click_response) do
|
28
24
|
@browser.result(command) || (@browser.page_id != page)
|
29
25
|
end
|
30
26
|
Hash === result ? result[:value] : nil
|
31
27
|
end
|
32
|
-
|
28
|
+
|
33
29
|
if String === value
|
34
30
|
raise Capybara::TimeoutError, value
|
35
31
|
end
|
36
32
|
end
|
37
|
-
|
33
|
+
|
38
34
|
def drag_to(node)
|
39
35
|
@browser.ask([:drag, {:from => @id, :to => node.id}])
|
40
36
|
end
|
41
|
-
|
37
|
+
|
38
|
+
def ==(other)
|
39
|
+
Terminus::Node === other and @id == other.id
|
40
|
+
end
|
41
|
+
alias :eql? :==
|
42
|
+
|
42
43
|
def find(xpath)
|
43
44
|
@browser.ask([:find, xpath, @id]).map { |id| Node.new(@browser, id) }
|
44
45
|
end
|
45
|
-
|
46
|
+
|
47
|
+
def hash
|
48
|
+
@id.hash
|
49
|
+
end
|
50
|
+
|
51
|
+
# Capybara invokes `node.native ==` to determine node equality
|
52
|
+
def native
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
46
56
|
def select
|
47
57
|
@browser.ask([:select, @id])
|
48
58
|
end
|
49
|
-
|
59
|
+
|
60
|
+
def selected?
|
61
|
+
!!self['selected']
|
62
|
+
end
|
63
|
+
|
50
64
|
def set(value)
|
51
65
|
result = @browser.ask([:set, @id, value])
|
52
66
|
raise Capybara::NotSupportedByDriverError.new if result == 'not_allowed'
|
53
67
|
end
|
54
|
-
|
68
|
+
|
55
69
|
def trigger(event_type)
|
56
70
|
@browser.ask([:trigger, @id, event_type])
|
57
71
|
end
|
58
|
-
|
72
|
+
|
59
73
|
def unselect
|
60
74
|
allowed = @browser.ask([:unselect, @id])
|
61
75
|
raise Capybara::UnselectNotAllowed.new unless allowed
|
62
76
|
end
|
63
|
-
|
77
|
+
|
64
78
|
def to_s
|
65
79
|
"<#{self.class.name} #{@id}>"
|
66
80
|
end
|
67
81
|
alias :inspect :to_s
|
68
|
-
|
82
|
+
|
69
83
|
alias :select_option :select
|
70
84
|
alias :unselect_option :unselect
|
71
|
-
|
85
|
+
|
72
86
|
SYNC_DSL_METHODS = [ [:[], :attribute],
|
73
87
|
[:[]=, :set_attribute],
|
74
88
|
:tag_name,
|
@@ -76,7 +90,7 @@ module Terminus
|
|
76
90
|
:value,
|
77
91
|
[:visible?, :is_visible]
|
78
92
|
]
|
79
|
-
|
93
|
+
|
80
94
|
SYNC_DSL_METHODS.each do |method|
|
81
95
|
if Array === method
|
82
96
|
name, command = *method
|
@@ -87,7 +101,7 @@ module Terminus
|
|
87
101
|
@browser.ask([command, @id, *args])
|
88
102
|
end
|
89
103
|
end
|
90
|
-
|
104
|
+
|
91
105
|
end
|
92
106
|
end
|
93
107
|
|
data/lib/terminus/proxy.rb
CHANGED
@@ -1,96 +1,132 @@
|
|
1
1
|
module Terminus
|
2
2
|
class Proxy
|
3
|
-
|
3
|
+
|
4
4
|
CONTENT_TYPES = %w[text/plain text/html]
|
5
5
|
BASIC_RESOURCES = %w[/favicon.ico /robots.txt]
|
6
6
|
MAX_REDIRECTS = 5
|
7
7
|
REDIRECT_CODES = [301, 302, 303, 305, 307]
|
8
|
-
|
8
|
+
|
9
9
|
INFINITE_REDIRECT_RESPONSE = [
|
10
10
|
200,
|
11
11
|
{'Content-Type' => 'text/html'},
|
12
12
|
[File.read(ROOT + '/terminus/views/infinite.html')]
|
13
13
|
]
|
14
|
-
|
14
|
+
|
15
|
+
ERROR_PAGE = ERB.new(<<-HTML)
|
16
|
+
<!doctype html>
|
17
|
+
<html>
|
18
|
+
<head>
|
19
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
20
|
+
</head>
|
21
|
+
<body>
|
22
|
+
<h1><%= error.class %>: <%=ERB::Util.h error.message %></h1>
|
23
|
+
<ul>
|
24
|
+
<% error.backtrace.each do |line| %>
|
25
|
+
<li><%=ERB::Util.h line %></li>
|
26
|
+
<% end %>
|
27
|
+
</ul>
|
28
|
+
</body>
|
29
|
+
</html>
|
30
|
+
HTML
|
31
|
+
|
15
32
|
autoload :DriverBody, ROOT + '/terminus/proxy/driver_body'
|
16
33
|
autoload :External, ROOT + '/terminus/proxy/external'
|
17
34
|
autoload :Rewrite, ROOT + '/terminus/proxy/rewrite'
|
18
|
-
|
35
|
+
|
19
36
|
def self.[](app)
|
20
37
|
@proxies ||= {}
|
21
38
|
@proxies[app] ||= new(app)
|
22
39
|
end
|
23
|
-
|
40
|
+
|
24
41
|
def self.content_type(response)
|
25
42
|
type = response[1].find { |key, _| key =~ /^content-type$/i }
|
26
43
|
type && type.last.split(';').first
|
27
44
|
end
|
28
|
-
|
45
|
+
|
29
46
|
def initialize(app)
|
30
47
|
app = External.new(app) if Host === app
|
31
48
|
@app = app
|
32
49
|
@redirects = 0
|
33
50
|
end
|
34
|
-
|
51
|
+
|
35
52
|
def call(env)
|
36
53
|
add_cookies(env)
|
37
|
-
response =
|
54
|
+
response = forward_request(env)
|
38
55
|
store_cookies(env, response)
|
39
|
-
|
40
|
-
|
41
|
-
@redirects += 1
|
42
|
-
if @redirects > MAX_REDIRECTS
|
43
|
-
@redirects = 0
|
44
|
-
response = INFINITE_REDIRECT_RESPONSE
|
45
|
-
end
|
46
|
-
else
|
47
|
-
@redirects = 0
|
48
|
-
end
|
49
|
-
|
50
|
-
if location = response[1].keys.grep(/^location$/i).first
|
51
|
-
app_host = URI.parse('http://' + env['HTTP_HOST']).host
|
52
|
-
response[1][location] = Terminus.rewrite_remote(response[1][location], app_host).to_s
|
53
|
-
end
|
54
|
-
|
55
|
-
return response if response.first == -1 or # async response
|
56
|
-
REDIRECT_CODES.include?(response.first) or # redirects
|
57
|
-
BASIC_RESOURCES.include?(env['PATH_INFO']) or # not pages - favicon etc
|
58
|
-
env.has_key?('HTTP_X_REQUESTED_WITH') # Ajax calls
|
59
|
-
|
60
|
-
content_type = Proxy.content_type(response)
|
61
|
-
return response unless CONTENT_TYPES.include?(content_type)
|
62
|
-
|
63
|
-
response[1] = response[1].dup
|
64
|
-
response[1].delete_if { |key, _| key =~ /^content-length$/i }
|
65
|
-
response[2] = DriverBody.new(env, response)
|
66
|
-
|
67
|
-
response
|
56
|
+
response = detect_infinite_redirect(response)
|
57
|
+
rewrite_response(env, response)
|
68
58
|
end
|
69
|
-
|
59
|
+
|
70
60
|
private
|
71
|
-
|
61
|
+
|
72
62
|
def add_cookies(env)
|
73
63
|
return unless External === @app
|
74
64
|
cookies = Terminus.cookies.get_cookies(env['REQUEST_URI'])
|
75
65
|
env['HTTP_COOKIE'] = (cookies + [env['HTTP_COOKIE']]).compact.join('; ')
|
76
|
-
rescue => e
|
77
|
-
puts e.message
|
78
66
|
end
|
79
|
-
|
67
|
+
|
80
68
|
def store_cookies(env, response)
|
81
69
|
set_cookie = response[1].keys.grep(/^set-cookie$/i).first
|
82
70
|
return unless set_cookie
|
83
|
-
|
71
|
+
|
84
72
|
host = External === @app ? @app.uri.host : env['HTTP_HOST']
|
85
73
|
endpoint = "http://#{host}#{env['PATH_INFO']}"
|
86
|
-
|
74
|
+
|
87
75
|
[*response[1][set_cookie]].compact.each do |cookie|
|
88
76
|
cookie.split(/\s*,\s*(?=\S+=)/).each do |value|
|
89
77
|
Terminus.cookies.set_cookie(endpoint, value)
|
90
78
|
end
|
91
79
|
end
|
92
80
|
end
|
93
|
-
|
81
|
+
|
82
|
+
def forward_request(env)
|
83
|
+
env.delete('HTTP_REFERER') if Terminus.visited?(env['REQUEST_URI'])
|
84
|
+
@app.call(env)
|
85
|
+
rescue => error
|
86
|
+
[500, {'Content-Type' => 'text/html'}, [ERROR_PAGE.result(binding)]]
|
87
|
+
end
|
88
|
+
|
89
|
+
def detect_infinite_redirect(response)
|
90
|
+
if REDIRECT_CODES.include?(response.first)
|
91
|
+
@redirects += 1
|
92
|
+
if @redirects > MAX_REDIRECTS
|
93
|
+
@redirects = 0
|
94
|
+
response = INFINITE_REDIRECT_RESPONSE
|
95
|
+
end
|
96
|
+
else
|
97
|
+
@redirects = 0
|
98
|
+
end
|
99
|
+
response
|
100
|
+
end
|
101
|
+
|
102
|
+
def rewrite_response(env, response)
|
103
|
+
rewrite_location(env, response)
|
104
|
+
return response if return_unmodified?(env, response)
|
105
|
+
|
106
|
+
response[1] = response[1].dup
|
107
|
+
response[1].delete_if { |key, _| key =~ /^content-length$/i }
|
108
|
+
response[2] = DriverBody.new(env, response)
|
109
|
+
response
|
110
|
+
end
|
111
|
+
|
112
|
+
def rewrite_location(env, response)
|
113
|
+
return unless location = response[1].keys.grep(/^location$/i).first
|
114
|
+
app_host = URI.parse('http://' + env['HTTP_HOST']).host
|
115
|
+
response[1][location] = Terminus.rewrite_remote(response[1][location], app_host).to_s
|
116
|
+
end
|
117
|
+
|
118
|
+
def return_unmodified?(env, response)
|
119
|
+
return true if response.first == -1 or # async response
|
120
|
+
REDIRECT_CODES.include?(response.first) or # redirects
|
121
|
+
BASIC_RESOURCES.include?(env['PATH_INFO']) or # not pages - favicon etc
|
122
|
+
env.has_key?('HTTP_X_REQUESTED_WITH') # Ajax calls
|
123
|
+
|
124
|
+
content_type = Proxy.content_type(response)
|
125
|
+
return true unless CONTENT_TYPES.include?(content_type)
|
126
|
+
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
94
130
|
end
|
95
131
|
end
|
96
132
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module Terminus
|
2
2
|
class Proxy
|
3
|
-
|
3
|
+
|
4
4
|
class DriverBody
|
5
5
|
ASYNC_BROWSERS = %w[Android]
|
6
|
-
|
7
|
-
TEMPLATE = ERB.new(<<-
|
6
|
+
|
7
|
+
TEMPLATE = ERB.new(<<-HTML)
|
8
8
|
<script type="text/javascript" id="terminus-data">
|
9
9
|
TERMINUS_STATUS = <%= @response.first %>;
|
10
10
|
TERMINUS_HEADERS = {};
|
@@ -24,7 +24,7 @@ module Terminus
|
|
24
24
|
(function() {
|
25
25
|
var head = document.getElementsByTagName('head')[0],
|
26
26
|
script = document.createElement('script');
|
27
|
-
|
27
|
+
|
28
28
|
script.type = 'text/javascript';
|
29
29
|
script.src = '<%= @host %>/bootstrap.js';
|
30
30
|
head.appendChild(script);
|
@@ -39,9 +39,8 @@ module Terminus
|
|
39
39
|
}, 0);
|
40
40
|
</script>
|
41
41
|
<% end %>
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
HTML
|
43
|
+
|
45
44
|
def initialize(env, response)
|
46
45
|
@env = env
|
47
46
|
@response = response
|
@@ -49,35 +48,35 @@ module Terminus
|
|
49
48
|
@host = "http://#{@env['SERVER_NAME']}:#{Terminus.port}"
|
50
49
|
@async = ASYNC_BROWSERS.include?(UserAgent.parse(env['HTTP_USER_AGENT']).browser)
|
51
50
|
end
|
52
|
-
|
51
|
+
|
53
52
|
def each(&block)
|
54
53
|
script_injected = false
|
55
54
|
@source = ''
|
56
|
-
|
55
|
+
|
57
56
|
@body.each do |fragment|
|
58
57
|
@source << fragment
|
59
58
|
output = inject_script(fragment)
|
60
59
|
script_injected ||= (output != fragment)
|
61
60
|
block.call(output)
|
62
61
|
end
|
63
|
-
|
62
|
+
|
64
63
|
unless script_injected
|
65
64
|
block.call(TEMPLATE.result(binding))
|
66
65
|
end
|
67
66
|
end
|
68
|
-
|
67
|
+
|
69
68
|
private
|
70
|
-
|
69
|
+
|
71
70
|
def inject_script(fragment)
|
72
71
|
fragment.gsub(/((?:^\s*)?<\/body>)/i) do
|
73
|
-
TEMPLATE.result(binding) + $1
|
72
|
+
TEMPLATE.result(binding) + ($1 || '')
|
74
73
|
end
|
75
74
|
end
|
76
|
-
|
75
|
+
|
77
76
|
def page_source
|
78
77
|
@source.inspect.gsub('</script>', '</scr"+"ipt>')
|
79
78
|
end
|
80
79
|
end
|
81
|
-
|
80
|
+
|
82
81
|
end
|
83
82
|
end
|
@@ -1,25 +1,31 @@
|
|
1
1
|
module Terminus
|
2
2
|
class Proxy
|
3
|
-
|
3
|
+
|
4
4
|
class External < Rack::Proxy
|
5
|
+
attr_reader :uri
|
6
|
+
|
5
7
|
def initialize(uri)
|
6
8
|
@uri = uri
|
7
9
|
end
|
8
|
-
|
10
|
+
|
9
11
|
def rewrite_env(env)
|
10
12
|
env = env.dup
|
11
13
|
env['SERVER_NAME'] = @uri.host
|
12
14
|
env['SERVER_PORT'] = @uri.port
|
13
15
|
env['HTTP_HOST'] = "#{@uri.host}:#{@uri.port}"
|
14
16
|
env.delete('HTTP_ACCEPT_ENCODING')
|
15
|
-
|
17
|
+
|
16
18
|
if scheme = @uri.scheme
|
17
19
|
env['rack.url_scheme'] = scheme
|
18
20
|
end
|
19
|
-
|
21
|
+
|
22
|
+
if %w[PUT POST].include?(env['REQUEST_METHOD'])
|
23
|
+
env['CONTENT_LENGTH'] ||= '0'
|
24
|
+
end
|
25
|
+
|
20
26
|
env
|
21
27
|
end
|
22
|
-
|
28
|
+
|
23
29
|
def call(env)
|
24
30
|
dock_host = env['SERVER_NAME']
|
25
31
|
response = super
|
@@ -29,6 +35,6 @@ module Terminus
|
|
29
35
|
response
|
30
36
|
end
|
31
37
|
end
|
32
|
-
|
38
|
+
|
33
39
|
end
|
34
40
|
end
|