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
@@ -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
+
@@ -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
@@ -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
 
@@ -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 = @app.call(env)
54
+ response = forward_request(env)
38
55
  store_cookies(env, response)
39
-
40
- if REDIRECT_CODES.include?(response.first)
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(<<-DRIVER)
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
- DRIVER
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