Class: SeleniumScreencast::Recorder

Inherits:
Object
  • Object
show all
Defined in:
lib/selenium_screencast/recorder.rb

Overview

The recording lifecycle built on CDP (Chrome DevTools Protocol)'s Page.startScreencast. Receives a browser that responds to #devtools via injection, and knows nothing about Capybara or RSpec.

Instance Method Summary collapse

Constructor Details

#initialize(browser, output_path:, config: SeleniumScreencast.config) ⇒ Recorder

Returns a new instance of Recorder.



10
11
12
13
14
15
# File 'lib/selenium_screencast/recorder.rb', line 10

def initialize(browser, output_path:, config: SeleniumScreencast.config)
  @browser = browser
  @output_path = Pathname(output_path.to_s)
  @config = config
  @state = nil
end

Instance Method Details

#recording?Boolean

Returns:

  • (Boolean)


73
74
75
# File 'lib/selenium_screencast/recorder.rb', line 73

def recording?
  !@state.nil?
end

#startObject



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/selenium_screencast/recorder.rb', line 17

def start
  raise "SeleniumScreencast::Recorder is already recording" if recording?

  devtools = @browser.devtools(target_type: "page")

  frame_store = FrameStore.for_output(@output_path)

  callback = build_frame_callback(devtools, frame_store)
  # on returns the array of registered callbacks. Removing our own
  # callback from this array on stop keeps it from lingering into the
  # next recording.
  callbacks = devtools.page.on(:screencast_frame, &callback)

  begin
    devtools.page.start_screencast(
      format: "jpeg", quality: @config.quality, every_nth_frame: @config.every_nth_frame
    )
  rescue StandardError
    # If starting fails, remove ourselves from the shared callback array
    # immediately before re-raising, so we don't linger in it.
    callbacks.delete(callback)
    raise
  end

  @state = { devtools:, callback:, callbacks:, frame_store: }
end

#stop(encode: true) ⇒ Object

Stops the recording. With encode: false, the captured frames are discarded without invoking ffmpeg, so nothing is written to output_path. This lets a caller that only wants video under some condition (e.g. the RSpec adapter's "failed only" mode) skip the encode cost entirely rather than encoding and deleting afterwards. Recorder is told whether to encode, never why: it stays unaware of test outcomes.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/selenium_screencast/recorder.rb', line 50

def stop(encode: true)
  state = @state
  return if state.nil?

  stop_screencast(state, wait_for_inflight: encode)

  encode_frames(state) if encode
ensure
  # Even if encode raises SeleniumScreencast::Error (invalid format value,
  # ffmpeg missing), always clean up in ensure so the intermediate JPEG
  # frames don't linger uncleaned.
  # debug? only applies when we actually encoded: keeping frames is for
  # inspecting what a produced video was built from, so with encode: false
  # they are always removed. Otherwise a full suite in "failed only" mode
  # would leave a *_frames dir behind for every passing example.
  state&.fetch(:frame_store)&.cleanup(keep: @config.debug? && encode)
  @state = nil
  # NOTE: never close devtools. It's a shared instance memoized per target
  # on the browser side; closing it would break other recordings and
  # selenium's own devtools usage too. Removing ourselves from the
  # callback array is sufficient cleanup.
end