Module: RSpec::Signal::Integrations::Capybara

Defined in:
lib/rspec/signal/integrations/capybara.rb

Overview

Captures browser state for failing system/feature examples.

A failing system spec whose report says only "Unable to find css" is much less useful than one that also says which URL the browser was on and what the JavaScript console said. Everything here is best effort: any problem simply means the report has less detail.

Constant Summary collapse

RELEVANT_TYPES =
%i[system feature].freeze
MAX_CONSOLE_LINES =
20

Class Method Summary collapse

Class Method Details

.capture(example, signal_config) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rspec/signal/integrations/capybara.rb', line 29

def capture(example, signal_config)
  return unless example.exception
  return unless relevant?(example)

  session = existing_session
  return unless session

  diagnostics = collect(session, signal_config)
  return if diagnostics.empty?

  example.[:rspec_signal_diagnostics] =
    (example.[:rspec_signal_diagnostics] || {}).merge(diagnostics)
rescue StandardError, ::LoadError
  nil
end

.collect(session, signal_config) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
# File 'lib/rspec/signal/integrations/capybara.rb', line 63

def collect(session, signal_config)
  diagnostics = {}
  diagnostics[:url] = try { session.current_url }
  diagnostics[:path] = try { session.current_path }
  diagnostics[:title] = try { session.title }
  diagnostics[:status_code] = try { session.status_code }
  diagnostics[:driver] = try { ::Capybara.current_driver }
  diagnostics[:console] = console_messages(session)
  diagnostics[:saved_page] = write_page_html(session, signal_config) if signal_config.capture_page_html
  diagnostics.reject { |_, value| value.nil? || value.to_s.strip.empty? }
end

.console_messages(session) ⇒ Object

Browser console output is small and often contains the actual cause of a JavaScript-driven failure.



77
78
79
80
81
82
# File 'lib/rspec/signal/integrations/capybara.rb', line 77

def console_messages(session)
  logs = try { session.driver.browser.logs.get(:browser) }
  return nil if logs.nil? || logs.empty?

  logs.last(MAX_CONSOLE_LINES).map { |entry| try { "#{entry.level}: #{entry.message}" } }.compact
end

.existing_sessionObject

Deliberately avoids Capybara.current_session, which would create a session (and possibly boot a browser) if none exists.



52
53
54
55
56
57
58
59
60
61
# File 'lib/rspec/signal/integrations/capybara.rb', line 52

def existing_session
  return nil unless defined?(::Capybara)

  pool = ::Capybara.instance_variable_get(:@session_pool)
  return nil if pool.nil? || pool.empty?

  ::Capybara.current_session
rescue StandardError
  nil
end

.install!(rspec_config, signal_config) ⇒ Object



20
21
22
23
24
25
26
27
# File 'lib/rspec/signal/integrations/capybara.rb', line 20

def install!(rspec_config, signal_config)
  # `prepend_after` so this runs *before* any example-group-level
  # `after(:each)` hook -- rspec-rails tears the Capybara session down
  # at that level, and capturing after teardown would find nothing.
  rspec_config.prepend_after(:each) do |example|
    RSpec::Signal::Integrations::Capybara.capture(example, signal_config)
  end
end

.relevant?(example) ⇒ Boolean

Returns:

  • (Boolean)


45
46
47
48
# File 'lib/rspec/signal/integrations/capybara.rb', line 45

def relevant?(example)
   = example.
  RELEVANT_TYPES.include?([:type]) || [:js] == true
end

.tryObject



94
95
96
97
98
# File 'lib/rspec/signal/integrations/capybara.rb', line 94

def try
  yield
rescue StandardError, ::NotImplementedError
  nil
end

.write_page_html(session, signal_config) ⇒ Object



84
85
86
87
88
89
90
91
92
# File 'lib/rspec/signal/integrations/capybara.rb', line 84

def write_page_html(session, signal_config)
  dir = File.join(signal_config.output_path, "pages")
  FileUtils.mkdir_p(dir)
  path = File.join(dir, "#{Time.now.to_i}-#{rand(1_000_000)}.html")
  File.write(path, session.html)
  signal_config.project.display_path(path)
rescue StandardError
  nil
end