capybara-lightpanda 0.10.0 → 0.11.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 +14 -0
- data/README.md +1 -1
- data/lib/capybara/lightpanda/node.rb +180 -0
- data/lib/capybara/lightpanda/options.rb +10 -1
- data/lib/capybara/lightpanda/process.rb +28 -6
- 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: 6b898fd26a9e25f95263b605f022264cd2a132bbca82081bd931fe542c88673e
|
|
4
|
+
data.tar.gz: 4776e1326f56cd6f123538b130dfb7de1edb1a745a45574e31520a1a5227ce94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 025d4fd23a6f0b4379c6e34d878610b657a06dcf33ab5490b606e5e42ed2e452475860283df232f8c397bbf1b48144f0d89be33bf7e5a70fe430a14e26bfb1a7
|
|
7
|
+
data.tar.gz: 16193011f9ea880c74300a45491ab5dbe36abe55e0a080fe1f65edf36d9722daf6e9842de361f498068952970f3f2d975a64d8b92eb8ecaf2600c01b342caf34
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-08-25
|
|
4
|
+
|
|
5
|
+
> **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.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Drag and drop between elements.** `element.drag_to(target)` performs an HTML5 drag — `dragstart` through `drop` with a shared `DataTransfer`, so dropzone pages and `setData`/`getData` handlers behave as in Chrome, and drag events carry whole-number coordinates like Chrome's. Libraries that only respond to real mouse movement (SortableJS's fallback mode) remain out of reach — there is still no layout engine to drive a pointer through.
|
|
10
|
+
- **`load_images` driver option.** `load_images: true` makes the browser fetch `<img>` sources over the network, so image requests appear in `network.traffic` and are counted by `wait_for_network_idle` — useful for suites that assert on image traffic. Off by default (images are skipped entirely, as before). Needs a nightly from 2026-08-26 onward.
|
|
11
|
+
- **Screenshots show the page.** `save_screenshot` — and the automatic failure screenshots capybara-screenshot takes — now produce a real rendering of the page's content instead of a fixed placeholder image. It is a text-layout rendering, useful for seeing what the DOM held at failure time; pixel-accurate visual regression still needs a full browser.
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- The internal workaround for reading an element's `draggable` state is gone now that the browser answers natively — no change required on your end.
|
|
16
|
+
|
|
3
17
|
## [0.10.0] - 2026-08-19
|
|
4
18
|
|
|
5
19
|
> **Update Lightpanda before upgrading.** This release requires a Lightpanda nightly build ≥ 8448 **or a tagged release ≥ 0.3.7**. The driver refuses to start below either floor and tells you which version it found. If you pin the browser (see below), pin `0.3.7` or newer.
|
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.
|
|
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 8796 — 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
|
|
|
@@ -211,6 +211,41 @@ module Capybara
|
|
|
211
211
|
nil
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
+
# Maps Capybara's documented drop_modifiers aliases onto the DragEvent
|
|
215
|
+
# init keys (`ctrlKey`, `metaKey`, ...). Same table as Cuprite's #315.
|
|
216
|
+
DRAG_MODIFIER_ALIASES = { control: :ctrl, command: :meta, cmd: :meta }.freeze
|
|
217
|
+
|
|
218
|
+
# Capybara's `Element#drag_to` — HTML5 half only. HTML5_DRAG_JS replays
|
|
219
|
+
# Capybara's own Selenium HTML5_DRAG_DROP_SCRIPT (the same source
|
|
220
|
+
# Cuprite's drag.js ports): dragstart on the draggable ancestor, then
|
|
221
|
+
# dragenter -> 2x dragover -> dragleave/drop -> dragend, setTimeout-paced,
|
|
222
|
+
# sharing one DataTransfer so `setData` in the page's dragstart handler is
|
|
223
|
+
# readable at drop. Runs through `evaluate_async` (the script signals
|
|
224
|
+
# completion via the appended callback), so the drag has fully played out
|
|
225
|
+
# before this method returns.
|
|
226
|
+
#
|
|
227
|
+
# The legacy path is coordinate-based mouse dragging, which Lightpanda
|
|
228
|
+
# cannot express (no layout to produce coordinates from) — it raises
|
|
229
|
+
# instead of silently no-oping. `html5: nil` auto-detects like Selenium
|
|
230
|
+
# does, via LEGACY_DRAG_CHECK_JS: we dispatch a synthetic mousedown where
|
|
231
|
+
# Selenium presses a real button, then apply the same
|
|
232
|
+
# prevented-or-no-draggable-ancestor test.
|
|
233
|
+
#
|
|
234
|
+
# `steps:`/`scroll:` (Cuprite's legacy-path knobs) are accepted and
|
|
235
|
+
# ignored so suites migrating from cuprite don't ArgumentError.
|
|
236
|
+
def drag_to(other, html5: nil, delay: 0.05, drop_modifiers: [], **)
|
|
237
|
+
keys = Array(drop_modifiers).map { |m| DRAG_MODIFIER_ALIASES.fetch(m.to_sym, m.to_sym).to_s }
|
|
238
|
+
html5 = !call(LEGACY_DRAG_CHECK_JS) if html5.nil?
|
|
239
|
+
unless html5
|
|
240
|
+
raise NotImplementedError,
|
|
241
|
+
"drag_to needs coordinate mouse dispatch for non-HTML5 (legacy) drags, which Lightpanda " \
|
|
242
|
+
"cannot do (no layout). Pass `html5: true` to force HTML5 DragEvent simulation."
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
driver.browser.evaluate_async(HTML5_DRAG_JS, self, other, (delay * 1000).to_i, keys)
|
|
246
|
+
nil
|
|
247
|
+
end
|
|
248
|
+
|
|
214
249
|
def select_option
|
|
215
250
|
call(SELECT_OPTION_JS)
|
|
216
251
|
end
|
|
@@ -645,6 +680,151 @@ module Capybara
|
|
|
645
680
|
}
|
|
646
681
|
JS
|
|
647
682
|
|
|
683
|
+
# Selenium's MOUSEDOWN_TRACKER + LEGACY_DRAG_CHECK folded into one round
|
|
684
|
+
# trip. Selenium presses a real mouse button before checking; we dispatch
|
|
685
|
+
# a synthetic mousedown so drag libraries that preventDefault on it
|
|
686
|
+
# (mouse-based / fallback DnD) still steer the check toward the legacy
|
|
687
|
+
# path. Returns true when the drag would need the legacy (coordinate)
|
|
688
|
+
# path: mousedown prevented / never observed, or no draggable ancestor.
|
|
689
|
+
LEGACY_DRAG_CHECK_JS = <<~JS
|
|
690
|
+
function() {
|
|
691
|
+
var doc = this.ownerDocument || document;
|
|
692
|
+
var prevented = null;
|
|
693
|
+
doc.addEventListener('mousedown', function(ev) { prevented = ev.defaultPrevented; }, { once: true });
|
|
694
|
+
this.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
|
|
695
|
+
if (prevented === true || prevented === null) return true;
|
|
696
|
+
var el = this;
|
|
697
|
+
do {
|
|
698
|
+
if (el.draggable) return false;
|
|
699
|
+
} while ((el = el.parentElement));
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
JS
|
|
703
|
+
|
|
704
|
+
# Ported near-verbatim from Capybara's Selenium driver
|
|
705
|
+
# (capybara/selenium/extensions/html5_drag.rb, HTML5_DRAG_DROP_SCRIPT) —
|
|
706
|
+
# the same source Cuprite's #315 drag.js ports — kept close to ease
|
|
707
|
+
# future syncs. Upstream quirks preserved deliberately: `rectPt.top` in
|
|
708
|
+
# pointOnRect (DOMPoint has no .top, that branch just falls through), the
|
|
709
|
+
# undeclared `key` loop variable, and `callback.call(true)`. The
|
|
710
|
+
# `source.draggable` reads are native — the HTMLElement.draggable IDL
|
|
711
|
+
# shipped upstream in #3257 (build 8793, guaranteed by the floor; the
|
|
712
|
+
# `_lightpanda.isDraggable` polyfill was retired with that bump).
|
|
713
|
+
# Coordinates come from getBoundingClientRect, which Lightpanda
|
|
714
|
+
# synthesizes without layout — dropzones reading clientX/Y get
|
|
715
|
+
# plausible-but-synthetic (integer since #3259) numbers.
|
|
716
|
+
HTML5_DRAG_JS = <<~JS
|
|
717
|
+
function rectCenter(rect){
|
|
718
|
+
return new DOMPoint(
|
|
719
|
+
(rect.left + rect.right)/2,
|
|
720
|
+
(rect.top + rect.bottom)/2
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function pointOnRect(pt, rect) {
|
|
725
|
+
var rectPt = rectCenter(rect);
|
|
726
|
+
var slope = (rectPt.y - pt.y) / (rectPt.x - pt.x);
|
|
727
|
+
|
|
728
|
+
if (pt.x <= rectPt.x) { // left side
|
|
729
|
+
var minXy = slope * (rect.left - pt.x) + pt.y;
|
|
730
|
+
if (rect.top <= minXy && minXy <= rect.bottom)
|
|
731
|
+
return new DOMPoint(rect.left, minXy);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (pt.x >= rectPt.x) { // right side
|
|
735
|
+
var maxXy = slope * (rect.right - pt.x) + pt.y;
|
|
736
|
+
if (rect.top <= maxXy && maxXy <= rect.bottom)
|
|
737
|
+
return new DOMPoint(rect.right, maxXy);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (pt.y <= rectPt.y) { // top side
|
|
741
|
+
var minYx = (rectPt.top - pt.y) / slope + pt.x;
|
|
742
|
+
if (rect.left <= minYx && minYx <= rect.right)
|
|
743
|
+
return new DOMPoint(minYx, rect.top);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (pt.y >= rectPt.y) { // bottom side
|
|
747
|
+
var maxYx = (rect.bottom - pt.y) / slope + pt.x;
|
|
748
|
+
if (rect.left <= maxYx && maxYx <= rect.right)
|
|
749
|
+
return new DOMPoint(maxYx, rect.bottom);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
return new DOMPoint(pt.x,pt.y);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function dragEnterTarget() {
|
|
756
|
+
target.scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'});
|
|
757
|
+
var targetRect = target.getBoundingClientRect();
|
|
758
|
+
var sourceCenter = rectCenter(source.getBoundingClientRect());
|
|
759
|
+
|
|
760
|
+
for (var i = 0; i < drop_modifier_keys.length; i++) {
|
|
761
|
+
key = drop_modifier_keys[i];
|
|
762
|
+
if (key == "control"){
|
|
763
|
+
key = "ctrl"
|
|
764
|
+
}
|
|
765
|
+
opts[key + 'Key'] = true;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
var dragEnterEvent = new DragEvent('dragenter', opts);
|
|
769
|
+
target.dispatchEvent(dragEnterEvent);
|
|
770
|
+
|
|
771
|
+
// fire 2 dragover events to simulate dragging with a direction
|
|
772
|
+
var entryPoint = pointOnRect(sourceCenter, targetRect)
|
|
773
|
+
var dragOverOpts = Object.assign({clientX: entryPoint.x, clientY: entryPoint.y}, opts);
|
|
774
|
+
var dragOverEvent = new DragEvent('dragover', dragOverOpts);
|
|
775
|
+
target.dispatchEvent(dragOverEvent);
|
|
776
|
+
window.setTimeout(dragOnTarget, step_delay);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function dragOnTarget() {
|
|
780
|
+
var targetCenter = rectCenter(target.getBoundingClientRect());
|
|
781
|
+
var dragOverOpts = Object.assign({clientX: targetCenter.x, clientY: targetCenter.y}, opts);
|
|
782
|
+
var dragOverEvent = new DragEvent('dragover', dragOverOpts);
|
|
783
|
+
target.dispatchEvent(dragOverEvent);
|
|
784
|
+
window.setTimeout(dragLeave, step_delay, dragOverEvent.defaultPrevented, dragOverOpts);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function dragLeave(drop, dragOverOpts) {
|
|
788
|
+
var dragLeaveOptions = Object.assign({}, opts, dragOverOpts);
|
|
789
|
+
var dragLeaveEvent = new DragEvent('dragleave', dragLeaveOptions);
|
|
790
|
+
target.dispatchEvent(dragLeaveEvent);
|
|
791
|
+
if (drop) {
|
|
792
|
+
var dropEvent = new DragEvent('drop', dragLeaveOptions);
|
|
793
|
+
target.dispatchEvent(dropEvent);
|
|
794
|
+
}
|
|
795
|
+
var dragEndEvent = new DragEvent('dragend', dragLeaveOptions);
|
|
796
|
+
source.dispatchEvent(dragEndEvent);
|
|
797
|
+
callback.call(true);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
var source = arguments[0],
|
|
801
|
+
target = arguments[1],
|
|
802
|
+
step_delay = arguments[2],
|
|
803
|
+
drop_modifier_keys = arguments[3],
|
|
804
|
+
callback = arguments[4];
|
|
805
|
+
|
|
806
|
+
var dt = new DataTransfer();
|
|
807
|
+
var opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
|
808
|
+
|
|
809
|
+
while (source && !source.draggable) {
|
|
810
|
+
source = source.parentElement;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (source.tagName == 'A'){
|
|
814
|
+
dt.setData('text/uri-list', source.href);
|
|
815
|
+
dt.setData('text', source.href);
|
|
816
|
+
}
|
|
817
|
+
if (source.tagName == 'IMG'){
|
|
818
|
+
dt.setData('text/uri-list', source.src);
|
|
819
|
+
dt.setData('text', source.src);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
var dragEvent = new DragEvent('dragstart', opts);
|
|
823
|
+
source.dispatchEvent(dragEvent);
|
|
824
|
+
|
|
825
|
+
window.setTimeout(dragEnterTarget, step_delay);
|
|
826
|
+
JS
|
|
827
|
+
|
|
648
828
|
VISIBLE_JS = "function() { return _lightpanda.isVisible(this); }"
|
|
649
829
|
|
|
650
830
|
VISIBLE_TEXT_JS = "function() { return _lightpanda.visibleText(this); }"
|
|
@@ -47,9 +47,16 @@ module Capybara
|
|
|
47
47
|
# a runaway confirm cancels the action and the spec can still pass. false
|
|
48
48
|
# (default, Cuprite parity) warns on stderr; true raises
|
|
49
49
|
# UnhandledModalError from the action that opened it.
|
|
50
|
+
# load_images: passes `--load-resources image` so `<img>` elements are
|
|
51
|
+
# fetched over the network (upstream #3230, build >= 8834). Off by
|
|
52
|
+
# default, matching Lightpanda's default of skipping image fetches
|
|
53
|
+
# entirely. When on, image requests ride the Network domain like any
|
|
54
|
+
# other — they show in `network.traffic` and count toward
|
|
55
|
+
# `wait_for_network_idle`. On builds < 8834 the flag is a fatal
|
|
56
|
+
# UnknownOption at boot, so only enable against binaries that have it.
|
|
50
57
|
attr_accessor :host, :port, :timeout, :handshake_timeout, :process_timeout,
|
|
51
58
|
:window_size, :browser_path, :headless, :logger, :save_path,
|
|
52
|
-
:raise_on_unhandled_modal
|
|
59
|
+
:raise_on_unhandled_modal, :load_images
|
|
53
60
|
attr_writer :ws_url
|
|
54
61
|
|
|
55
62
|
def initialize(options = {})
|
|
@@ -63,6 +70,7 @@ module Capybara
|
|
|
63
70
|
@headless = options.fetch(:headless, true)
|
|
64
71
|
@save_path = options[:save_path]
|
|
65
72
|
@raise_on_unhandled_modal = options.fetch(:raise_on_unhandled_modal, false)
|
|
73
|
+
@load_images = options.fetch(:load_images, false)
|
|
66
74
|
@ws_url = options[:ws_url]
|
|
67
75
|
@logger = parse_logger(options[:logger])
|
|
68
76
|
end
|
|
@@ -88,6 +96,7 @@ module Capybara
|
|
|
88
96
|
logger: logger,
|
|
89
97
|
save_path: save_path,
|
|
90
98
|
raise_on_unhandled_modal: raise_on_unhandled_modal,
|
|
99
|
+
load_images: load_images,
|
|
91
100
|
}
|
|
92
101
|
h[:ws_url] = @ws_url if @ws_url
|
|
93
102
|
h
|
|
@@ -180,7 +180,8 @@ module Capybara
|
|
|
180
180
|
# set, merged 2026-07-30) — a use-after-free in exactly the path
|
|
181
181
|
# `Node#select_option`/`Node#set` on a <select> drives, i.e. a browser
|
|
182
182
|
# crash surfacing as DeadBrowserError mid-spec.
|
|
183
|
-
# Build 8448 = the #3087 merge (6d824c88) —
|
|
183
|
+
# Build 8448 = the #3087 merge (6d824c88) — the binding floor of the
|
|
184
|
+
# 2026-07-30 bump.
|
|
184
185
|
# Subsumed by 8448, recorded so they are not re-derived: build 8298 made
|
|
185
186
|
# Network.enable idempotent (the gem never hit it — the Notification is
|
|
186
187
|
# per-BrowserContext and Network#enable's @enabled guard means one enable
|
|
@@ -191,7 +192,16 @@ module Capybara
|
|
|
191
192
|
# `window_size` option. Note the override only reached
|
|
192
193
|
# Page.getLayoutMetrics later (~build 8300); the gem does not depend on
|
|
193
194
|
# that half, so it is NOT part of the floor.
|
|
194
|
-
|
|
195
|
+
#
|
|
196
|
+
# 2026-08-25 bump 8448 -> 8796: #3257 (HTMLElement.draggable IDL, build
|
|
197
|
+
# 8793) and #3259 (MouseEvent coordinate getters floor to integers
|
|
198
|
+
# Chrome-style, build 8796; PointerEvent stays fractional). The floor
|
|
199
|
+
# guarantees native `.draggable`, so the drag scripts in node.rb read it
|
|
200
|
+
# verbatim — the `_lightpanda.isDraggable` polyfill was retired with
|
|
201
|
+
# this bump — and drag events carry integer clientX/Y (the shared spec
|
|
202
|
+
# asserting that runs un-skipped). Build 8796 = the #3259 merge
|
|
203
|
+
# (341a01570), which is also the 2026-08-25 nightly cut.
|
|
204
|
+
MINIMUM_NIGHTLY_BUILD = Gem::Version.new("8796")
|
|
195
205
|
|
|
196
206
|
# Second, equivalent floor for the *release* channel.
|
|
197
207
|
#
|
|
@@ -200,15 +210,21 @@ module Capybara
|
|
|
200
210
|
# bare "0.3.6" carrying no commit counter at all, and MINIMUM_NIGHTLY_BUILD
|
|
201
211
|
# has nothing to compare against. Releases are cut from the same trunk, so
|
|
202
212
|
# a release is acceptable exactly when its own commit count clears the
|
|
203
|
-
# nightly floor
|
|
204
|
-
#
|
|
205
|
-
#
|
|
213
|
+
# nightly floor. As of 2026-08-25 NO published release clears the 8796
|
|
214
|
+
# floor — 0.3.7 (build 8671) predates the draggable-IDL and
|
|
215
|
+
# coordinate-flooring fixes the floor exists for — so this pin names the
|
|
216
|
+
# NEXT release. 0.3.8 is not tagged yet; any future tag is cut from a
|
|
217
|
+
# trunk already past build 8834, so it clears the floor by construction.
|
|
218
|
+
# Until upstream tags it, the release channel is deliberately unusable
|
|
219
|
+
# (nightly-only), reported with the standard too-old error. When 0.3.8
|
|
220
|
+
# ships: verify `git rev-list --count 0.3.8` >= 8796 and update the
|
|
221
|
+
# lockstep table in process_test.rb.
|
|
206
222
|
#
|
|
207
223
|
# INVARIANT: every MINIMUM_NIGHTLY_BUILD bump must also move this to the
|
|
208
224
|
# first release containing that build (`git rev-list --count <tag>` in the
|
|
209
225
|
# browser repo tells you). Leaving it behind would let the release channel
|
|
210
226
|
# silently accept a binary the nightly channel rejects.
|
|
211
|
-
MINIMUM_RELEASE = Gem::Version.new("0.3.
|
|
227
|
+
MINIMUM_RELEASE = Gem::Version.new("0.3.8")
|
|
212
228
|
|
|
213
229
|
attr_reader :pid, :ws_url, :version, :nightly_build, :release
|
|
214
230
|
|
|
@@ -371,6 +387,12 @@ module Capybara
|
|
|
371
387
|
"--cdp-max-message-size",
|
|
372
388
|
(100 * 1024 * 1024).to_s,
|
|
373
389
|
]
|
|
390
|
+
# Opt-in image fetching (upstream #3230, build >= 8834). Deliberately
|
|
391
|
+
# NOT always-on like --enable-external-stylesheets: images never feed
|
|
392
|
+
# the DOM predicates, so the default spends no bandwidth on them. On
|
|
393
|
+
# builds < 8834 the flag is a fatal UnknownOption at boot — see
|
|
394
|
+
# Options#load_images.
|
|
395
|
+
base.push("--load-resources", "image") if @options.load_images
|
|
374
396
|
extra = ENV.fetch("LIGHTPANDA_EXTRA_ARGS", "").split
|
|
375
397
|
base + extra
|
|
376
398
|
end
|