capybara 3.1.1 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +19 -0
- data/README.md +1 -1
- data/lib/capybara.rb +2 -0
- data/lib/capybara/config.rb +2 -1
- data/lib/capybara/driver/base.rb +1 -1
- data/lib/capybara/driver/node.rb +3 -3
- data/lib/capybara/node/actions.rb +90 -92
- data/lib/capybara/node/base.rb +2 -2
- data/lib/capybara/node/document_matchers.rb +5 -5
- data/lib/capybara/node/element.rb +47 -16
- data/lib/capybara/node/finders.rb +13 -13
- data/lib/capybara/node/matchers.rb +18 -17
- data/lib/capybara/node/simple.rb +6 -2
- data/lib/capybara/queries/ancestor_query.rb +1 -1
- data/lib/capybara/queries/base_query.rb +3 -3
- data/lib/capybara/queries/current_path_query.rb +1 -1
- data/lib/capybara/queries/match_query.rb +8 -0
- data/lib/capybara/queries/selector_query.rb +97 -42
- data/lib/capybara/queries/sibling_query.rb +1 -1
- data/lib/capybara/queries/text_query.rb +12 -7
- data/lib/capybara/rack_test/browser.rb +9 -7
- data/lib/capybara/rack_test/form.rb +15 -17
- data/lib/capybara/rack_test/node.rb +12 -12
- data/lib/capybara/result.rb +26 -15
- data/lib/capybara/rspec.rb +1 -2
- data/lib/capybara/rspec/compound.rb +4 -4
- data/lib/capybara/rspec/matchers.rb +2 -2
- data/lib/capybara/selector.rb +75 -225
- data/lib/capybara/selector/css.rb +2 -2
- data/lib/capybara/selector/filter_set.rb +17 -21
- data/lib/capybara/selector/filters/base.rb +24 -1
- data/lib/capybara/selector/filters/expression_filter.rb +3 -5
- data/lib/capybara/selector/filters/node_filter.rb +4 -4
- data/lib/capybara/selector/selector.rb +221 -69
- data/lib/capybara/selenium/driver.rb +15 -88
- data/lib/capybara/selenium/node.rb +25 -28
- data/lib/capybara/server.rb +10 -54
- data/lib/capybara/server/animation_disabler.rb +43 -0
- data/lib/capybara/server/middleware.rb +55 -0
- data/lib/capybara/session.rb +29 -30
- data/lib/capybara/session/config.rb +11 -1
- data/lib/capybara/session/matchers.rb +5 -5
- data/lib/capybara/spec/session/assert_text_spec.rb +1 -1
- data/lib/capybara/spec/session/body_spec.rb +10 -12
- data/lib/capybara/spec/session/click_link_spec.rb +3 -3
- data/lib/capybara/spec/session/element/assert_match_selector_spec.rb +1 -1
- data/lib/capybara/spec/session/fill_in_spec.rb +9 -0
- data/lib/capybara/spec/session/find_field_spec.rb +1 -1
- data/lib/capybara/spec/session/find_spec.rb +8 -3
- data/lib/capybara/spec/session/has_link_spec.rb +2 -2
- data/lib/capybara/spec/session/node_spec.rb +50 -0
- data/lib/capybara/spec/session/node_wrapper_spec.rb +5 -5
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
- data/lib/capybara/spec/session/window/windows_spec.rb +3 -5
- data/lib/capybara/spec/spec_helper.rb +4 -2
- data/lib/capybara/spec/views/with_animation.erb +46 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +3 -2
- data/spec/filter_set_spec.rb +19 -2
- data/spec/result_spec.rb +33 -1
- data/spec/rspec/features_spec.rb +6 -10
- data/spec/rspec/shared_spec_matchers.rb +4 -4
- data/spec/selector_spec.rb +74 -4
- data/spec/selenium_spec_marionette.rb +2 -0
- data/spec/server_spec.rb +1 -1
- data/spec/session_spec.rb +12 -0
- data/spec/shared_selenium_session.rb +30 -0
- metadata +8 -9
- data/.yard/templates_custom/default/class/html/selectors.erb +0 -38
- data/.yard/templates_custom/default/class/html/setup.rb +0 -17
- data/.yard/yard_extensions.rb +0 -78
- data/.yardopts +0 -1
@@ -101,7 +101,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
101
101
|
def needs_server?; true; end
|
102
102
|
|
103
103
|
def execute_script(script, *args)
|
104
|
-
browser.execute_script(script, *args
|
104
|
+
browser.execute_script(script, *native_args(args))
|
105
105
|
end
|
106
106
|
|
107
107
|
def evaluate_script(script, *args)
|
@@ -111,7 +111,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
111
111
|
|
112
112
|
def evaluate_async_script(script, *args)
|
113
113
|
browser.manage.timeouts.script_timeout = Capybara.default_max_wait_time
|
114
|
-
result = browser.execute_async_script(script, *args
|
114
|
+
result = browser.execute_async_script(script, *native_args(args))
|
115
115
|
unwrap_script_result(result)
|
116
116
|
end
|
117
117
|
|
@@ -137,7 +137,11 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
137
137
|
begin
|
138
138
|
@browser.manage.delete_all_cookies
|
139
139
|
clear_storage
|
140
|
-
rescue Selenium::WebDriver::Error::
|
140
|
+
# rescue Selenium::WebDriver::Error::NoSuchAlertError
|
141
|
+
# # Handle a bug in Firefox/Geckodriver where it thinks it needs an alert modal to exist
|
142
|
+
# # for no good reason
|
143
|
+
# retry
|
144
|
+
rescue Selenium::WebDriver::Error::UnhandledError # rubocop:disable Lint/HandleExceptions
|
141
145
|
# delete_all_cookies fails when we've previously gone
|
142
146
|
# to about:blank, so we rescue this error and do nothing
|
143
147
|
# instead.
|
@@ -167,7 +171,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
167
171
|
@browser.navigate.to("about:blank")
|
168
172
|
sleep 0.1 # slight wait for alert
|
169
173
|
@browser.switch_to.alert.accept
|
170
|
-
rescue modal_error # rubocop:disable Metrics/BlockNesting
|
174
|
+
rescue modal_error # rubocop:disable Metrics/BlockNesting, Lint/HandleExceptions
|
171
175
|
# alert now gone, should mean navigation happened
|
172
176
|
end
|
173
177
|
end
|
@@ -263,8 +267,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
263
267
|
end
|
264
268
|
|
265
269
|
def quit
|
266
|
-
@browser
|
267
|
-
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED
|
270
|
+
@browser&.quit
|
271
|
+
rescue Selenium::WebDriver::Error::SessionNotCreatedError, Errno::ECONNREFUSED # rubocop:disable Lint/HandleExceptions
|
268
272
|
# Browser must have already gone
|
269
273
|
rescue Selenium::WebDriver::Error::UnknownError => e
|
270
274
|
unless silenced_unknown_error_message?(e.message) # Most likely already gone
|
@@ -327,6 +331,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
327
331
|
|
328
332
|
private
|
329
333
|
|
334
|
+
def native_args(args)
|
335
|
+
args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg }
|
336
|
+
end
|
337
|
+
|
330
338
|
def clear_storage
|
331
339
|
if options[:clear_session_storage]
|
332
340
|
if @browser.respond_to? :session_storage
|
@@ -335,6 +343,7 @@ private
|
|
335
343
|
warn "sessionStorage clear requested but is not available for this driver"
|
336
344
|
end
|
337
345
|
end
|
346
|
+
|
338
347
|
if options[:clear_local_storage]
|
339
348
|
if @browser.respond_to? :local_storage
|
340
349
|
@browser.local_storage.clear
|
@@ -352,60 +361,6 @@ private
|
|
352
361
|
end
|
353
362
|
end
|
354
363
|
|
355
|
-
def insert_modal_handlers(accept, response_text)
|
356
|
-
prompt_response = if accept
|
357
|
-
if response_text.nil?
|
358
|
-
"default_text"
|
359
|
-
else
|
360
|
-
"'#{response_text.gsub('\\', '\\\\\\').gsub("'", "\\\\'")}'"
|
361
|
-
end
|
362
|
-
else
|
363
|
-
'null'
|
364
|
-
end
|
365
|
-
|
366
|
-
script = <<-JS
|
367
|
-
if (typeof window.capybara === 'undefined') {
|
368
|
-
window.capybara = {
|
369
|
-
modal_handlers: [],
|
370
|
-
current_modal_status: function() {
|
371
|
-
return [this.modal_handlers[0].called, this.modal_handlers[0].modal_text];
|
372
|
-
},
|
373
|
-
add_handler: function(handler) {
|
374
|
-
this.modal_handlers.unshift(handler);
|
375
|
-
},
|
376
|
-
remove_handler: function(handler) {
|
377
|
-
window.alert = handler.alert;
|
378
|
-
window.confirm = handler.confirm;
|
379
|
-
window.prompt = handler.prompt;
|
380
|
-
},
|
381
|
-
handler_called: function(handler, str) {
|
382
|
-
handler.called = true;
|
383
|
-
handler.modal_text = str;
|
384
|
-
this.remove_handler(handler);
|
385
|
-
}
|
386
|
-
};
|
387
|
-
};
|
388
|
-
|
389
|
-
var modal_handler = {
|
390
|
-
prompt: window.prompt,
|
391
|
-
confirm: window.confirm,
|
392
|
-
alert: window.alert,
|
393
|
-
called: false
|
394
|
-
}
|
395
|
-
window.capybara.add_handler(modal_handler);
|
396
|
-
|
397
|
-
window.alert = window.confirm = function(str = "") {
|
398
|
-
window.capybara.handler_called(modal_handler, str.toString());
|
399
|
-
return #{accept ? 'true' : 'false'};
|
400
|
-
}
|
401
|
-
window.prompt = function(str = "", default_text = "") {
|
402
|
-
window.capybara.handler_called(modal_handler, str.toString());
|
403
|
-
return #{prompt_response};
|
404
|
-
}
|
405
|
-
JS
|
406
|
-
execute_script script
|
407
|
-
end
|
408
|
-
|
409
364
|
def within_given_window(handle)
|
410
365
|
original_handle = current_window_handle
|
411
366
|
if handle == original_handle
|
@@ -436,34 +391,6 @@ private
|
|
436
391
|
end
|
437
392
|
end
|
438
393
|
|
439
|
-
def find_headless_modal(text: nil, **options)
|
440
|
-
# Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
|
441
|
-
# Actual wait time may be longer than specified
|
442
|
-
wait = Selenium::WebDriver::Wait.new(
|
443
|
-
timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0,
|
444
|
-
ignore: modal_error
|
445
|
-
)
|
446
|
-
begin
|
447
|
-
wait.until do
|
448
|
-
called, alert_text = evaluate_script('window.capybara && window.capybara.current_modal_status()')
|
449
|
-
if called
|
450
|
-
execute_script('window.capybara && window.capybara.modal_handlers.shift()')
|
451
|
-
regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
|
452
|
-
raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}" unless alert_text.match(regexp)
|
453
|
-
alert_text
|
454
|
-
elsif called.nil?
|
455
|
-
# page changed so modal_handler data has gone away
|
456
|
-
warn "Can't verify modal text when page change occurs - ignoring" if options[:text]
|
457
|
-
""
|
458
|
-
else
|
459
|
-
nil
|
460
|
-
end
|
461
|
-
end
|
462
|
-
rescue Selenium::WebDriver::Error::TimeOutError
|
463
|
-
raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}"
|
464
|
-
end
|
465
|
-
end
|
466
|
-
|
467
394
|
def silenced_unknown_error_message?(msg)
|
468
395
|
silenced_unknown_error_messages.any? { |r| msg =~ r }
|
469
396
|
end
|
@@ -21,7 +21,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def value
|
24
|
-
if tag_name == "select"
|
24
|
+
if tag_name == "select" && multiple?
|
25
25
|
native.find_elements(:css, "option:checked").map { |n| n[:value] || n.text }
|
26
26
|
else
|
27
27
|
native[:value]
|
@@ -76,51 +76,36 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
76
76
|
native.click if selected?
|
77
77
|
end
|
78
78
|
|
79
|
-
def click(keys = [], options
|
80
|
-
if keys.empty? && !(options
|
79
|
+
def click(keys = [], **options)
|
80
|
+
if keys.empty? && !coords?(options)
|
81
81
|
native.click
|
82
82
|
else
|
83
83
|
scroll_if_needed do
|
84
84
|
action_with_modifiers(keys, options) do |a|
|
85
|
-
|
86
|
-
a.click
|
87
|
-
else
|
88
|
-
a.click(native)
|
89
|
-
end
|
85
|
+
coords?(options) ? a.click : a.click(native)
|
90
86
|
end
|
91
87
|
end
|
92
88
|
end
|
93
|
-
rescue => e
|
89
|
+
rescue StandardError => e
|
94
90
|
if e.is_a?(::Selenium::WebDriver::Error::ElementClickInterceptedError) ||
|
95
91
|
e.message =~ /Other element would receive the click/
|
96
|
-
|
97
|
-
driver.execute_script("arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'})", self)
|
98
|
-
rescue # Swallow error if scrollIntoView with options isn't supported
|
99
|
-
end
|
92
|
+
scroll_to_center
|
100
93
|
end
|
101
94
|
raise e
|
102
95
|
end
|
103
96
|
|
104
|
-
def right_click(keys = [], options
|
97
|
+
def right_click(keys = [], **options)
|
105
98
|
scroll_if_needed do
|
106
99
|
action_with_modifiers(keys, options) do |a|
|
107
|
-
|
108
|
-
a.context_click
|
109
|
-
else
|
110
|
-
a.context_click(native)
|
111
|
-
end
|
100
|
+
coords?(options) ? a.context_click : a.context_click(native)
|
112
101
|
end
|
113
102
|
end
|
114
103
|
end
|
115
104
|
|
116
|
-
def double_click(keys = [], options
|
105
|
+
def double_click(keys = [], **options)
|
117
106
|
scroll_if_needed do
|
118
107
|
action_with_modifiers(keys, options) do |a|
|
119
|
-
|
120
|
-
a.double_click
|
121
|
-
else
|
122
|
-
a.double_click(native)
|
123
|
-
end
|
108
|
+
coords?(options) ? a.double_click : a.double_click(native)
|
124
109
|
end
|
125
110
|
end
|
126
111
|
end
|
@@ -197,8 +182,12 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
197
182
|
|
198
183
|
private
|
199
184
|
|
185
|
+
def coords?(options)
|
186
|
+
options[:x] && options[:y]
|
187
|
+
end
|
188
|
+
|
200
189
|
def boolean_attr(val)
|
201
|
-
val
|
190
|
+
val && (val != "false")
|
202
191
|
end
|
203
192
|
|
204
193
|
# a reference to the select node if this is an option node
|
@@ -229,6 +218,11 @@ private
|
|
229
218
|
def scroll_if_needed
|
230
219
|
yield
|
231
220
|
rescue ::Selenium::WebDriver::Error::MoveTargetOutOfBoundsError
|
221
|
+
scroll_to_center
|
222
|
+
yield
|
223
|
+
end
|
224
|
+
|
225
|
+
def scroll_to_center
|
232
226
|
script = <<-'JS'
|
233
227
|
try {
|
234
228
|
arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
@@ -236,8 +230,11 @@ private
|
|
236
230
|
arguments[0].scrollIntoView(true);
|
237
231
|
}
|
238
232
|
JS
|
239
|
-
|
240
|
-
|
233
|
+
begin
|
234
|
+
driver.execute_script(script, self)
|
235
|
+
rescue StandardError # rubocop:disable Lint/HandleExceptions
|
236
|
+
# Swallow error if scrollIntoView with options isn't supported
|
237
|
+
end
|
241
238
|
end
|
242
239
|
|
243
240
|
def set_date(value) # rubocop:disable Naming/AccessorMethodName
|
data/lib/capybara/server.rb
CHANGED
@@ -3,56 +3,11 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'net/http'
|
5
5
|
require 'rack'
|
6
|
+
require 'capybara/server/middleware'
|
7
|
+
require 'capybara/server/animation_disabler'
|
6
8
|
|
7
9
|
module Capybara
|
8
10
|
class Server
|
9
|
-
class Middleware
|
10
|
-
class Counter
|
11
|
-
attr_reader :value
|
12
|
-
|
13
|
-
def initialize
|
14
|
-
@value = 0
|
15
|
-
@mutex = Mutex.new
|
16
|
-
end
|
17
|
-
|
18
|
-
def increment
|
19
|
-
@mutex.synchronize { @value += 1 }
|
20
|
-
end
|
21
|
-
|
22
|
-
def decrement
|
23
|
-
@mutex.synchronize { @value -= 1 }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
attr_accessor :error
|
28
|
-
|
29
|
-
def initialize(app, server_errors)
|
30
|
-
@app = app
|
31
|
-
@counter = Counter.new
|
32
|
-
@server_errors = server_errors
|
33
|
-
end
|
34
|
-
|
35
|
-
def pending_requests?
|
36
|
-
@counter.value > 0
|
37
|
-
end
|
38
|
-
|
39
|
-
def call(env)
|
40
|
-
if env["PATH_INFO"] == "/__identify__"
|
41
|
-
[200, {}, [@app.object_id.to_s]]
|
42
|
-
else
|
43
|
-
@counter.increment
|
44
|
-
begin
|
45
|
-
@app.call(env)
|
46
|
-
rescue *@server_errors => e
|
47
|
-
@error ||= e
|
48
|
-
raise e
|
49
|
-
ensure
|
50
|
-
@counter.decrement
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
11
|
class << self
|
57
12
|
def ports
|
58
13
|
@ports ||= {}
|
@@ -61,9 +16,10 @@ module Capybara
|
|
61
16
|
|
62
17
|
attr_reader :app, :port, :host
|
63
18
|
|
64
|
-
def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors)
|
19
|
+
def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors, extra_middleware: [])
|
65
20
|
warn "Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments" unless deprecated_options.empty?
|
66
21
|
@app = app
|
22
|
+
@extra_middleware = extra_middleware
|
67
23
|
@server_thread = nil # suppress warnings
|
68
24
|
@host = deprecated_options[1] || host
|
69
25
|
@reportable_errors = deprecated_options[2] || reportable_errors
|
@@ -86,10 +42,10 @@ module Capybara
|
|
86
42
|
end
|
87
43
|
|
88
44
|
def responsive?
|
89
|
-
return false if @server_thread
|
45
|
+
return false if @server_thread&.join(0)
|
90
46
|
|
91
47
|
begin
|
92
|
-
res = if
|
48
|
+
res = if !using_ssl?
|
93
49
|
http_connect
|
94
50
|
else
|
95
51
|
https_connect
|
@@ -99,11 +55,11 @@ module Capybara
|
|
99
55
|
@using_ssl = true
|
100
56
|
end
|
101
57
|
|
102
|
-
if res.is_a?(Net::HTTPSuccess)
|
58
|
+
if res.is_a?(Net::HTTPSuccess) || res.is_a?(Net::HTTPRedirection)
|
103
59
|
return res.body == app.object_id.to_s
|
104
60
|
end
|
105
61
|
rescue SystemCallError
|
106
|
-
|
62
|
+
false
|
107
63
|
end
|
108
64
|
|
109
65
|
def wait_for_pending_requests
|
@@ -147,7 +103,7 @@ module Capybara
|
|
147
103
|
end
|
148
104
|
|
149
105
|
def middleware
|
150
|
-
@middleware ||= Middleware.new(app, @reportable_errors)
|
106
|
+
@middleware ||= Middleware.new(app, @reportable_errors, @extra_middleware)
|
151
107
|
end
|
152
108
|
|
153
109
|
def port_key
|
@@ -162,7 +118,7 @@ module Capybara
|
|
162
118
|
server = TCPServer.new(host, 0)
|
163
119
|
server.addr[1]
|
164
120
|
ensure
|
165
|
-
server
|
121
|
+
server&.close
|
166
122
|
end
|
167
123
|
end
|
168
124
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
class Server
|
5
|
+
class AnimationDisabler
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
@status, @headers, @body = @app.call(env)
|
12
|
+
return [@status, @headers, @body] unless html_content?
|
13
|
+
response = Rack::Response.new([], @status, @headers)
|
14
|
+
|
15
|
+
@body.each { |html| response.write insert_disable(html) }
|
16
|
+
@body.close if @body.respond_to?(:close)
|
17
|
+
|
18
|
+
response.finish
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def html_content?
|
24
|
+
!!(@headers["Content-Type"] =~ /html/)
|
25
|
+
end
|
26
|
+
|
27
|
+
def insert_disable(html)
|
28
|
+
html.sub(%r{(</head>)}, DISABLE_MARKUP + '\\1')
|
29
|
+
end
|
30
|
+
|
31
|
+
DISABLE_MARKUP = <<~HTML
|
32
|
+
<script defer>(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);</script>
|
33
|
+
<style>
|
34
|
+
* {
|
35
|
+
transition: none !important;
|
36
|
+
animation-duration: 0s !important;
|
37
|
+
animation-delay: 0s !important;
|
38
|
+
}
|
39
|
+
</style>
|
40
|
+
HTML
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
class Server
|
5
|
+
class Middleware
|
6
|
+
class Counter
|
7
|
+
attr_reader :value
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@value = 0
|
11
|
+
@mutex = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def increment
|
15
|
+
@mutex.synchronize { @value += 1 }
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrement
|
19
|
+
@mutex.synchronize { @value -= 1 }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_accessor :error
|
24
|
+
|
25
|
+
def initialize(app, server_errors, extra_middleware = [])
|
26
|
+
@app = app
|
27
|
+
@extended_app = extra_middleware.inject(@app) do |ex_app, klass|
|
28
|
+
klass.new(ex_app)
|
29
|
+
end
|
30
|
+
@counter = Counter.new
|
31
|
+
@server_errors = server_errors
|
32
|
+
end
|
33
|
+
|
34
|
+
def pending_requests?
|
35
|
+
@counter.value.positive?
|
36
|
+
end
|
37
|
+
|
38
|
+
def call(env)
|
39
|
+
if env["PATH_INFO"] == "/__identify__"
|
40
|
+
[200, {}, [@app.object_id.to_s]]
|
41
|
+
else
|
42
|
+
@counter.increment
|
43
|
+
begin
|
44
|
+
@extended_app.call(env)
|
45
|
+
rescue *@server_errors => e
|
46
|
+
@error ||= e
|
47
|
+
raise e
|
48
|
+
ensure
|
49
|
+
@counter.decrement
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|