Class: Capybara::Simulated::Trace

Inherits:
Object
  • Object
show all
Defined in:
lib/capybara/simulated/trace.rb

Overview

Per-test trace of Capybara actions with DOM snapshots, console output, and network requests interleaved. JSON output, one file per test — downstream tooling builds whatever viewer it wants.

Off by default. ‘CSIM_TRACE_DIR=/path/to/dir` enables auto-mode via `Browser#record_action`; the RSpec hook in `csim_rspec.rb` persists with a slugged filename. Programmatic activation is via `Driver#start_tracing` / `#stop_tracing`.

Defined Under Namespace

Classes: Step

Constant Summary collapse

VIEWER_TEMPLATE_PATH =
File.expand_path('trace_viewer.html', __dir__)
VIEWER_DATA_TOKEN =
'__CSIM_TRACE_DATA__'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(metadata: {}) ⇒ Trace

Returns a new instance of Trace.



52
53
54
55
56
57
58
59
# File 'lib/capybara/simulated/trace.rb', line 52

def initialize(metadata: {})
  @steps        = []
  @metadata     = 
  @started_at   = monotonic_ms
  @console_buf  = []
  @network_buf  = []
  @open_step    = nil
end

Instance Attribute Details

#metadataObject (readonly)

Returns the value of attribute metadata.



50
51
52
# File 'lib/capybara/simulated/trace.rb', line 50

def 
  @metadata
end

#stepsObject (readonly)

Returns the value of attribute steps.



50
51
52
# File 'lib/capybara/simulated/trace.rb', line 50

def steps
  @steps
end

Class Method Details

.render_viewer(json_text) ⇒ Object

Render the self-contained HTML viewer for a trace JSON string, embedding it inline (the ‘capybara-simulated trace` CLI is the caller). `</` → `</` so an embedded `</script>` inside a DOM snapshot can’t close the data block early — still valid JSON (‘/` is a legal JSON escape for `/`). The whole point of inline embedding over fetch / `import … with { type: ’json’ }‘ is that the result opens straight from `file://` with no server (module / fetch loads are CORS-blocked for `file://` origins).



43
44
45
46
47
48
# File 'lib/capybara/simulated/trace.rb', line 43

def self.render_viewer(json_text)
  template = (@viewer_template ||= File.read(VIEWER_TEMPLATE_PATH))
  # Block form: the replacement is taken literally, so backslashes
  # in the JSON aren't interpreted as regexp backreferences.
  template.sub(VIEWER_DATA_TOKEN) { json_text.to_s.gsub('</', '<\/') }
end

Instance Method Details

#begin_step(kind, description:, url_before: nil) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/capybara/simulated/trace.rb', line 90

def begin_step(kind, description:, url_before: nil)
  finish_step if @open_step
  @open_step = {
    kind:        kind,
    description: description,
    url_before:  url_before,
    start_ms:    monotonic_ms
  }
  @console_buf = []
  @network_buf = []
end

#empty?Boolean

Returns:

  • (Boolean)


123
# File 'lib/capybara/simulated/trace.rb', line 123

def empty? = @steps.empty?

#finish_step(url_after: nil, dom_after: nil, error: nil) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/capybara/simulated/trace.rb', line 102

def finish_step(url_after: nil, dom_after: nil, error: nil)
  return unless @open_step
  s = @open_step
  @steps << Step.new(
    index:        @steps.size,
    kind:         s[:kind],
    description:  s[:description],
    url_before:   s[:url_before],
    url_after:    url_after,
    dom_after:    dom_after,
    console:      @console_buf,
    network:      @network_buf,
    elapsed_ms:   (s[:start_ms] - @started_at).round,
    duration_ms:  (monotonic_ms - s[:start_ms]).round,
    error:        error
  )
  @open_step   = nil
  @console_buf = []
  @network_buf = []
end

#log_console(severity, message) ⇒ Object

Pushed from ‘Browser#log_console` (the JS bridge’s ‘console.*` host-fn target) and `Browser#rack_request` (network). Entries land on the currently-open step; outside a step they’re dropped (boot noise, post-test cleanup).



65
66
67
68
# File 'lib/capybara/simulated/trace.rb', line 65

def log_console(severity, message)
  return unless @open_step
  @console_buf << {severity: severity.to_s, message: message.to_s}
end

#log_network(method, url, status, content_type: nil, size: nil, duration_ms: nil, redirected: nil, request_headers: nil, request_body: nil, response_headers: nil, response_body: nil) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/capybara/simulated/trace.rb', line 70

def log_network(method, url, status,
                content_type: nil, size: nil, duration_ms: nil, redirected: nil,
                request_headers: nil, request_body: nil,
                response_headers: nil, response_body: nil)
  return unless @open_step
  @network_buf << {
    method:           method.to_s,
    url:              url.to_s,
    status:           status,
    content_type:     content_type,
    size:             size,
    duration_ms:      duration_ms,
    redirected:       redirected,
    request_headers:  request_headers,
    request_body:     request_body,
    response_headers: response_headers,
    response_body:    response_body
  }.compact  # drop fields the caller couldn't determine, keeping entries lean
end

#to_hObject



125
126
127
# File 'lib/capybara/simulated/trace.rb', line 125

def to_h
  {version: 1, metadata: @metadata, steps: @steps.map(&:to_h)}
end

#to_json(*args) ⇒ Object



129
# File 'lib/capybara/simulated/trace.rb', line 129

def to_json(*args) = JSON.generate(to_h, *args)

#write_json(path) ⇒ Object



131
132
133
134
135
# File 'lib/capybara/simulated/trace.rb', line 131

def write_json(path)
  FileUtils.mkdir_p(File.dirname(path))
  File.write(path, to_json)
  path
end