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