Wrap server lifecycle in a block for DI
The server block receives the browser, yields the URL, and cleans up
in its own ensure. Callers can point Capture at an existing server
without Puma.
Assisted-by: Claude Opus 4.6 via Claude Code
diff --git a/BACKLOG.md b/BACKLOG.md
index 8c040dc..9901889 100644
--- a/BACKLOG.md
+++ b/BACKLOG.md
@@ -18,3 +18,6 @@
- CLI interface
- Don't re-download baseline for snapshots unnecessarily
- Dark mode
+- Add snapshots of more states to check (adding notes, etc.)
+- Scope some of the snapshots down to more specific selectors
+- Cuprite (Capybara) instead of Ferrum?
diff --git a/lib/ketchup/snapshots.rb b/lib/ketchup/snapshots.rb
index 7068bf2..cb11ec7 100644
--- a/lib/ketchup/snapshots.rb
+++ b/lib/ketchup/snapshots.rb
@@ -14,9 +14,10 @@ require_relative "web"
module Ketchup
module Snapshots
class Capture
- def initialize(output_dir:, logger: Logger.new($stderr))
+ def initialize(output_dir:, logger: Logger.new($stderr), &server)
@output_dir = Pathname(output_dir)
@logger = logger
+ @server = server || method(:default_server)
@names = []
end
@@ -24,36 +25,23 @@ module Ketchup
FileUtils.rm_rf(@output_dir)
@output_dir.mkpath
- config = Puma::Configuration.new do |c|
- c.app Web.freeze.app
- c.bind "tcp://127.0.0.1:0"
- c.log_requests false
- c.quiet
- end
-
- launcher = Puma::Launcher.new(config)
- saved_out, saved_err = $stdout.dup, $stderr.dup
- $stdout.reopen(File::NULL)
- $stderr.reopen(File::NULL)
- thread = Thread.new { launcher.run }
- sleep 0.1 until launcher.connected_ports.any?
- $stdout.reopen(saved_out)
- $stderr.reopen(saved_err)
-
- port = launcher.connected_ports.first
- @base = "http://127.0.0.1:#{port}"
- @logger.info("Server listening on port #{port}")
- user = User.find_or_create(login: "snapshot@example.com") { |u| u.name = "Snapshot User" }
-
@browser = Ferrum::Browser.new(
headless: true,
window_size: [1280, 900]
)
- @browser.headers.set(
- "Tailscale-User-Login" => user.login,
- "Tailscale-User-Name" => user.name
- )
+ @server.call(@browser) do |url|
+ @base = url
+ @logger.info("Server at #{@base}")
+ run_capture
+ end
+ ensure
+ @browser&.quit
+ end
+
+ private
+
+ def run_capture
# Empty dashboard
snap("empty-dashboard") do
goto @base
@@ -97,8 +85,34 @@ module Ketchup
snap("after-complete")
(@output_dir / "manifest.json").write(JSON.pretty_generate(@names))
+ end
+
+ def default_server(browser)
+ config = Puma::Configuration.new do |c|
+ c.app Web.freeze.app
+ c.bind "tcp://127.0.0.1:0"
+ c.log_requests false
+ c.quiet
+ end
+
+ launcher = Puma::Launcher.new(config)
+ saved_out, saved_err = $stdout.dup, $stderr.dup
+ $stdout.reopen(File::NULL)
+ $stderr.reopen(File::NULL)
+ thread = Thread.new { launcher.run }
+ sleep 0.1 until launcher.connected_ports.any?
+ $stdout.reopen(saved_out)
+ $stderr.reopen(saved_err)
+
+ url = "http://127.0.0.1:#{launcher.connected_ports.first}"
+ user = User.find_or_create(login: "snapshot@example.com") { |u| u.name = "Snapshot User" }
+ browser.headers.set(
+ "Tailscale-User-Login" => user.login,
+ "Tailscale-User-Name" => user.name
+ )
+
+ yield url
ensure
- @browser&.quit
if launcher
$stdout.reopen(File::NULL)
$stderr.reopen(File::NULL)
@@ -109,8 +123,6 @@ module Ketchup
end
end
- private
-
def goto(url)
@browser.goto(url)
end