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.
@@ -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 = first published nightly carrying the #2708 merge — now
101
- # the binding floor.
102
- MINIMUM_NIGHTLY_BUILD = Gem::Version.new("6736")
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
- return if @nightly_build && @nightly_build >= MINIMUM_NIGHTLY_BUILD
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Lightpanda
5
- VERSION = "0.9.0"
5
+ VERSION = "0.10.0"
6
6
  end
7
7
  end
@@ -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.9.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.10
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: []