Class: Browserctl::Recording
- Inherits:
-
Object
- Object
- Browserctl::Recording
- Defined in:
- lib/browserctl/recording.rb
Overview
rubocop:disable Metrics/ClassLength
Constant Summary collapse
- RECORDINGS_DIR =
File.join(Dir.tmpdir, "browserctl-recordings")
- STATE_FILE =
File.("~/.browserctl/active_recording")
- RECORDABLE =
%w[page_open navigate fill click screenshot evaluate].freeze
- SENSITIVE_PARAM_PATTERN =
/\A(token|key|secret|auth|code|access_token|api_key|client_secret|state)\z/ix- SECRET_FIELD_PATTERN =
Selector tokens that signal a fill is targeting a secret-shaped field. The captured group (or matched substring) is used as the inferred field name; that name later drives the generated ‘secret_ref:` placeholder.
/\b(password|passwd|api[_-]?key|token|secret|otp|pin|client[_-]?secret|access[_-]?token)\b/i- WAIT_THRESHOLD_SECONDS =
Conservative thresholds for inferring an explicit wait between recorded steps. Gaps shorter than the threshold come from natural input cadence; gaps above it usually mean the page actually had work to do.
1.5- WAIT_PADDING_SECONDS =
5- WAIT_FLOOR_SECONDS =
5- LOG_FORMAT =
Bumped when the recording log shape changes in a way that older tooling (workflow generate, replay) cannot read.
"v0.11"
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
61 62 63 |
# File 'lib/browserctl/recording.rb', line 61 def self.active File.exist?(STATE_FILE) ? File.read(STATE_FILE).strip : nil end |
.append(cmd, response: nil, **attrs) ⇒ Object
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/browserctl/recording.rb', line 65 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 File.open(log_path(name), "a") do |f| f.puts JSON.generate(entry) end end |
.generate_workflow(name, output_path: nil, keep_log: false) ⇒ Object
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/browserctl/recording.rb', line 84 def self.generate_workflow(name, output_path: nil, keep_log: false) log = log_path(name) raise "no recording found for '#{name}'" unless File.exist?(log) raw = File.readlines(log).map { |l| JSON.parse(l, symbolize_names: true) } lines = raw.reject { |l| l[:cmd] == "_meta" } ruby = build_workflow_ruby(name, lines) File.write(output_path, ruby) if output_path warn_about_ref_interactions(lines) ruby ensure FileUtils.rm_f(log) if log && !keep_log end |
.start(name) ⇒ Object
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/browserctl/recording.rb', line 35 def self.start(name) FileUtils.mkdir_p(RECORDINGS_DIR, mode: 0o700) FileUtils.mkdir_p(File.dirname(STATE_FILE)) File.write(STATE_FILE, name) FileUtils.rm_f(log_path(name)) FileUtils.touch(log_path(name)) File.chmod(0o600, log_path(name)) File.open(log_path(name), "a") do |f| f.puts JSON.generate( cmd: "_meta", log_format: LOG_FORMAT, recording: name, started_at: Time.now.utc.iso8601 ) end name end |
.stop ⇒ Object
53 54 55 56 57 58 59 |
# File 'lib/browserctl/recording.rb', line 53 def self.stop name = active raise "no active recording — run: browserctl record start <name>" unless name File.unlink(STATE_FILE) name end |
.warn_about_ref_interactions(lines) ⇒ Object
98 99 100 101 102 103 104 |
# File 'lib/browserctl/recording.rb', line 98 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 |