capybara 3.1.1 → 3.2.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.
- 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
|