Class: Browserctl::Recording
- Inherits:
-
Object
- Object
- Browserctl::Recording
- Defined in:
- lib/browserctl/recording.rb,
lib/browserctl/recording/state.rb,
lib/browserctl/recording/redactor.rb,
lib/browserctl/recording/log_writer.rb,
lib/browserctl/recording/workflow_renderer.rb
Overview
Captures a sequence of daemon commands as a JSONL log and renders it back out as a Ruby workflow file. This class is the public facade; the focused responsibilities live under ‘Browserctl::Recording::*`:
-
‘Recording::State` — singleton over the on-disk marker.
-
‘Recording::Redactor` — secret-aware redaction.
-
‘Recording::LogWriter` — log file I/O and JSONL formatting.
-
‘Recording::WorkflowRenderer` — log-to-Ruby workflow translation.
Defined Under Namespace
Modules: LogWriter, Redactor, State, WorkflowRenderer
Constant Summary collapse
- RECORDINGS_DIR =
File.join(Dir.tmpdir, "browserctl-recordings")
- STATE_FILE =
File.("~/.browserctl/active_recording")
- RECORDING_FORMAT_VERSION =
Recording-log format version, written into the ‘_meta` header and validated when generate_workflow loads a recording. See docs/reference/format-versions.md.
1- SUPPORTED_FORMAT_VERSIONS =
[RECORDING_FORMAT_VERSION].freeze
- LOG_FORMAT =
Bumped when the recording log shape changes in a way that older tooling (workflow generate, replay) cannot read.
"v0.11"- RECORDABLE =
%w[page_open navigate fill click screenshot evaluate].freeze
Class Method Summary collapse
- .active ⇒ Object
- .append(cmd, response: nil, **attrs) ⇒ Object
- .generate_workflow(name, output_path: nil, keep_log: false) ⇒ Object
- .start(name) ⇒ Object
- .stop ⇒ Object
- .warn_about_ref_interactions(lines) ⇒ Object
Class Method Details
.active ⇒ Object
42 43 44 |
# File 'lib/browserctl/recording.rb', line 42 def self.active State.active end |
.append(cmd, response: nil, **attrs) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/browserctl/recording.rb', line 46 def self.append(cmd, response: nil, **attrs) name = active return unless name return unless RECORDABLE.include?(cmd.to_s) if %w[click fill].include?(cmd.to_s) && attrs[:selector].nil? record_ref_interaction(name, cmd.to_s, attrs, response) return end attrs = prepare_attrs(cmd.to_s, attrs) entry = { cmd: cmd.to_s, ts: now }.merge(attrs.transform_keys(&:to_s)) entry.merge!((response)) if response LogWriter.append_entry(name, entry) end |
.generate_workflow(name, output_path: nil, keep_log: false) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/browserctl/recording.rb', line 63 def self.generate_workflow(name, output_path: nil, keep_log: false) log = LogWriter.log_path(name) raise Browserctl::Error, "no recording found for '#{name}'" unless File.exist?(log) raw = LogWriter.read_entries(name) LogWriter.verify_format_version!(raw, path: log) lines = raw.reject { |l| l[:cmd] == "_meta" } ruby = WorkflowRenderer.render(name, lines) File.write(output_path, ruby) if output_path warn_about_ref_interactions(lines) ruby ensure LogWriter.delete_log(name) unless keep_log end |
.start(name) ⇒ Object
32 33 34 35 36 |
# File 'lib/browserctl/recording.rb', line 32 def self.start(name) LogWriter.init_log(name) State.write(name) name end |
.stop ⇒ Object
38 39 40 |
# File 'lib/browserctl/recording.rb', line 38 def self.stop State.clear! end |
.warn_about_ref_interactions(lines) ⇒ Object
78 79 80 81 82 83 84 |
# File 'lib/browserctl/recording.rb', line 78 def self.warn_about_ref_interactions(lines) ref_count = lines.count { |l| l[:cmd] == "_ref_interaction" } return unless ref_count.positive? warn "Warning: #{ref_count} ref-based interaction(s) were captured but cannot be replayed by ref." warn "Search the generated workflow for 'TODO: ref-based' and replace with stable CSS selectors." end |