Class: Capybara::Lightpanda::Binary
- Inherits:
-
Object
- Object
- Capybara::Lightpanda::Binary
- Defined in:
- lib/capybara/lightpanda/binary.rb
Defined Under Namespace
Classes: Result
Constant Summary collapse
- GITHUB_RELEASE_URL =
"https://github.com/lightpanda-io/browser/releases/download"- PLATFORMS =
{ %w[x86_64 linux] => "lightpanda-x86_64-linux", %w[aarch64 darwin] => "lightpanda-aarch64-macos", %w[arm64 darwin] => "lightpanda-aarch64-macos", }.freeze
- DEFAULT_CACHE_TIME =
86_400- PROVISION_HINT =
One-liner that re-provisions the binary from a process with no HTTP-stubbing loaded (VCR/WebMock guard the test process itself). Referenced from the stale-fallback warning and BETA_TESTING.md.
"bundle exec ruby -r capybara-lightpanda " \ "-e 'Capybara::Lightpanda::Binary.remove; puts Capybara::Lightpanda::Binary.update'"
Class Attribute Summary collapse
- .cache_time ⇒ Object
- .install_dir ⇒ Object
- .logger ⇒ Object
-
.proxy_addr ⇒ Object
Returns the value of attribute proxy_addr.
-
.proxy_pass ⇒ Object
Returns the value of attribute proxy_pass.
-
.proxy_port ⇒ Object
Returns the value of attribute proxy_port.
-
.proxy_user ⇒ Object
Returns the value of attribute proxy_user.
-
.required_version ⇒ Object
Set a specific release tag (e.g. “0.3.0”) to pin downloads to that release.
Class Method Summary collapse
- .configure {|_self| ... } ⇒ Object
-
.current_version ⇒ Object
Returns the ‘lightpanda version` output of the cached binary, or nil if the binary isn’t present / not runnable.
- .default_binary_path ⇒ Object
- .download ⇒ Object
- .exec ⇒ Object
- .fetch(url) ⇒ Object
-
.install_path ⇒ Object
Path the gem writes the downloaded binary to.
- .path ⇒ Object
- .platform_binary ⇒ Object
-
.remove ⇒ Object
Delete the cached binary.
- .run ⇒ Object
-
.update ⇒ Object
Canonical entrypoint: ensure the binary at install_path is current, download if needed, return its path.
-
.update_hint(binary_path) ⇒ Object
Build a path-appropriate “how to update” command for Process’s too-old-binary error.
- .version ⇒ Object
Class Attribute Details
.cache_time ⇒ Object
55 56 57 |
# File 'lib/capybara/lightpanda/binary.rb', line 55 def cache_time @cache_time ||= Integer(ENV.fetch("LIGHTPANDA_CACHE_TIME", DEFAULT_CACHE_TIME)) end |
.install_dir ⇒ Object
59 60 61 |
# File 'lib/capybara/lightpanda/binary.rb', line 59 def install_dir @install_dir ||= File.dirname(default_binary_path) end |
.logger ⇒ Object
63 64 65 66 67 68 |
# File 'lib/capybara/lightpanda/binary.rb', line 63 def logger return @logger if defined?(@logger) && @logger return nil unless ENV["LIGHTPANDA_DEBUG"] @logger = Capybara::Lightpanda::Logger.new($stderr.tap { |s| s.sync = true }) end |
.proxy_addr ⇒ Object
Returns the value of attribute proxy_addr.
53 54 55 |
# File 'lib/capybara/lightpanda/binary.rb', line 53 def proxy_addr @proxy_addr end |
.proxy_pass ⇒ Object
Returns the value of attribute proxy_pass.
53 54 55 |
# File 'lib/capybara/lightpanda/binary.rb', line 53 def proxy_pass @proxy_pass end |
.proxy_port ⇒ Object
Returns the value of attribute proxy_port.
53 54 55 |
# File 'lib/capybara/lightpanda/binary.rb', line 53 def proxy_port @proxy_port end |
.proxy_user ⇒ Object
Returns the value of attribute proxy_user.
53 54 55 |
# File 'lib/capybara/lightpanda/binary.rb', line 53 def proxy_user @proxy_user end |
.required_version ⇒ Object
Set a specific release tag (e.g. “0.3.0”) to pin downloads to that release. When nil, the rolling “nightly” tag is used. The pin only affects download URL construction — the gem’s MINIMUM_NIGHTLY_BUILD floor is still enforced at process start.
48 49 50 |
# File 'lib/capybara/lightpanda/binary.rb', line 48 def required_version @required_version end |
Class Method Details
.configure {|_self| ... } ⇒ Object
70 71 72 |
# File 'lib/capybara/lightpanda/binary.rb', line 70 def configure yield self end |
.current_version ⇒ Object
Returns the ‘lightpanda version` output of the cached binary, or nil if the binary isn’t present / not runnable.
155 156 157 158 159 160 161 162 163 |
# File 'lib/capybara/lightpanda/binary.rb', line 155 def current_version path = install_path return nil unless File.executable?(path) stdout, _, status = Open3.capture3(path, "version") status.success? ? stdout.strip : nil rescue Errno::ENOENT nil end |
.default_binary_path ⇒ Object
242 243 244 245 246 |
# File 'lib/capybara/lightpanda/binary.rb', line 242 def default_binary_path cache_dir = ENV.fetch("XDG_CACHE_HOME") { File.("~/.cache") } File.join(cache_dir, "lightpanda", "lightpanda") end |
.download ⇒ Object
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/capybara/lightpanda/binary.rb', line 189 def download binary_name = platform_binary tag = required_version || "nightly" url = "#{GITHUB_RELEASE_URL}/#{tag}/#{binary_name}" destination = install_path log("Downloading #{binary_name} (#{tag}) → #{destination}") FileUtils.mkdir_p(File.dirname(destination)) download_file(url, destination) FileUtils.chmod(0o755, destination) @path = destination destination end |
.exec ⇒ Object
173 174 175 |
# File 'lib/capybara/lightpanda/binary.rb', line 173 def exec(*) Kernel.exec(path, *) end |
.fetch(url) ⇒ Object
177 178 179 180 181 182 |
# File 'lib/capybara/lightpanda/binary.rb', line 177 def fetch(url) result = run("fetch", "--dump", url) raise BinaryError, result.stderr unless result.success? result.stdout end |
.install_path ⇒ Object
Path the gem writes the downloaded binary to. Honors a user-configured install_dir; otherwise falls back to default_binary_path.
250 251 252 253 254 255 256 |
# File 'lib/capybara/lightpanda/binary.rb', line 250 def install_path if @install_dir File.join(@install_dir, "lightpanda") else default_binary_path end end |
.path ⇒ Object
74 75 76 |
# File 'lib/capybara/lightpanda/binary.rb', line 74 def path @path ||= update end |
.platform_binary ⇒ Object
235 236 237 238 239 240 |
# File 'lib/capybara/lightpanda/binary.rb', line 235 def platform_binary arch = normalize_arch(RbConfig::CONFIG["host_cpu"]) os = normalize_os(RbConfig::CONFIG["host_os"]) PLATFORMS[[arch, os]] || raise(UnsupportedPlatformError, "Unsupported platform: #{arch}-#{os}") end |
.remove ⇒ Object
Delete the cached binary. Returns the path that was deleted, or nil if nothing was there.
140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/capybara/lightpanda/binary.rb', line 140 def remove path = install_path unless File.exist?(path) log("Nothing to remove at #{path}") return nil end File.delete(path) @path = nil log("Removed #{path}") path end |
.run ⇒ Object
165 166 167 168 169 170 171 |
# File 'lib/capybara/lightpanda/binary.rb', line 165 def run(*) stdout, stderr, status = Open3.capture3(path, *) Result.new(stdout: stdout, stderr: stderr, status: status) rescue Errno::ENOENT raise BinaryNotFoundError, "Lightpanda binary not found" end |
.update ⇒ Object
Canonical entrypoint: ensure the binary at install_path is current, download if needed, return its path. Pinned (required_version set) never re-downloads when present. Unpinned re-downloads when older than cache_time. When unpinned and the gem cache is empty/stale, an already-installed ‘lightpanda` on PATH (e.g. via Homebrew) wins over re-downloading — keeps test suites running under VCR/WebMock from triggering surprise HTTP to github.com.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/capybara/lightpanda/binary.rb', line 85 def update destination = install_path if required_version if File.executable?(destination) log("Pinned #{required_version} present at #{destination}") return destination end return download end if cached_fresh?(destination) log("Cached binary at #{destination} is fresh (< #{cache_time}s)") return destination end if (system_path = system_binary_path) log("Using lightpanda from PATH at #{system_path}") return system_path end # Stale-or-absent cache, nothing on PATH: refresh from the network. # If that fails (GitHub 5xx, DNS/connect timeouts, SocketError) but a # usable — if stale — binary is already cached, keep using it rather # than hard-failing. A cold cache (nothing on disk) still surfaces the # error. The MINIMUM_NIGHTLY_BUILD floor is enforced downstream in # Process#start, so a sub-floor binary can't slip in. # # Deliberately StandardError, not Exception: WebMock's # NetConnectNotAllowedError descends from Exception so it propagates # through app rescue blocks by design — a test suite that blocks net # connections SHOULD fail loudly here, not silently fall back. CI # pre-provisions the binary outside that guard instead (real-apps.yml). begin download rescue StandardError => e raise unless File.executable?(destination) # Kernel.warn, not log: log() is silent unless LIGHTPANDA_DEBUG or # an explicit logger is set, and this fallback is exactly the # moment the user needs to hear about — a VCR-guarded suite (whose # UnhandledHTTPRequestError is a StandardError, unlike raw # WebMock's Exception) lands here silently, keeps a stale binary, # and later hits a confusing MINIMUM_NIGHTLY_BUILD floor error # with no trace of the blocked download. warn("[capybara-lightpanda] Binary download failed (#{e.class}: #{e.}); " \ "falling back to the cached binary at #{destination}. " \ "If your suite stubs HTTP (VCR/WebMock), pre-provision from an " \ "unstubbed process: #{PROVISION_HINT}") destination end end |
.update_hint(binary_path) ⇒ Object
Build a path-appropriate “how to update” command for Process’s too-old-binary error. Three branches:
-
Symlink into a ‘/Cellar/` directory → installed via Homebrew; suggest `brew update && brew upgrade lightpanda` (brew pins each user’s binary at install time and doesn’t refresh on its own when the tap publishes a newer nightly).
-
Path equals our own cache → suggest the require-the-gem one-liner. NOT the lightpanda:binary:* rake tasks: in a Rails app the gem usually sits in the :test Gemfile group, so the tasks only exist under RAILS_ENV=test (the Railtie can’t help a plain ‘bundle exec rake` in development), and outside Rails they’re never loaded at all. The one-liner requires the gem explicitly, so it works from any environment. The ‘remove` step is required because `update` honors `cache_time` and would otherwise no-op on a too-old-but-not-yet-expired file.
-
Anything else (user-managed install at a custom path) → keep the curl-overwrite suggestion, since we don’t know how the file got there.
224 225 226 227 228 229 230 231 232 233 |
# File 'lib/capybara/lightpanda/binary.rb', line 224 def update_hint(binary_path) if brew_managed?(binary_path) "brew update && brew upgrade lightpanda" elsif binary_path == install_path PROVISION_HINT else "curl -sL #{GITHUB_RELEASE_URL}/nightly/#{platform_binary} " \ "-o #{binary_path} && chmod +x #{binary_path}" end end |
.version ⇒ Object
184 185 186 187 |
# File 'lib/capybara/lightpanda/binary.rb', line 184 def version result = run("version") result.output.strip end |