capybara-lightpanda 0.14.2 → 0.15.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 +26 -0
- data/README.md +4 -4
- data/lib/capybara/lightpanda/auto_scripts.rb +3 -3
- data/lib/capybara/lightpanda/browser/console.rb +73 -51
- data/lib/capybara/lightpanda/browser/finder.rb +83 -32
- data/lib/capybara/lightpanda/browser/modals.rb +8 -7
- data/lib/capybara/lightpanda/browser/navigation.rb +10 -1
- data/lib/capybara/lightpanda/browser/runtime.rb +3 -3
- data/lib/capybara/lightpanda/browser.rb +13 -4
- data/lib/capybara/lightpanda/client/web_socket.rb +21 -2
- data/lib/capybara/lightpanda/client.rb +4 -1
- data/lib/capybara/lightpanda/cookies.rb +1 -1
- data/lib/capybara/lightpanda/driver.rb +27 -28
- data/lib/capybara/lightpanda/errors.rb +3 -3
- data/lib/capybara/lightpanda/javascripts/attach.js +2 -1
- data/lib/capybara/lightpanda/javascripts/predicates.js +73 -8
- data/lib/capybara/lightpanda/keyboard.rb +15 -5
- data/lib/capybara/lightpanda/network.rb +1 -1
- data/lib/capybara/lightpanda/node.rb +92 -68
- data/lib/capybara/lightpanda/options.rb +9 -7
- data/lib/capybara/lightpanda/process.rb +71 -6
- data/lib/capybara/lightpanda/version.rb +1 -1
- metadata +1 -2
- data/lib/capybara/lightpanda/javascripts/errors.js +0 -74
|
@@ -20,10 +20,11 @@ module Capybara
|
|
|
20
20
|
# `Capybara::Lightpanda.configure { |c| c.port = 9222 }` when external
|
|
21
21
|
# tooling needs a known address.
|
|
22
22
|
DEFAULT_PORT = 0
|
|
23
|
-
# Mirrors Lightpanda's own `Viewport.default` (1920x1080)
|
|
24
|
-
#
|
|
25
|
-
# a 1024x768 default would silently shrink the viewport
|
|
26
|
-
# suite and flip `@media` branches under them.
|
|
23
|
+
# Mirrors Lightpanda's own `Viewport.default` (1920x1080), not the
|
|
24
|
+
# 1024x768 some drivers default to. window_size is applied for real now
|
|
25
|
+
# (see below), so a 1024x768 default would silently shrink the viewport
|
|
26
|
+
# of every existing suite and flip `@media` branches under them.
|
|
27
|
+
# Matching the browser's
|
|
27
28
|
# native default keeps `window_size` truthful AND leaves default
|
|
28
29
|
# behavior byte-identical to before it was wired up.
|
|
29
30
|
DEFAULT_WINDOW_SIZE = [1920, 1080].freeze
|
|
@@ -36,16 +37,17 @@ module Capybara
|
|
|
36
37
|
# no rendering engine, so getBoundingClientRect stays synthetic and
|
|
37
38
|
# nothing reflows. Sizing down will not make an off-viewport element
|
|
38
39
|
# report as obscured.
|
|
39
|
-
# headless is accepted
|
|
40
|
+
# headless is accepted so suites migrating from other drivers keep
|
|
41
|
+
# working, but is inert —
|
|
40
42
|
# headless is the only mode Lightpanda runs in.
|
|
41
|
-
# save_path: directory for downloaded files
|
|
43
|
+
# save_path: directory for downloaded files. nil falls
|
|
42
44
|
# back to Capybara.save_path at create_page time; downloads stay off when
|
|
43
45
|
# both are nil (Browser#create_page only opts in when a path exists).
|
|
44
46
|
# raise_on_unhandled_modal: a JS dialog that opens with no
|
|
45
47
|
# accept_modal/dismiss_modal pre-arm in flight is resolved by Lightpanda's
|
|
46
48
|
# silent default (confirm → cancel, prompt → null, alert → dismissed), so
|
|
47
49
|
# a runaway confirm cancels the action and the spec can still pass. false
|
|
48
|
-
# (default
|
|
50
|
+
# (default) warns on stderr; true raises
|
|
49
51
|
# UnhandledModalError from the action that opened it.
|
|
50
52
|
# load_images: adds `image` to the `--load-resources` value so `<img>`
|
|
51
53
|
# elements are fetched over the network (upstream #3230, build >= 8834,
|
|
@@ -264,7 +264,48 @@ module Capybara
|
|
|
264
264
|
# end in Network.loadingFailed, which Network consumes); #3529 (9877)
|
|
265
265
|
# SameSite=None without Secure refused (Cookies#set reports it);
|
|
266
266
|
# #3702 (9968) click no longer focuses (CLICK_JS focuses itself).
|
|
267
|
-
|
|
267
|
+
#
|
|
268
|
+
# 2026-10-16 bump 9994 -> 10641 (= release 1.0.1, the new lockstep cap).
|
|
269
|
+
# Retired or ungated by it:
|
|
270
|
+
# #3958 (10613, ours) Window and Document carry the GlobalEventHandlers
|
|
271
|
+
# `on*` attributes, so javascripts/event_handlers.js is gone — jQuery
|
|
272
|
+
# 1.x sees `'onchange' in window` natively.
|
|
273
|
+
# #3940 (10536) a link to a non-fetchable scheme (mailto:, tel:) is a
|
|
274
|
+
# no-op that keeps the document, so CLICK_JS no longer cancels the
|
|
275
|
+
# click's default from a window listener.
|
|
276
|
+
# #3755 / #3849 (10049 / 10310, ours / upstream) exceptions from timers,
|
|
277
|
+
# rAF, inline handlers and microtasks reach `window.onerror`, so
|
|
278
|
+
# Browser#page_errors sees them; #3757 (10052) hides a closed
|
|
279
|
+
# [popover]; #3759 (10057) routes send_keys into an iframe; #3874
|
|
280
|
+
# (10366) Ctrl+A selects a field — upstream_fixes_test.rb runs them
|
|
281
|
+
# un-gated.
|
|
282
|
+
# #3763 (10239, ours) history traverses pushState entries in place;
|
|
283
|
+
# #3831 (10255) `window` serializes as CyclicObject, not a node;
|
|
284
|
+
# #3843 (10261) a failed navigation commits an error document — their
|
|
285
|
+
# tests drop the nightly_build branches.
|
|
286
|
+
# #3952 (10575) typed text fires `change` when committed by blur/Enter.
|
|
287
|
+
# A `nightly_build` gate is nil on a release binary, so every such test
|
|
288
|
+
# skipped on 1.0.1 although it carried the fix: ungate, don't re-gate.
|
|
289
|
+
#
|
|
290
|
+
# 2026-10-23 bump 10641 -> 10965 (= release 1.0.2, the new lockstep cap;
|
|
291
|
+
# CSS nesting #3962 at 10976 and blank-page timers #4148 at 11012 stay
|
|
292
|
+
# above it). Rides along:
|
|
293
|
+
# #4096 (10904) Runtime.exceptionThrown for unhandled rejections, and
|
|
294
|
+
# `unhandledrejection` no longer fires for a rejection handled in
|
|
295
|
+
# the same tick — errors.js stops reporting those false positives;
|
|
296
|
+
# page_errors_test.rb drops its nightly_build gate. With #4115
|
|
297
|
+
# (10910, an iframe's exception carries its context id) this
|
|
298
|
+
# unblocks the B19 swap off errors.js.
|
|
299
|
+
# #3997 (10685) the scheduler rework: a MessageChannel task loop no
|
|
300
|
+
# longer delays a pending fetch tenfold (#4042), which held
|
|
301
|
+
# wait_for_idle.
|
|
302
|
+
# #3658 (10657) client rects follow the scroll and
|
|
303
|
+
# DOM.scrollIntoViewIfNeeded is real — HTML5_DRAG_JS centers the
|
|
304
|
+
# drop target itself because scrollIntoView ignores `block`.
|
|
305
|
+
MINIMUM_NIGHTLY_BUILD = Gem::Version.new("10965")
|
|
306
|
+
|
|
307
|
+
# Lines of browser output #output keeps (ferrum keeps the same 100).
|
|
308
|
+
OUTPUT_LINES = 100
|
|
268
309
|
|
|
269
310
|
# Second, equivalent floor for the *release* channel.
|
|
270
311
|
#
|
|
@@ -273,16 +314,16 @@ module Capybara
|
|
|
273
314
|
# bare "0.3.6" carrying no commit counter at all, and MINIMUM_NIGHTLY_BUILD
|
|
274
315
|
# has nothing to compare against. Releases are cut from the same trunk, so
|
|
275
316
|
# a release is acceptable exactly when its own commit count clears the
|
|
276
|
-
# nightly floor. 1.0.
|
|
317
|
+
# nightly floor. 1.0.2 (2026-10-23) is build 10965 — exactly the nightly
|
|
277
318
|
# floor, which is no accident: the floor was raised *to* the release so
|
|
278
|
-
# both channels gate the same commit. (0.
|
|
279
|
-
#
|
|
319
|
+
# both channels gate the same commit. (1.0.1 = 10641 predates fixes the
|
|
320
|
+
# floor exists for.)
|
|
280
321
|
#
|
|
281
322
|
# INVARIANT: every MINIMUM_NIGHTLY_BUILD bump must also move this to the
|
|
282
323
|
# first release containing that build (`git rev-list --count <tag>` in the
|
|
283
324
|
# browser repo tells you). Leaving it behind would let the release channel
|
|
284
325
|
# silently accept a binary the nightly channel rejects.
|
|
285
|
-
MINIMUM_RELEASE = Gem::Version.new("1.0.
|
|
326
|
+
MINIMUM_RELEASE = Gem::Version.new("1.0.2")
|
|
286
327
|
|
|
287
328
|
class << self
|
|
288
329
|
# `lightpanda version` prints one of two shapes, and the gem supports
|
|
@@ -348,6 +389,17 @@ module Capybara
|
|
|
348
389
|
@stderr_r = nil
|
|
349
390
|
@stderr_w = nil
|
|
350
391
|
@finalizer_registered = false
|
|
392
|
+
@output = []
|
|
393
|
+
@output_partial = {}
|
|
394
|
+
@output_mutex = Mutex.new
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# The last OUTPUT_LINES lines Lightpanda wrote to stdout/stderr, oldest
|
|
398
|
+
# first, across restarts of this Process. Kept for diagnostics: a crash
|
|
399
|
+
# or panic only ever shows up there, and from build 10783 so do page
|
|
400
|
+
# errors (`page_error` scope, WARN). Mirrors ferrum's Process::Output.
|
|
401
|
+
def output
|
|
402
|
+
@output_mutex.synchronize { @output.dup }
|
|
351
403
|
end
|
|
352
404
|
|
|
353
405
|
def start
|
|
@@ -416,7 +468,7 @@ module Capybara
|
|
|
416
468
|
next unless ready
|
|
417
469
|
|
|
418
470
|
ready[0].each do |io|
|
|
419
|
-
io.read_nonblock(4096)
|
|
471
|
+
record_output(io, io.read_nonblock(4096))
|
|
420
472
|
rescue IO::WaitReadable
|
|
421
473
|
# No data
|
|
422
474
|
rescue EOFError
|
|
@@ -430,6 +482,18 @@ module Capybara
|
|
|
430
482
|
end
|
|
431
483
|
end
|
|
432
484
|
|
|
485
|
+
# Split per pipe so a line cut across two reads is kept whole, and a
|
|
486
|
+
# stdout fragment never glues onto a stderr one.
|
|
487
|
+
def record_output(io, chunk)
|
|
488
|
+
text = chunk.dup.force_encoding(Encoding::UTF_8).scrub
|
|
489
|
+
@output_mutex.synchronize do
|
|
490
|
+
*lines, rest = "#{@output_partial[io]}#{text}".split("\n", -1)
|
|
491
|
+
@output_partial[io] = rest
|
|
492
|
+
@output.concat(lines)
|
|
493
|
+
@output.shift(@output.size - OUTPUT_LINES) if @output.size > OUTPUT_LINES
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
433
497
|
def spawn_process(binary_path)
|
|
434
498
|
args = build_args
|
|
435
499
|
|
|
@@ -499,6 +563,7 @@ module Capybara
|
|
|
499
563
|
ready[0].each do |io|
|
|
500
564
|
chunk = io.read_nonblock(1024)
|
|
501
565
|
output << chunk
|
|
566
|
+
record_output(io, chunk)
|
|
502
567
|
|
|
503
568
|
if (match = output.match(READY_PATTERN))
|
|
504
569
|
@ws_url = "ws://#{match[1]}/"
|
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.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Navid Emad
|
|
@@ -91,7 +91,6 @@ files:
|
|
|
91
91
|
- lib/capybara/lightpanda/headers.rb
|
|
92
92
|
- lib/capybara/lightpanda/javascripts/attach.js
|
|
93
93
|
- lib/capybara/lightpanda/javascripts/banner.js
|
|
94
|
-
- lib/capybara/lightpanda/javascripts/errors.js
|
|
95
94
|
- lib/capybara/lightpanda/javascripts/predicates.js
|
|
96
95
|
- lib/capybara/lightpanda/javascripts/turbo.js
|
|
97
96
|
- lib/capybara/lightpanda/keyboard.rb
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
// --- Uncaught page errors ---
|
|
2
|
-
// Lightpanda implements no `Runtime.exceptionThrown` (upstream gap, wishlist
|
|
3
|
-
// B19), so an uncaught exception in page JS reaches no CDP client at all. It is
|
|
4
|
-
// invisible to console_logs, which only ever sees explicit console.* calls, so
|
|
5
|
-
// a handler that dies on a TypeError leaves no trace and the failure surfaces
|
|
6
|
-
// far away as ElementNotFound on whatever it was supposed to produce. That is
|
|
7
|
-
// the exact shape of the solidus taxon-tree failure in real-apps causes.yml.
|
|
8
|
-
//
|
|
9
|
-
// These listeners are passive — no preventDefault, so nothing the page would
|
|
10
|
-
// otherwise see is swallowed, and nothing that would have propagated stops
|
|
11
|
-
// propagating. They re-emit through the same console.debug sentinel channel
|
|
12
|
-
// turbo.js uses, and console.rb routes them into Browser#page_errors rather
|
|
13
|
-
// than #console_logs. Keeping the two apart is deliberate: console_logs stays
|
|
14
|
-
// "the console.* calls the page made", which is what Chrome models (an
|
|
15
|
-
// exception is exceptionThrown, not consoleAPICalled) and what Playwright and
|
|
16
|
-
// Puppeteer expose (pageerror, separate from console). Folding exceptions into
|
|
17
|
-
// console_logs would also break every suite already asserting that buffer is
|
|
18
|
-
// free of errors.
|
|
19
|
-
//
|
|
20
|
-
// Delete this file once upstream emits Runtime.exceptionThrown and the floor
|
|
21
|
-
// covers it: subscribe Browser::Console to that event and feed the same buffer.
|
|
22
|
-
function _reportPageError(fields) {
|
|
23
|
-
try {
|
|
24
|
-
console.debug('__lightpanda_page_error_' + JSON.stringify(fields));
|
|
25
|
-
} catch (e) {
|
|
26
|
-
// A field that won't serialize (a getter that throws, a cyclic `reason`)
|
|
27
|
-
// must not turn one page error into two, so report the kind and move on.
|
|
28
|
-
try {
|
|
29
|
-
console.debug('__lightpanda_page_error_' + JSON.stringify({
|
|
30
|
-
kind: fields && fields.kind ? fields.kind : 'error',
|
|
31
|
-
message: '(page error could not be serialized)'
|
|
32
|
-
}));
|
|
33
|
-
} catch (e2) {}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
window.addEventListener('error', function (e) {
|
|
38
|
-
// A failed <img>/<script>/<link> fetch fires `error` too, but on the element
|
|
39
|
-
// and without bubbling, so it shouldn't reach this listener. Guard anyway
|
|
40
|
-
// (Lightpanda's dispatch need not match Chrome's exactly) and identify those
|
|
41
|
-
// by their missing string `message` — a resource failure is a network fact
|
|
42
|
-
// and Network#traffic already carries it.
|
|
43
|
-
if (!e || typeof e.message !== 'string') return;
|
|
44
|
-
|
|
45
|
-
_reportPageError({
|
|
46
|
-
kind: 'error',
|
|
47
|
-
message: e.message,
|
|
48
|
-
url: typeof e.filename === 'string' ? e.filename : '',
|
|
49
|
-
line: typeof e.lineno === 'number' ? e.lineno : null,
|
|
50
|
-
column: typeof e.colno === 'number' ? e.colno : null,
|
|
51
|
-
// Cap the stack: it rides a console argument over the CDP WebSocket, and a
|
|
52
|
-
// deep recursion stack can be megabytes.
|
|
53
|
-
stack: e.error && typeof e.error.stack === 'string' ? e.error.stack.slice(0, 4000) : null
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
window.addEventListener('unhandledrejection', function (e) {
|
|
58
|
-
var reason = e ? e.reason : null;
|
|
59
|
-
var message;
|
|
60
|
-
try {
|
|
61
|
-
message = reason && reason.message ? String(reason.message) : String(reason);
|
|
62
|
-
} catch (err) {
|
|
63
|
-
message = '(rejection reason could not be stringified)';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
_reportPageError({
|
|
67
|
-
kind: 'unhandledrejection',
|
|
68
|
-
message: message,
|
|
69
|
-
url: '',
|
|
70
|
-
line: null,
|
|
71
|
-
column: null,
|
|
72
|
-
stack: reason && typeof reason.stack === 'string' ? reason.stack.slice(0, 4000) : null
|
|
73
|
-
});
|
|
74
|
-
});
|