capybara-lightpanda 0.9.0 → 0.10.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 +37 -0
- data/README.md +14 -1
- data/lib/capybara/lightpanda/auto_scripts.rb +3 -1
- data/lib/capybara/lightpanda/binary.rb +70 -8
- data/lib/capybara/lightpanda/browser/console.rb +89 -7
- data/lib/capybara/lightpanda/browser/modals.rb +54 -3
- data/lib/capybara/lightpanda/browser/navigation.rb +1 -0
- data/lib/capybara/lightpanda/browser/selenium_compat.rb +124 -0
- data/lib/capybara/lightpanda/browser.rb +104 -4
- data/lib/capybara/lightpanda/downloads.rb +176 -0
- data/lib/capybara/lightpanda/driver.rb +109 -2
- data/lib/capybara/lightpanda/errors.rb +5 -0
- data/lib/capybara/lightpanda/javascripts/errors.js +74 -0
- data/lib/capybara/lightpanda/javascripts/predicates.js +19 -55
- data/lib/capybara/lightpanda/network.rb +36 -10
- data/lib/capybara/lightpanda/node.rb +177 -65
- data/lib/capybara/lightpanda/options.rb +44 -7
- data/lib/capybara/lightpanda/process.rb +151 -11
- data/lib/capybara/lightpanda/version.rb +1 -1
- data/lib/capybara-lightpanda.rb +1 -0
- metadata +5 -2
|
@@ -97,11 +97,120 @@ module Capybara
|
|
|
97
97
|
# merged 2026-06-12) made the index.js readystatechange re-dispatch
|
|
98
98
|
# shim redundant, so it was removed — on builds without #2708 Turbo's
|
|
99
99
|
# PageObserver never reaches pageLoaded() and turbo:load never fires.
|
|
100
|
-
# Build 6736
|
|
101
|
-
#
|
|
102
|
-
|
|
100
|
+
# Build 6736 carried the #2708 merge.
|
|
101
|
+
# PR #2722 (Browser.setDownloadBehavior streams Content-Disposition:
|
|
102
|
+
# attachment responses to disk under downloadPath + emits
|
|
103
|
+
# Browser.downloadWillBegin / downloadProgress, merged 2026-06-19) backs
|
|
104
|
+
# the Downloads tracker (downloads.rb) wired in Browser#create_page. On
|
|
105
|
+
# builds < 7545 setDownloadBehavior is a bare success no-op — every
|
|
106
|
+
# download would silently never be written to disk — so the floor MUST
|
|
107
|
+
# include it. Note Lightpanda triggers downloads on Content-Disposition:
|
|
108
|
+
# attachment, NOT by MIME type (a text/csv response without that header
|
|
109
|
+
# is rendered as a normal navigation), which is why Capybara's
|
|
110
|
+
# MIME-triggered :download shared spec stays in capybara_skip.
|
|
111
|
+
# Build 7545 = the #2722 merge (a808386c).
|
|
112
|
+
# PR #2785 (build 7550, improve innerText + outerText) AND PR #2795
|
|
113
|
+
# (build 7571, innerText uses StyleManager visibility) make native
|
|
114
|
+
# Element.innerText implement the HTML rendered-text collection steps —
|
|
115
|
+
# block line breaks + skipping display:none descendants. The
|
|
116
|
+
# _lightpanda.visibleText predicate (predicates.js) now delegates the
|
|
117
|
+
# rendered-text collection to native innerText (keeping only a visibility
|
|
118
|
+
# gate + ShadowRoot fragment walk), so the floor MUST include #2795; on
|
|
119
|
+
# builds < 7571 innerText returns textContent verbatim (no block breaks,
|
|
120
|
+
# leaks display:none text).
|
|
121
|
+
# Build 7571 = the #2795 merge (48ed689c).
|
|
122
|
+
# PR #2719 (css: apply @layer block rules to the cascade, merged
|
|
123
|
+
# 2026-07-17) makes StyleManager rank `@layer` rules correctly. Before it,
|
|
124
|
+
# a rule inside an `@layer` block was dropped from the cascade entirely,
|
|
125
|
+
# so an element hidden by an `@layer` `display: none` — the default shape
|
|
126
|
+
# of Tailwind v4's generated CSS — reported `checkVisibility() === true`.
|
|
127
|
+
# `_lightpanda.isVisible` (predicates.js) terminates in checkVisibility(),
|
|
128
|
+
# so on builds < 8160 Capybara believes such elements are visible and acts
|
|
129
|
+
# on them: clicks land on hidden nodes and `assert_no_selector` passes for
|
|
130
|
+
# visible ones. That is a wrong-answer bug rather than an exception, which
|
|
131
|
+
# is exactly the kind users can't diagnose, so the floor MUST include it.
|
|
132
|
+
# Build 8160 = the #2719 merge (b19b5725).
|
|
133
|
+
# PR #2983 (css: respect @layer priority, merged 2026-07-24) is the second
|
|
134
|
+
# half of that same fix and the reason 8160 is NOT sufficient. #2719 only
|
|
135
|
+
# made rules inside an `@layer` block *participate* in the cascade; it
|
|
136
|
+
# ranked them by specificity + document order alone. #2983 widened
|
|
137
|
+
# VisibilityRule.priority to carry a 12-bit layer rank, so layer order
|
|
138
|
+
# finally beats specificity as the spec requires. On builds 8160–8280 a
|
|
139
|
+
# page whose layers disagree about `display` resolves to the wrong rule —
|
|
140
|
+
# Tailwind v4's `@layer utilities` losing to `@layer base` is the common
|
|
141
|
+
# shape — and `_lightpanda.isVisible` (predicates.js) terminates in
|
|
142
|
+
# checkVisibility(), so Capybara again believes hidden elements are
|
|
143
|
+
# visible. Same undiagnosable wrong-answer class as #2719 itself, so the
|
|
144
|
+
# floor MUST include it.
|
|
145
|
+
# Build 8281 = the #2983 merge (76e2e7e6).
|
|
146
|
+
# PR #3015 (cdp: sanitize non-UTF-8 values, merged 2026-07-24) fixes
|
|
147
|
+
# issue #2992: a legacy-encoded Content-Disposition filename made
|
|
148
|
+
# Browser.downloadWillBegin emit `suggestedFilename` as a JSON array of
|
|
149
|
+
# bytes instead of a string. Downloads#build_will_handler does
|
|
150
|
+
# File.basename(params["suggestedFilename"].to_s), so below this build it
|
|
151
|
+
# records a "[130, 160, ...]" basename and Driver#downloads hands back a
|
|
152
|
+
# path that does not exist.
|
|
153
|
+
# Build 8283 = the #3015 merge (8fbcc7e5).
|
|
154
|
+
# PR #3054 (forms: close the ancestor dialog on method=dialog submission,
|
|
155
|
+
# merged 2026-07-25) makes `<form method="dialog">` close its nearest
|
|
156
|
+
# ancestor <dialog>, set returnValue from the submitter's IDL value, fire
|
|
157
|
+
# close, and perform no navigation. Below it the submission falls through
|
|
158
|
+
# to a GET navigation and the dialog stays open forever — which is the
|
|
159
|
+
# <dialog>+Turbo-confirm idiom Spree 5's admin uses for every destroy
|
|
160
|
+
# confirmation. There is no gem-side workaround to guard this: the only
|
|
161
|
+
# defense is the floor.
|
|
162
|
+
# Build 8311 = the #3054 merge (c917cadc).
|
|
163
|
+
# PR #3058 (forms: include optgroup children in a select's list of
|
|
164
|
+
# options, merged 2026-07-26) makes `<option>`s inside an `<optgroup>`
|
|
165
|
+
# reachable through HTMLSelectElement (options / value / selectedIndex /
|
|
166
|
+
# submission). Below it Capybara's `select` cannot find a grouped option
|
|
167
|
+
# at all and there is no gem-side workaround — a plain ElementNotFound on
|
|
168
|
+
# any grouped <select>. Build 8328 = the #3058 merge (c7182354).
|
|
169
|
+
# PR #3080 (webapi: increase max timer count, merged 2026-07-29) lifts the
|
|
170
|
+
# one-shot setTimeout cap 512 → 2048 (Airbnb-class pages schedule 600+);
|
|
171
|
+
# below it timers past the cap are silently dropped and page JS stalls in
|
|
172
|
+
# ways that read as Capybara timeouts. Build 8412 = the #3080 merge.
|
|
173
|
+
# PR #3081 + #3082/#3085 (script load/error events; dynamic scripts whose
|
|
174
|
+
# `src` is set via setAttribute, merged 2026-07-30) — below them an
|
|
175
|
+
# inline script fired no load event, a throwing script still fired
|
|
176
|
+
# `load`, and a setAttribute('src') script never loaded, which breaks
|
|
177
|
+
# loader idioms (importmap shims, lazy widget bootstraps) that gate the
|
|
178
|
+
# UI Capybara waits for. Builds 8414/8419.
|
|
179
|
+
# PR #3087 (fix: potential UAF when an option's value is programmatically
|
|
180
|
+
# set, merged 2026-07-30) — a use-after-free in exactly the path
|
|
181
|
+
# `Node#select_option`/`Node#set` on a <select> drives, i.e. a browser
|
|
182
|
+
# crash surfacing as DeadBrowserError mid-spec.
|
|
183
|
+
# Build 8448 = the #3087 merge (6d824c88) — now the binding floor.
|
|
184
|
+
# Subsumed by 8448, recorded so they are not re-derived: build 8298 made
|
|
185
|
+
# Network.enable idempotent (the gem never hit it — the Notification is
|
|
186
|
+
# per-BrowserContext and Network#enable's @enabled guard means one enable
|
|
187
|
+
# per context), and build 8305 (#3048) derives scrollWidth/scrollHeight
|
|
188
|
+
# from element content (the gem keeps scroll as a no-op either way).
|
|
189
|
+
# PR #2664 (Emulation.setDeviceMetricsOverride, build 7556) is subsumed:
|
|
190
|
+
# Browser#set_viewport calls it on every create_page to honor the
|
|
191
|
+
# `window_size` option. Note the override only reached
|
|
192
|
+
# Page.getLayoutMetrics later (~build 8300); the gem does not depend on
|
|
193
|
+
# that half, so it is NOT part of the floor.
|
|
194
|
+
MINIMUM_NIGHTLY_BUILD = Gem::Version.new("8448")
|
|
195
|
+
|
|
196
|
+
# Second, equivalent floor for the *release* channel.
|
|
197
|
+
#
|
|
198
|
+
# Tagged releases are built with `-Dversion=<tag>`, which resolves to a
|
|
199
|
+
# plain semver with no pre-release tag — so `lightpanda version` prints a
|
|
200
|
+
# bare "0.3.6" carrying no commit counter at all, and MINIMUM_NIGHTLY_BUILD
|
|
201
|
+
# has nothing to compare against. Releases are cut from the same trunk, so
|
|
202
|
+
# a release is acceptable exactly when its own commit count clears the
|
|
203
|
+
# nightly floor: 0.3.7 is build 8671, well past the #3087 merge (8448)
|
|
204
|
+
# this floor exists for. (0.3.6 is only 8318 — it predates the optgroup
|
|
205
|
+
# fix and everything after it, which is why this pin had to move.)
|
|
206
|
+
#
|
|
207
|
+
# INVARIANT: every MINIMUM_NIGHTLY_BUILD bump must also move this to the
|
|
208
|
+
# first release containing that build (`git rev-list --count <tag>` in the
|
|
209
|
+
# browser repo tells you). Leaving it behind would let the release channel
|
|
210
|
+
# silently accept a binary the nightly channel rejects.
|
|
211
|
+
MINIMUM_RELEASE = Gem::Version.new("0.3.7")
|
|
103
212
|
|
|
104
|
-
attr_reader :pid, :ws_url, :version, :nightly_build
|
|
213
|
+
attr_reader :pid, :ws_url, :version, :nightly_build, :release
|
|
105
214
|
|
|
106
215
|
def initialize(options)
|
|
107
216
|
@options = options
|
|
@@ -109,6 +218,7 @@ module Capybara
|
|
|
109
218
|
@ws_url = nil
|
|
110
219
|
@version = nil
|
|
111
220
|
@nightly_build = nil
|
|
221
|
+
@release = nil
|
|
112
222
|
@stdout_r = nil
|
|
113
223
|
@stdout_w = nil
|
|
114
224
|
@stderr_r = nil
|
|
@@ -147,20 +257,37 @@ module Capybara
|
|
|
147
257
|
|
|
148
258
|
private
|
|
149
259
|
|
|
260
|
+
# `lightpanda version` prints one of two shapes, and the gem supports both
|
|
261
|
+
# so a suite can either track nightly or pin a reproducible release:
|
|
262
|
+
#
|
|
263
|
+
# "1.0.0-nightly.8285+de85a51d" rolling nightly (also "dev.NNNN" for a
|
|
264
|
+
# locally compiled tree — same
|
|
265
|
+
# `git rev-list --count HEAD` counter,
|
|
266
|
+
# different label). Checked against
|
|
267
|
+
# MINIMUM_NIGHTLY_BUILD.
|
|
268
|
+
# "0.3.5" a tagged release: bare semver, no build
|
|
269
|
+
# metadata. Checked against MINIMUM_RELEASE.
|
|
270
|
+
#
|
|
271
|
+
# The release form is matched anchored — a bare semver and nothing else —
|
|
272
|
+
# so a stray "1.2.3" inside some other string can never be mistaken for a
|
|
273
|
+
# release. Anything that matches neither shape stays a hard failure: a
|
|
274
|
+
# binary we cannot identify is never assumed to be new enough.
|
|
150
275
|
def check_minimum_version(binary_path)
|
|
151
276
|
stdout, = Open3.capture3(binary_path, "version")
|
|
152
277
|
@version = stdout.strip
|
|
153
|
-
# Accept either `nightly.NNNN` (publicly distributed builds) or
|
|
154
|
-
# `dev.NNNN` (locally compiled trees) — the build number is the same
|
|
155
|
-
# `git rev-list --count HEAD` counter, just labelled differently.
|
|
156
|
-
build = @version[/(?:nightly|dev)\.(\d+)/, 1]
|
|
157
|
-
@nightly_build = Gem::Version.new(build) if build
|
|
158
278
|
|
|
159
|
-
|
|
279
|
+
if (build = @version[/(?:nightly|dev)\.(\d+)/, 1])
|
|
280
|
+
@nightly_build = Gem::Version.new(build)
|
|
281
|
+
return if @nightly_build >= MINIMUM_NIGHTLY_BUILD
|
|
282
|
+
elsif (tag = @version[/\A\d+\.\d+\.\d+\z/])
|
|
283
|
+
@release = Gem::Version.new(tag)
|
|
284
|
+
return if @release >= MINIMUM_RELEASE
|
|
285
|
+
end
|
|
160
286
|
|
|
161
287
|
raise BinaryError,
|
|
162
288
|
"Lightpanda #{@version} is too old. " \
|
|
163
|
-
"This gem requires build >= #{MINIMUM_NIGHTLY_BUILD}
|
|
289
|
+
"This gem requires nightly build >= #{MINIMUM_NIGHTLY_BUILD} " \
|
|
290
|
+
"or release >= #{MINIMUM_RELEASE}. " \
|
|
164
291
|
"Update: #{Binary.update_hint(binary_path)}"
|
|
165
292
|
rescue Errno::ENOENT
|
|
166
293
|
# Binary not runnable — let attempt_start handle it
|
|
@@ -230,6 +357,19 @@ module Capybara
|
|
|
230
357
|
# floor). Always on so linked CSS contributes to checkVisibility /
|
|
231
358
|
# getComputedStyle; see .claude/rules/lightpanda-io.md limitation #6.
|
|
232
359
|
"--enable-external-stylesheets",
|
|
360
|
+
# Raise the inbound CDP WebSocket message cap from Lightpanda's 1 MiB
|
|
361
|
+
# default (Config.zig `cdp_max_message_size`) to 100 MiB, matching
|
|
362
|
+
# Chrome's inbound DevTools buffer. An inbound message over the cap is
|
|
363
|
+
# dropped with WS close 1009 and NO JSON-RPC error — the whole CDP
|
|
364
|
+
# connection dies (wishlist A44). The 1 MiB default already covers
|
|
365
|
+
# axe-core (~553 KB), but larger injected bundles (jQuery + plugins,
|
|
366
|
+
# instrumentation) via execute_script exceed it (Node#drop used to as
|
|
367
|
+
# well, before files moved to DOM.setFileInputFiles). The reader
|
|
368
|
+
# buffer grows lazily (16 KB at init),
|
|
369
|
+
# so a high cap costs nothing until a big message actually arrives.
|
|
370
|
+
# Flag added in PR #2760 (build 7441) — below the floor, so guaranteed.
|
|
371
|
+
"--cdp-max-message-size",
|
|
372
|
+
(100 * 1024 * 1024).to_s,
|
|
233
373
|
]
|
|
234
374
|
extra = ENV.fetch("LIGHTPANDA_EXTRA_ARGS", "").split
|
|
235
375
|
base + extra
|
data/lib/capybara-lightpanda.rb
CHANGED
|
@@ -14,6 +14,7 @@ require_relative "capybara/lightpanda/utils/wait"
|
|
|
14
14
|
require_relative "capybara/lightpanda/client"
|
|
15
15
|
require_relative "capybara/lightpanda/headers"
|
|
16
16
|
require_relative "capybara/lightpanda/network"
|
|
17
|
+
require_relative "capybara/lightpanda/downloads"
|
|
17
18
|
require_relative "capybara/lightpanda/cookies"
|
|
18
19
|
require_relative "capybara/lightpanda/keyboard"
|
|
19
20
|
require_relative "capybara/lightpanda/browser"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capybara-lightpanda
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Navid Emad
|
|
@@ -79,16 +79,19 @@ files:
|
|
|
79
79
|
- lib/capybara/lightpanda/browser/modals.rb
|
|
80
80
|
- lib/capybara/lightpanda/browser/navigation.rb
|
|
81
81
|
- lib/capybara/lightpanda/browser/runtime.rb
|
|
82
|
+
- lib/capybara/lightpanda/browser/selenium_compat.rb
|
|
82
83
|
- lib/capybara/lightpanda/client.rb
|
|
83
84
|
- lib/capybara/lightpanda/client/subscriber.rb
|
|
84
85
|
- lib/capybara/lightpanda/client/web_socket.rb
|
|
85
86
|
- lib/capybara/lightpanda/cookies.rb
|
|
87
|
+
- lib/capybara/lightpanda/downloads.rb
|
|
86
88
|
- lib/capybara/lightpanda/driver.rb
|
|
87
89
|
- lib/capybara/lightpanda/element_extension.rb
|
|
88
90
|
- lib/capybara/lightpanda/errors.rb
|
|
89
91
|
- lib/capybara/lightpanda/headers.rb
|
|
90
92
|
- lib/capybara/lightpanda/javascripts/attach.js
|
|
91
93
|
- lib/capybara/lightpanda/javascripts/banner.js
|
|
94
|
+
- lib/capybara/lightpanda/javascripts/errors.js
|
|
92
95
|
- lib/capybara/lightpanda/javascripts/predicates.js
|
|
93
96
|
- lib/capybara/lightpanda/javascripts/turbo.js
|
|
94
97
|
- lib/capybara/lightpanda/keyboard.rb
|
|
@@ -126,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
126
129
|
- !ruby/object:Gem::Version
|
|
127
130
|
version: '0'
|
|
128
131
|
requirements: []
|
|
129
|
-
rubygems_version: 4.0.
|
|
132
|
+
rubygems_version: 4.0.16
|
|
130
133
|
specification_version: 4
|
|
131
134
|
summary: Capybara driver for the Lightpanda headless browser
|
|
132
135
|
test_files: []
|