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