capybara-lightpanda 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +1 -1
- data/lib/capybara/lightpanda/browser.rb +83 -7
- data/lib/capybara/lightpanda/node.rb +16 -0
- data/lib/capybara/lightpanda/process.rb +95 -41
- data/lib/capybara/lightpanda/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2515195575d1146f7cd14f479801360770c65d39d84c2bdc58be987e503bda88
|
|
4
|
+
data.tar.gz: ab94aba998ccafb870e109504e7f33dd22a35bcd2ba79750cfd2bb79226feaf1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3eadd479f7c543354a89c3f69a1be6ee94be6067b52171766c3da1b3c9b091a9e404c987e9af11c5230c89e8df319013822748b0bd6ba0e7e56ebd143f3e7689
|
|
7
|
+
data.tar.gz: fec1bbad25a59b177b6ac117e82bf65eef995196f233aa96742f71b2def90133132b913c0591bf40f2ca554d09cb4e6b26cbb132a0f3ae5e7ab811efa4504914
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0] - 2026-08-28
|
|
4
|
+
|
|
5
|
+
> **Update Lightpanda before upgrading.** This release requires a Lightpanda nightly build ≥ 8875, and the rolling `nightly` tag now carries it (8982 as of 2026-08-28), so updating the browser is all it takes. **No tagged Lightpanda release is new enough yet**: 0.3.7 is below the floor, so version pinning stays unavailable until Lightpanda ships its next release.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **The keyboard activates things.** Pressing Enter on a button, link or submit input — and Space on a button, checkbox or radio — now does what it does in a real browser: the control activates, forms submit, checkboxes toggle. Previously `send_keys(:enter)` and `send_keys(:space)` were silently ignored on those controls, so keyboard-accessibility specs passed by doing nothing. Printable keys also emit `keypress` now, and a handler that cancels `keypress` correctly suppresses Enter's implicit form submission.
|
|
10
|
+
- **A closed `<dialog>` is properly hidden.** Text and controls inside a `<dialog>` that has never been opened no longer match Capybara's default visibility filter. Suites using the `<dialog>`-as-confirmation idiom (Turbo, Spree 5's admin) could previously "find" and click a confirm button that was not on screen — passing where Chrome would have raised `ElementNotFound`.
|
|
11
|
+
- **Uppercase inline styles hide elements.** `style="display: NONE"` and `style="visibility: HIDDEN"` are now treated as hidden, matching the CSS spec's case-insensitive keywords. Legacy templates and CMS output that upcase keywords no longer read as visible.
|
|
12
|
+
- **`ws_url:` accepts `localhost`.** Connecting to an externally-managed Lightpanda over `ws://localhost:PORT/` works; previously only an IP literal such as `ws://127.0.0.1:PORT/` was accepted by the browser's handshake.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Keyboard-triggered navigation is waited for.** Now that Enter activates a submit button, pressing it starts a real navigation — but `send_keys` returned without waiting for it, so the very next `current_url` (or any non-waiting assertion) could still report the previous page. Keystrokes now settle the page the same way a click does. As with a click, a navigation whose server round-trip is slow still needs one of Capybara's waiting matchers, such as `have_current_path`.
|
|
17
|
+
|
|
18
|
+
- **Iframes keep working when the browser stops loading them by default.** Lightpanda build 8946 turns off `<iframe>` and Web Worker loading unless asked: the parser still puts the iframe in the DOM, but no frame is ever created, so `within_frame` and `switch_to_frame` find nothing and report no cause. The driver now asks for both on every session, which holds today's behavior across the change and needs no newer browser — the request is understood by every build the gem supports. Nothing to configure. If you run the rolling nightly, take this before your browser passes 8946.
|
|
19
|
+
|
|
20
|
+
- **A browser you manage yourself is version-checked like one the gem spawns.** `ws_url:` was the only way into the driver that skipped the version floor: a Lightpanda too old for the gem connected happily, then failed further along as a CDP error naming neither the version nor the option that let it through. The driver now reads the version over CDP at connect and refuses anything below the floor, with the same message the spawn path produces. An endpoint that cannot report a version at all — a Lightpanda older than the command, or a Chrome pointed at by mistake — is refused for the same reason. `page.driver.browser.version`, `.nightly_build` and `.release` report the browser on this path now instead of returning `nil`.
|
|
21
|
+
|
|
3
22
|
## [0.11.0] - 2026-08-25
|
|
4
23
|
|
|
5
24
|
> **Update Lightpanda before upgrading.** This release requires a Lightpanda nightly build ≥ 8796 (2026-08-25 or newer). **No tagged Lightpanda release is new enough yet**: 0.3.7 is below the floor and is now refused, so version pinning is unavailable until Lightpanda ships its next release — CI runs on the rolling nightly in the meantime.
|
data/README.md
CHANGED
|
@@ -57,7 +57,7 @@ driven_by :lightpanda
|
|
|
57
57
|
> Lightpanda is a headless agentic browser, not a layout engine. External `<link rel="stylesheet">` **are** fetched and applied (the gem enables this by default), and `@media` / `window.matchMedia()` evaluate against the `window_size` you configure — so a mobile-only CTA gated by `@media (max-width: …)` resolves at the width you ask for. What's missing is *layout*: nothing reflows, `getBoundingClientRect` stays synthetic, and there is no real scroll. Specs that assert on pixel geometry, scrolling, or screenshots — plus the two that catch people out, a second browser tab and a menu revealed purely by CSS `:hover` — should stay on Cuprite (or whichever full-browser driver you were already using). The [per-spec dual-driver setup](https://navidemad.github.io/capybara-lightpanda/docs/#dual-per-spec) routes that minority to Cuprite and the structural majority to Lightpanda for speed.
|
|
58
58
|
|
|
59
59
|
> [!TIP]
|
|
60
|
-
> For reproducible CI, pin the browser: `Capybara::Lightpanda::Binary.required_version = "0.3.8"`. (0.3.8 is the first release that will clear the gem's current floor, nightly build
|
|
60
|
+
> For reproducible CI, pin the browser: `Capybara::Lightpanda::Binary.required_version = "0.3.8"`. (0.3.8 is the first release that will clear the gem's current floor, nightly build 8875 — until upstream tags it, the driver needs the rolling `nightly` tag.) Without a pin the driver tracks Lightpanda's rolling `nightly` tag, which moves under you. See [Pinning the browser version](https://navidemad.github.io/capybara-lightpanda/docs/#pinning).
|
|
61
61
|
|
|
62
62
|
## Credits
|
|
63
63
|
|
|
@@ -73,28 +73,34 @@ module Capybara
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
# Lightpanda
|
|
77
|
-
# parsed
|
|
78
|
-
# the
|
|
76
|
+
# Lightpanda version string (e.g. "1.0.0-nightly.8925+a7bda0ea5") and its
|
|
77
|
+
# parsed build number. Learned from `lightpanda version` at Process
|
|
78
|
+
# startup on the spawn path, and from the CDP `LP.version` command when
|
|
79
|
+
# connecting to an externally-managed browser via ws_url — so these are
|
|
80
|
+
# populated either way, and a caller inspecting them does not have to
|
|
81
|
+
# know which path built the session.
|
|
79
82
|
def version
|
|
80
|
-
@process&.version
|
|
83
|
+
@process&.version || @remote_version
|
|
81
84
|
end
|
|
82
85
|
|
|
83
86
|
# Set on the nightly/dev channel only; nil for a tagged release, which
|
|
84
87
|
# carries no build counter. `release` is its mirror image — exactly one of
|
|
85
|
-
# the two is non-nil once the
|
|
88
|
+
# the two is non-nil once the session is up.
|
|
86
89
|
def nightly_build
|
|
87
|
-
@process&.nightly_build
|
|
90
|
+
@process&.nightly_build || @remote_nightly_build
|
|
88
91
|
end
|
|
89
92
|
|
|
90
93
|
def release
|
|
91
|
-
@process&.release
|
|
94
|
+
@process&.release || @remote_release
|
|
92
95
|
end
|
|
93
96
|
|
|
94
97
|
def initialize(options = {})
|
|
95
98
|
@options = Options.new(options)
|
|
96
99
|
@process = nil
|
|
97
100
|
@client = nil
|
|
101
|
+
@remote_version = nil
|
|
102
|
+
@remote_nightly_build = nil
|
|
103
|
+
@remote_release = nil
|
|
98
104
|
@target_id = nil
|
|
99
105
|
@session_id = nil
|
|
100
106
|
@browser_context_id = nil
|
|
@@ -121,6 +127,7 @@ module Capybara
|
|
|
121
127
|
|
|
122
128
|
if @options.ws_url?
|
|
123
129
|
@client = Client.new(@options.ws_url, @options)
|
|
130
|
+
check_remote_version
|
|
124
131
|
else
|
|
125
132
|
@process = Process.new(@options)
|
|
126
133
|
@process.start
|
|
@@ -152,6 +159,9 @@ module Capybara
|
|
|
152
159
|
attach_result = @client.command("Target.attachToTarget", { targetId: @target_id, flatten: true })
|
|
153
160
|
@session_id = attach_result["sessionId"]
|
|
154
161
|
|
|
162
|
+
# Must precede the first navigation: sub-resource loading is a
|
|
163
|
+
# per-BrowserContext setting read while the document loads.
|
|
164
|
+
configure_loading
|
|
155
165
|
@turbo_event.set
|
|
156
166
|
subscribe_to_console_logs
|
|
157
167
|
subscribe_to_console_capture
|
|
@@ -498,6 +508,34 @@ module Capybara
|
|
|
498
508
|
|
|
499
509
|
private
|
|
500
510
|
|
|
511
|
+
# Keep iframes and workers loading.
|
|
512
|
+
#
|
|
513
|
+
# Upstream #3305 (build 8946) turned `Config.LoadResources` into a
|
|
514
|
+
# four-field struct defaulting every sub-resource to false, so
|
|
515
|
+
# `Frame.iframeAddedCallback` now returns early unless `iframe` is set:
|
|
516
|
+
# the parser still registers the `<iframe>` in the DOM, but no child
|
|
517
|
+
# frame is ever created. That silently breaks `switch_to_frame` and
|
|
518
|
+
# Capybara's `within_frame` — core driver API, not an optional extra —
|
|
519
|
+
# and there is no CLI opt-in the gem can use below 8946, because the
|
|
520
|
+
# `--load-resources` *value* `iframe` only parses from that build.
|
|
521
|
+
#
|
|
522
|
+
# The `subFrame`/`worker` params predate #3305 and simply write a
|
|
523
|
+
# different field after it, which makes this the one form that spans the
|
|
524
|
+
# whole supported range: a no-op below 8946 where both already defaulted
|
|
525
|
+
# on, load-bearing above it, and no MINIMUM_NIGHTLY_BUILD bump either way.
|
|
526
|
+
#
|
|
527
|
+
# Workers are included to hold the gem's behavior still across the flip
|
|
528
|
+
# rather than to satisfy a feature we ship: a worker that silently never
|
|
529
|
+
# runs is the same debugging trap as an iframe that silently never loads.
|
|
530
|
+
#
|
|
531
|
+
# Not rescued on purpose. LP.configureLoading predates the floor by a
|
|
532
|
+
# wide margin, so a failure here means the endpoint is not a Lightpanda
|
|
533
|
+
# we understand, and swallowing it would restore the exact silence this
|
|
534
|
+
# call exists to prevent.
|
|
535
|
+
def configure_loading
|
|
536
|
+
page_command("LP.configureLoading", subFrame: true, worker: true)
|
|
537
|
+
end
|
|
538
|
+
|
|
501
539
|
def register_auto_scripts
|
|
502
540
|
page_command("Page.addScriptToEvaluateOnNewDocument", source: AutoScripts::JS)
|
|
503
541
|
end
|
|
@@ -590,6 +628,44 @@ module Capybara
|
|
|
590
628
|
end
|
|
591
629
|
end
|
|
592
630
|
|
|
631
|
+
# Binary.update_hint would be a lie here: it prints a curl into a path the
|
|
632
|
+
# gem controls, and under ws_url the browser is somebody else's process on
|
|
633
|
+
# possibly another host.
|
|
634
|
+
REMOTE_UPDATE_HINT = "the gem does not manage that browser — " \
|
|
635
|
+
"update the Lightpanda serving ws_url yourself."
|
|
636
|
+
private_constant :REMOTE_UPDATE_HINT
|
|
637
|
+
|
|
638
|
+
# The spawn path runs `lightpanda version` before the browser is even up
|
|
639
|
+
# (Process#check_minimum_version). An externally-managed browser has no
|
|
640
|
+
# binary to shell out to, so the same floor is enforced over CDP instead:
|
|
641
|
+
# `LP.version` returns the identical string the CLI prints, and
|
|
642
|
+
# Process.check_version! is the one parser both channels share.
|
|
643
|
+
#
|
|
644
|
+
# Without this, `ws_url:` was the one way into the driver that skipped the
|
|
645
|
+
# floor entirely — an old browser connected happily and then failed later
|
|
646
|
+
# as an unrelated-looking CDP error, with nothing naming the real cause.
|
|
647
|
+
#
|
|
648
|
+
# A browser that cannot answer `LP.version` is refused rather than assumed
|
|
649
|
+
# new enough, matching the spawn path's rule that an unidentifiable binary
|
|
650
|
+
# never passes. Two things land here: a Lightpanda predating the LP domain,
|
|
651
|
+
# and a Chrome someone pointed ws_url at by mistake — the second being easy
|
|
652
|
+
# to do and previously diagnosable only by watching later commands fail.
|
|
653
|
+
#
|
|
654
|
+
# Only #start calls this. #reconnect deliberately does not: it re-dials the
|
|
655
|
+
# same endpoint, whose version cannot have changed, and the crash-recovery
|
|
656
|
+
# path should not spend a round-trip re-asking.
|
|
657
|
+
def check_remote_version
|
|
658
|
+
result = @client.command("LP.version")
|
|
659
|
+
@remote_version, @remote_nightly_build, @remote_release =
|
|
660
|
+
Process.check_version!(result["version"]) { REMOTE_UPDATE_HINT }
|
|
661
|
+
rescue BrowserError, TimeoutError => e
|
|
662
|
+
raise BinaryError,
|
|
663
|
+
"Could not read the Lightpanda version at #{@options.ws_url} " \
|
|
664
|
+
"(LP.version failed: #{e.message}). ws_url: must point at a " \
|
|
665
|
+
"Lightpanda CDP endpoint new enough to answer LP.version; the gem " \
|
|
666
|
+
"cannot verify a browser it does not manage."
|
|
667
|
+
end
|
|
668
|
+
|
|
593
669
|
def restart_process_if_dead
|
|
594
670
|
return unless @process && !@process.alive?
|
|
595
671
|
|
|
@@ -256,9 +256,25 @@ module Capybara
|
|
|
256
256
|
raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
|
|
257
257
|
end
|
|
258
258
|
|
|
259
|
+
# The trailing `wait_for_idle` mirrors #click, and for the same reason.
|
|
260
|
+
# Since upstream #3264 (build >= 8842) `Input.dispatchKeyEvent` builds a
|
|
261
|
+
# *trusted* KeyboardEvent, so Enter on a submit input / <a href> and
|
|
262
|
+
# Space on a button or checkbox synthesize a real activation click —
|
|
263
|
+
# which means a keystroke can now start a navigation. Before #3264 it
|
|
264
|
+
# could not, which is why this settle step was historically absent; its
|
|
265
|
+
# absence then showed up as `current_url` being read against the
|
|
266
|
+
# outgoing document (test/features/keyboard_activation_test.rb).
|
|
267
|
+
#
|
|
268
|
+
# Caveat, shared with #click: `wait_for_idle` only sniffs for ~50 ms
|
|
269
|
+
# (Browser::SNIFF_WINDOW) and watches Runtime.executionContextsCleared,
|
|
270
|
+
# which Lightpanda emits when the *response* lands, not when the request
|
|
271
|
+
# starts. A navigation whose server round-trip exceeds that window is
|
|
272
|
+
# therefore still not awaited here — use Capybara's waiting matchers
|
|
273
|
+
# (`have_current_path`) for those, as you would after a click.
|
|
259
274
|
def send_keys(*)
|
|
260
275
|
call("function() { this.focus() }")
|
|
261
276
|
driver.browser.keyboard.type(*)
|
|
277
|
+
driver.browser.wait_for_idle
|
|
262
278
|
end
|
|
263
279
|
|
|
264
280
|
def tag_name
|
|
@@ -201,7 +201,37 @@ module Capybara
|
|
|
201
201
|
# this bump — and drag events carry integer clientX/Y (the shared spec
|
|
202
202
|
# asserting that runs un-skipped). Build 8796 = the #3259 merge
|
|
203
203
|
# (341a01570), which is also the 2026-08-25 nightly cut.
|
|
204
|
-
|
|
204
|
+
#
|
|
205
|
+
# 2026-08-26 bump 8796 -> 8875, four fixes the gem now asserts on:
|
|
206
|
+
# #3264 (8842) CDP Input.dispatchKeyEvent builds a *trusted*
|
|
207
|
+
# KeyboardEvent, so frame/user_input.zig fires `keypress` on
|
|
208
|
+
# printable/Enter keydowns and synthesizes a trusted PointerEvent
|
|
209
|
+
# click for Enter on <button>/<a href>/input[submit|button|reset|
|
|
210
|
+
# image] and Space-keyup on those plus checkbox/radio. That click is
|
|
211
|
+
# a real activation event (PointerEvent.Proto = MouseEvent), so
|
|
212
|
+
# Node#send_keys(:enter) submits/navigates and send_keys(:space)
|
|
213
|
+
# toggles a checkbox — both were no-ops below 8842. No gem code
|
|
214
|
+
# drives it; Keyboard never sends CDP `type: "char"`, so nothing
|
|
215
|
+
# double-fires. Pinned by test/features/keyboard_activation_test.rb.
|
|
216
|
+
# #3269 (8868) adds `dialog:not([open]) { display: none }` to the
|
|
217
|
+
# UA-stylesheet truth checkVisibility() and getComputedStyle().display
|
|
218
|
+
# share, so a closed <dialog> AND its subtree read as non-visible.
|
|
219
|
+
# Below 8868 Capybara matched text and controls inside a never-opened
|
|
220
|
+
# dialog. Pinned by test/features/upstream_bugs_test.rb (Bug #4).
|
|
221
|
+
# #3270 (8857) inline-style keyword matching in checkVisibility /
|
|
222
|
+
# getComputedStyle is case-insensitive for display / visibility /
|
|
223
|
+
# opacity / pointer-events, so `style="display: NONE"` hides.
|
|
224
|
+
# Feeds _lightpanda.isVisible. Pinned by visibility_keywords_test.rb.
|
|
225
|
+
# #3256 (8875) the CDP WS handshake accepts the exact lowercase
|
|
226
|
+
# `Host: localhost:<port>` form (bare `localhost`, `LOCALHOST:<port>`
|
|
227
|
+
# and `localhost.evil.com:<port>` still 403). Below 8875 only an IP
|
|
228
|
+
# literal passed, so a user-supplied `ws_url:` had to say 127.0.0.1.
|
|
229
|
+
# Pinned by test/features/ws_url_host_test.rb.
|
|
230
|
+
# Build 8875 = the #3256 merge (f2169836e). NOTE: this floor leads the
|
|
231
|
+
# nightly channel — the 2026-08-26 nightly is 8855 — so it lands with the
|
|
232
|
+
# next nightly cut. #3267 (8880, Authorization stripped on cross-origin
|
|
233
|
+
# redirects) arrives with the same floor but drives nothing gem-side.
|
|
234
|
+
MINIMUM_NIGHTLY_BUILD = Gem::Version.new("8875")
|
|
205
235
|
|
|
206
236
|
# Second, equivalent floor for the *release* channel.
|
|
207
237
|
#
|
|
@@ -210,15 +240,15 @@ module Capybara
|
|
|
210
240
|
# bare "0.3.6" carrying no commit counter at all, and MINIMUM_NIGHTLY_BUILD
|
|
211
241
|
# has nothing to compare against. Releases are cut from the same trunk, so
|
|
212
242
|
# a release is acceptable exactly when its own commit count clears the
|
|
213
|
-
# nightly floor. As of 2026-08-
|
|
214
|
-
# floor — 0.3.7 (build 8671) predates the
|
|
215
|
-
#
|
|
216
|
-
#
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
221
|
-
#
|
|
243
|
+
# nightly floor. As of 2026-08-26 NO published release clears the 8875
|
|
244
|
+
# floor — 0.3.7 (build 8671) predates every fix the floor exists for — so
|
|
245
|
+
# this pin names the NEXT release. 0.3.8 is not tagged yet; any future tag
|
|
246
|
+
# is cut from a trunk already past build 8886, so it clears the floor by
|
|
247
|
+
# construction. Until upstream tags it, the release channel is
|
|
248
|
+
# deliberately unusable (nightly-only), reported with the standard
|
|
249
|
+
# too-old error. When 0.3.8 ships: verify
|
|
250
|
+
# `git rev-list --count 0.3.8` >= 8875 and update the lockstep table in
|
|
251
|
+
# process_test.rb.
|
|
222
252
|
#
|
|
223
253
|
# INVARIANT: every MINIMUM_NIGHTLY_BUILD bump must also move this to the
|
|
224
254
|
# first release containing that build (`git rev-list --count <tag>` in the
|
|
@@ -226,6 +256,56 @@ module Capybara
|
|
|
226
256
|
# silently accept a binary the nightly channel rejects.
|
|
227
257
|
MINIMUM_RELEASE = Gem::Version.new("0.3.8")
|
|
228
258
|
|
|
259
|
+
class << self
|
|
260
|
+
# `lightpanda version` prints one of two shapes, and the gem supports
|
|
261
|
+
# both so a suite can either track nightly or pin a reproducible
|
|
262
|
+
# release:
|
|
263
|
+
#
|
|
264
|
+
# "1.0.0-nightly.8285+de85a51d" rolling nightly (also "dev.NNNN" for
|
|
265
|
+
# a locally compiled tree — same
|
|
266
|
+
# `git rev-list --count HEAD` counter,
|
|
267
|
+
# different label). Checked against
|
|
268
|
+
# MINIMUM_NIGHTLY_BUILD.
|
|
269
|
+
# "0.3.5" a tagged release: bare semver, no
|
|
270
|
+
# build metadata. Checked against
|
|
271
|
+
# MINIMUM_RELEASE.
|
|
272
|
+
#
|
|
273
|
+
# The release form is matched anchored — a bare semver and nothing else
|
|
274
|
+
# — so a stray "1.2.3" inside some other string can never be mistaken
|
|
275
|
+
# for a release. Anything that matches neither shape stays a hard
|
|
276
|
+
# failure: a version we cannot identify is never assumed to be new
|
|
277
|
+
# enough.
|
|
278
|
+
#
|
|
279
|
+
# Lives on the class, not the instance, because there are two ways to
|
|
280
|
+
# learn a version and only one of them involves a Process: the spawn
|
|
281
|
+
# path shells out to the binary, while an externally-managed browser
|
|
282
|
+
# (`ws_url:`) is asked over CDP. `LP.version` returns the identical
|
|
283
|
+
# string the CLI prints, so both channels share this one parser rather
|
|
284
|
+
# than drifting apart — the drift being how `ws_url:` shipped with no
|
|
285
|
+
# floor check at all.
|
|
286
|
+
#
|
|
287
|
+
# Returns [version, nightly_build, release]; exactly one of the latter
|
|
288
|
+
# two is non-nil. The update hint is yielded rather than passed so the
|
|
289
|
+
# caller's hint-building only runs on the failure path.
|
|
290
|
+
def check_version!(raw)
|
|
291
|
+
version = raw.to_s.strip
|
|
292
|
+
|
|
293
|
+
if (build = version[/(?:nightly|dev)\.(\d+)/, 1])
|
|
294
|
+
nightly_build = Gem::Version.new(build)
|
|
295
|
+
return [version, nightly_build, nil] if nightly_build >= MINIMUM_NIGHTLY_BUILD
|
|
296
|
+
elsif (tag = version[/\A\d+\.\d+\.\d+\z/])
|
|
297
|
+
release = Gem::Version.new(tag)
|
|
298
|
+
return [version, nil, release] if release >= MINIMUM_RELEASE
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
raise BinaryError,
|
|
302
|
+
"Lightpanda #{version} is too old. " \
|
|
303
|
+
"This gem requires nightly build >= #{MINIMUM_NIGHTLY_BUILD} " \
|
|
304
|
+
"or release >= #{MINIMUM_RELEASE}. " \
|
|
305
|
+
"Update: #{yield}"
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
229
309
|
attr_reader :pid, :ws_url, :version, :nightly_build, :release
|
|
230
310
|
|
|
231
311
|
def initialize(options)
|
|
@@ -273,38 +353,12 @@ module Capybara
|
|
|
273
353
|
|
|
274
354
|
private
|
|
275
355
|
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
# "1.0.0-nightly.8285+de85a51d" rolling nightly (also "dev.NNNN" for a
|
|
280
|
-
# locally compiled tree — same
|
|
281
|
-
# `git rev-list --count HEAD` counter,
|
|
282
|
-
# different label). Checked against
|
|
283
|
-
# MINIMUM_NIGHTLY_BUILD.
|
|
284
|
-
# "0.3.5" a tagged release: bare semver, no build
|
|
285
|
-
# metadata. Checked against MINIMUM_RELEASE.
|
|
286
|
-
#
|
|
287
|
-
# The release form is matched anchored — a bare semver and nothing else —
|
|
288
|
-
# so a stray "1.2.3" inside some other string can never be mistaken for a
|
|
289
|
-
# release. Anything that matches neither shape stays a hard failure: a
|
|
290
|
-
# binary we cannot identify is never assumed to be new enough.
|
|
356
|
+
# Shells the floor check out to the binary. The ws_url path can't — see
|
|
357
|
+
# Browser#check_remote_version, which feeds the same parser from CDP.
|
|
291
358
|
def check_minimum_version(binary_path)
|
|
292
359
|
stdout, = Open3.capture3(binary_path, "version")
|
|
293
|
-
@version =
|
|
294
|
-
|
|
295
|
-
if (build = @version[/(?:nightly|dev)\.(\d+)/, 1])
|
|
296
|
-
@nightly_build = Gem::Version.new(build)
|
|
297
|
-
return if @nightly_build >= MINIMUM_NIGHTLY_BUILD
|
|
298
|
-
elsif (tag = @version[/\A\d+\.\d+\.\d+\z/])
|
|
299
|
-
@release = Gem::Version.new(tag)
|
|
300
|
-
return if @release >= MINIMUM_RELEASE
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
raise BinaryError,
|
|
304
|
-
"Lightpanda #{@version} is too old. " \
|
|
305
|
-
"This gem requires nightly build >= #{MINIMUM_NIGHTLY_BUILD} " \
|
|
306
|
-
"or release >= #{MINIMUM_RELEASE}. " \
|
|
307
|
-
"Update: #{Binary.update_hint(binary_path)}"
|
|
360
|
+
@version, @nightly_build, @release =
|
|
361
|
+
self.class.check_version!(stdout) { Binary.update_hint(binary_path) }
|
|
308
362
|
rescue Errno::ENOENT
|
|
309
363
|
# Binary not runnable — let attempt_start handle it
|
|
310
364
|
end
|
|
@@ -371,7 +425,7 @@ module Capybara
|
|
|
371
425
|
"info",
|
|
372
426
|
# External stylesheet fetch (PR #2487, build >= 6353 — enforced by the
|
|
373
427
|
# floor). Always on so linked CSS contributes to checkVisibility /
|
|
374
|
-
# getComputedStyle; see .claude/rules/lightpanda-io.md limitation #
|
|
428
|
+
# getComputedStyle; see .claude/rules/lightpanda-io.md limitation #5.
|
|
375
429
|
"--enable-external-stylesheets",
|
|
376
430
|
# Raise the inbound CDP WebSocket message cap from Lightpanda's 1 MiB
|
|
377
431
|
# default (Config.zig `cdp_max_message_size`) to 100 MiB, matching
|