Class: Capybara::Simulated::Driver
- Inherits:
-
Driver::Base
- Object
- Driver::Base
- Capybara::Simulated::Driver
- Defined in:
- lib/capybara/simulated/driver.rb
Defined Under Namespace
Classes: FakePlaywrightLocator, FakePlaywrightPage
Constant Summary collapse
- PRIMARY_HANDLE =
Per-window Browser/VM.
open_aux_windowcreates a fresh Browser sharing the Driver's cookie + localStorage jars (origin-shared in real browsers) and visits the target URL;switch_to_windowflips@active_handleso subsequent driver ops route throughcurrent_browser. sessionStorage + DOM + history + the JS VM stay per-window. 'csim-window-0'- @@live_lock =
Mutex.new
- @@live =
[WeakRef
] — dead refs filtered on read. []
Instance Attribute Summary collapse
-
#app ⇒ Object
readonly
Returns the value of attribute app.
-
#browser ⇒ Object
readonly
Returns the value of attribute browser.
-
#owner_thread ⇒ Object
readonly
Returns the value of attribute owner_thread.
Class Method Summary collapse
Instance Method Summary collapse
- #accept_modal(type, **options, &block) ⇒ Object
- #active_element ⇒ Object
-
#blob_bytes_for(url, accessor) ⇒ Object
Resolve a blob: URL's bytes from whichever Browser created it (the bytes live in the creator's isolate, not necessarily the navigator's).
-
#blob_partition_site_of(url) ⇒ Object
The storage-partition site a blob: URL was created in (its creator's top-level site), or nil for an unknown / revoked / unpartitioned URL.
-
#broadcast_channel(source_browser, name, data, origin = nil) ⇒ Object
BroadcastChannel.postMessage— deliver to every OTHER window's channels with the same name (same-window delivery is handled in-VM by the sender). -
#clear_http_cache ⇒ Object
Start the next fetches from a cold HTTP cache.
- #close_window(h) ⇒ Object
-
#cross_partition_blob?(url, accessor) ⇒ Boolean
Is this blob: URL in a different storage partition than
accessor's top-level site? Unknown blobs (no entry) are treated as same-partition (no extra gating beyond the existing same-origin behaviour). -
#current_browser ⇒ Object
Active window's Browser.
- #current_trace ⇒ Object
- #current_url ⇒ Object
- #current_window_handle ⇒ Object
- #dismiss_modal(type, **options, &block) ⇒ Object
-
#dispose ⇒ Object
Full teardown of the whole driver: aux windows AND the primary browser's V8 isolate.
-
#disposed? ⇒ Boolean
Has this driver been permanently dropped? (A
reset!between examples does NOT set this — that rebuilds the page on a live runtime.). -
#drain_background_requests ⇒ Object
Join every window's background app-request threads (async
loads, keepalive fetches) without resetting anything else.
- #evaluate_async_script(script, *args) ⇒ Object
- #evaluate_script(script, *args) ⇒ Object
-
#execute_script(script, *args) ⇒ Object
Capybara's
execute_scriptcontract is "run it, discard the return". - #find_css(query, **_) ⇒ Object
- #find_xpath(query, **_) ⇒ Object
-
#fire_aux_window_load(handle) ⇒ Object
Cross-window remote-ref RPC: route a node/object proxy op to the window that owns the ref (handle), executing in that window's VM.
- #fullscreen_window(handle) ⇒ Object
- #go_back ⇒ Object
- #go_forward ⇒ Object
- #header(name, value) ⇒ Object
- #html ⇒ Object
-
#initialize(app, js_engine: nil, viewport: nil, user_agent: nil) ⇒ Driver
constructor
viewport: [w, h]anduser_agent:(typically supplied viaCapybara.register_driver) force the JS-sideinnerWidth/innerHeightandnavigator.userAgent(plusHTTP_USER_AGENTon Rack requests) before the first navigate, somatchMedia/ mobile-breakpoint branches and server-side UA-based mobile detection both resolve before any document loads. - #invalid_element_errors ⇒ Object
- #javascript_enabled? ⇒ Boolean
-
#js_engine ⇒ Object
Which JS engine is behind this driver (
:v8/:quickjs), for a trace's metadata. -
#maximize_window(handle) ⇒ Object
Both restore the window to the display it lives on (
Browser#screen_size), which is where it started — so they undo aresize_torather than doing nothing. - #needs_server? ⇒ Boolean
- #no_such_window_error ⇒ Object
-
#open_aux_window(url = nil, name: nil, opener_handle: nil, source: nil, blob_snapshot: nil, post: nil, opener: false, referrer: nil, defer_load: false) ⇒ Object
Open (or, by
name, reuse) an auxiliary window. -
#open_new_window(_kind = :tab) ⇒ Object
Capybara
Session#open_new_window(:tab)entry point — opens atabout:blank(socurrent_url/title match a real new tab) and the test thenswitch_to_window+visits the real URL. -
#open_window_from_js(opener_browser, url, name, opener_realm_id = 0, about_base = nil, about_origin = nil) ⇒ Object
window.open(url, name)from theopenerwindow's JS. - #opener_handle_of(browser) ⇒ Object
-
#peek_script(expr) ⇒ Object
Clock-free read of a JS expression in the active browsing context (no virtual-time advance, unlike evaluate_script) — for polling page state between event-loop frames without perturbing the clock.
- #refresh ⇒ Object
-
#register_blob_partition(url, browser, site) ⇒ Object
Record / drop a blob URL's storage partition (called by Browser#blob_register / #blob_unregister).
- #reset! ⇒ Object
- #reset_history! ⇒ Object
-
#reset_windows! ⇒ Object
Dispose every auxiliary window and return focus to the primary — a fresh browsing context has no sibling windows.
-
#resize(w, h) ⇒ Object
Forem's ahoy-tracking spec calls
driver.resize(w, h)directly rather than throughcurrent_window.resize_to. - #resize_window_to(handle, w, h) ⇒ Object
- #response_headers ⇒ Object
-
#revoke_blob_partitioned(url, source) ⇒ Object
A user
URL.revokeObjectURL(url)fromsource. -
#run_event_loop_frame(frame_ms) ⇒ Object
Run one real-cadence event-loop frame and return the loop's observable state.
-
#save_screenshot(path, full: false, **_opts) ⇒ Object
A real raster of the laid-out page (see js/src/paint.js), not a serialization of it: the painter reads the same boxes every geometry query reads, so a screenshot can only show what the driver already believes.
- #send_keys(*keys) ⇒ Object
-
#set_geolocation(latitude: nil, longitude: nil, accuracy: 10, denied: false, **rest) ⇒ Object
CDP-ish geolocation override (Capybara driver-level API).
-
#start_tracing(**metadata) ⇒ Object
Per-test trace recording.
- #status_code ⇒ Object
- #stop_tracing(path: nil) ⇒ Object
-
#storage_broadcast(source_browser, kind, key, old, new, url) ⇒ Object
A localStorage change fans out to every OTHER window (localStorage spans same-origin browsing contexts).
-
#sw_navigation_fetch(url, **kw) ⇒ Object
The fetch-event round-trip for a controlled NAVIGATION, resolved across the whole window set: a service-worker registration is profile-wide, so the Browser hosting the controlling SW may be any window's — or a parked one whose window already closed (searched newest-parked first: a re-registered scope's newest worker is the active registration).
-
#switch_to_frame(frame) ⇒ Object
Capybara
within_frame/switch_to_frame. - #switch_to_window(h) ⇒ Object
- #title ⇒ Object
-
#trace_screenshot ⇒ Object
The ACTIVE window's page, painted for the trace's final state (
TracePersistence) —current_browser, like every other user-facing read here, not the primarybrowser: a test that ended insideswitch_to_windowwould otherwise be handed a picture of the window it was not looking at. - #tracing? ⇒ Boolean
- #unregister_blob_partition(url) ⇒ Object
- #visit(path) ⇒ Object
-
#wait? ⇒ Boolean
Dynamic wait?: only poll when there's pending timer work that real-time advancement could resolve.
-
#window_browser(handle) ⇒ Object
The Browser backing a handle, or nil if the window is closed/unknown.
-
#window_browser!(handle) ⇒ Object
Same, but for the operations that ADDRESS a window rather than probe for one: a closed or unknown handle is Capybara's
WindowError, never a silent fall back to the current window. - #window_closed?(handle) ⇒ Boolean
- #window_handles ⇒ Object
-
#window_history_go(handle, delta) ⇒ Object
A cross-window
w.history.back()/forward()/go(n): traverse the target window's history. -
#window_location(handle) ⇒ Object
raw_: an identity read — this runs inside host-fn callbacks (a popup's boot script reading opener.location), where the ticking current_url must not re-enter.
-
#window_post_message(source_browser, target_handle, data, target_origin, sender_origin) ⇒ Object
targetWindow.postMessage(data, targetOrigin)— queue on the target window's Browser, tagged with the source window's handle. -
#window_read(handle, prop, doc: false) ⇒ Object
A cross-window property read (
win.foo/win.document.foo) — read the primitive off the target window's VM. - #window_ref_call(handle, id, method, args) ⇒ Object
- #window_ref_get(handle, id, prop) ⇒ Object
- #window_ref_set(handle, id, prop, value) ⇒ Object
- #window_set_location(handle, url) ⇒ Object
-
#window_size(handle) ⇒ Object
Every window has its own viewport, so these address the window the handle names — not the active one.
-
#with_playwright_page {|FakePlaywrightPage.new(current_browser)| ... } ⇒ Object
Playwright-driver compatibility shim.
-
#worker_drive_pending? ⇒ Boolean
Worker cross-thread work in flight in ANY window — the same aggregate scope as run_event_loop_frame's merged
async(which folds every window in), so a caller distinguishing "waiting on a worker thread" from other async channels (the wpt_runner's clock-hold) sees a popup-hosted worker/SW round trip too, not just the active window's.
Constructor Details
#initialize(app, js_engine: nil, viewport: nil, user_agent: nil) ⇒ Driver
viewport: [w, h] and user_agent: (typically supplied via
Capybara.register_driver) force the JS-side
innerWidth/innerHeight and navigator.userAgent (plus
HTTP_USER_AGENT on Rack requests) before the first navigate,
so matchMedia / mobile-breakpoint branches and server-side
UA-based mobile detection both resolve before any document
loads. The Browser tracks both as "defaults" so reset!
(per-test teardown) restores them between specs.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/capybara/simulated/driver.rb', line 70 def initialize(app, js_engine: nil, viewport: nil, user_agent: nil) # `Capybara.disable_animation` is delivered to the real drivers by a SERVER # middleware (session.rb adds AnimationDisabler to the Puma stack), which # injects `animation-duration: 0s !important` CSS into every HTML response. # This driver calls the Rack app in-process and never builds that server, so # the same wrap happens here — an app suite that turns animations off # (Discourse's rails_helper) must see the same 0s durations a real browser # run sees, or every `await`-on-animation close path (FloatKit's menu) parks # on a full-length animation-fallback timer no user action waits for. @app = Capybara.disable_animation ? Capybara::Server::AnimationDisabler.new(app) : app @js_engine = js_engine # Cookies + localStorage are origin-shared across windows # (real browser semantics), so we own the jars at the Driver # level and inject them into every per-window Browser. Each # Browser still has its own sessionStorage + DOM + JS VM. @cookies = {} @cookie_flags = {} # (host \0 name) => {secure: true} — attribute sidecar for the shared jar @auth_cache = {} @local_storage = {} # Cache Storage (caches/Cache) is origin-shared like localStorage — owned at the # Driver level and injected, so a service worker and every same-origin window see # the same caches (partitioned by origin key within the store). @cache_storage = {} # Capture the universal-server flag ONCE, at session construction — the WPT # runner sets CSIM_LOCAL_ALL_HOSTS only while building the session, then # restores it. Every window (incl. aux windows opened later) inherits this so # cross-origin iframes eager-build consistently across the whole session. @all_hosts_local = ENV['CSIM_LOCAL_ALL_HOSTS'] == '1' @browser = build_window_browser @browser.window_handle = PRIMARY_HANDLE @aux_windows = [] # [{handle:, browser:, name:, opener:}, …] # Browsers whose WINDOW closed while they still host an active service-worker # registration. A registration is profile-wide state independent of the document # that created it (a page registers, closes, and the SW keeps controlling # navigations elsewhere), so the Browser is parked — keeping its SW worker # thread + scope registry alive for sw_navigation_fetch — instead of disposed. # Reclaimed by reset_windows!. @sw_parked = [] @active_handle = nil @next_window_seq = 0 # Driver-level blob URL partition map: url => {browser:, site:}. A blob URL's # storage partition is its creating context's top-level SITE; another window # can resolve the blob only from the same partition (and same origin, which # the blob: URL embeds). Bytes aren't copied here — they're read back from the # creating Browser's own store, so this stays a light reference map. @blob_partitions = {} @blob_partitions_lock = Mutex.new @owner_thread = Thread.current @@live_lock.synchronize { @@live << WeakRef.new(self) } @browser. = if @browser.default_user_agent = user_agent if user_agent end |
Instance Attribute Details
#app ⇒ Object (readonly)
Returns the value of attribute app.
46 47 48 |
# File 'lib/capybara/simulated/driver.rb', line 46 def app @app end |
#browser ⇒ Object (readonly)
Returns the value of attribute browser.
159 160 161 |
# File 'lib/capybara/simulated/driver.rb', line 159 def browser @browser end |
#owner_thread ⇒ Object (readonly)
Returns the value of attribute owner_thread.
46 47 48 |
# File 'lib/capybara/simulated/driver.rb', line 46 def owner_thread @owner_thread end |
Class Method Details
.each_live_on_thread(thread) ⇒ Object
51 52 53 54 55 56 57 58 59 60 |
# File 'lib/capybara/simulated/driver.rb', line 51 def self.each_live_on_thread(thread) drivers = @@live_lock.synchronize { @@live.select!(&:weakref_alive?) @@live.filter_map {|ref| ref.__getobj__ rescue nil } } # A DISPOSED driver is not live: its runtime context is gone, so calling into it raises # (`undefined method 'call' for nil` out of `run_loop_step`). `dispose` deregisters, but the # predicate is the belt — a WeakRef stays in the list until GC actually collects. drivers.each {|d| yield d if d.owner_thread == thread && !d.disposed? } end |
Instance Method Details
#accept_modal(type, **options, &block) ⇒ Object
913 |
# File 'lib/capybara/simulated/driver.rb', line 913 def accept_modal(type, **, &block) = run_modal(type, accept: true, **, &block) |
#active_element ⇒ Object
885 886 887 888 |
# File 'lib/capybara/simulated/driver.rb', line 885 def active_element handle = current_browser.active_element_handle handle ? Node.new(self, handle) : nil end |
#blob_bytes_for(url, accessor) ⇒ Object
Resolve a blob: URL's bytes from whichever Browser created it (the bytes live in the creator's isolate, not necessarily the navigator's). Used to load a blob document into a TOP-LEVEL window (window.open / a window navigation): that new context is the blob's own partition, so no partition gate applies here — the cross-partition rule for windows is the noopener severing above, and for nested frames it is enforced at the frame-navigation site. Falls back to the accessor's own store for an unpartitioned URL (worker / legacy path).
561 562 563 564 565 |
# File 'lib/capybara/simulated/driver.rb', line 561 def blob_bytes_for(url, accessor) entry = @blob_partitions_lock.synchronize { @blob_partitions[url.to_s] } creator = entry ? entry[:browser] : accessor creator.respond_to?(:read_blob_for_window) ? creator.read_blob_for_window(url) : nil end |
#blob_partition_site_of(url) ⇒ Object
The storage-partition site a blob: URL was created in (its creator's top-level site), or nil for an unknown / revoked / unpartitioned URL.
541 542 543 544 |
# File 'lib/capybara/simulated/driver.rb', line 541 def blob_partition_site_of(url) e = @blob_partitions_lock.synchronize { @blob_partitions[url.to_s] } e && e[:site] end |
#broadcast_channel(source_browser, name, data, origin = nil) ⇒ Object
BroadcastChannel.postMessage — deliver to every OTHER window's channels
with the same name (same-window delivery is handled in-VM by the sender).
621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
# File 'lib/capybara/simulated/driver.rb', line 621 def broadcast_channel(source_browser, name, data, origin = nil) # An opaque origin is unique to its own agent cluster; its key is a token ('opaque:…') minted # per-realm and therefore only unique WITHIN one isolate — two unrelated opaque contexts in # DIFFERENT windows could mint the same token. A BroadcastChannel never bridges two distinct # opaque origins, and no opaque origin spans separate top-level windows here, so a cross- # WINDOW post from an opaque origin reaches no one: drop it rather than risk a cross-isolate # token collision. (Same-isolate opaque peers are reached in-VM / via enqueue_broadcast; an # inherited-origin blob worker via its own inbox — neither goes through this cross-window path.) return if origin.to_s.start_with?('opaque:') window_entries.each do |w| next if w[:browser].equal?(source_browser) w[:browser].enqueue_broadcast(name, data, nil, origin) end end |
#clear_http_cache ⇒ Object
Start the next fetches from a cold HTTP cache. reset! keeps what a persistent
browser profile would — fresh Cache-Control: immutable responses, plus the
still-fresh script / stylesheet sources and @font-face files — so a test whose
app serves new bytes at a cacheable URL it already served (a stylesheet digested
from a DB row that a rolled-back example reuses) asks for the cold cache a fresh
Playwright / Cuprite context starts with. Process-wide: every session shares the
one cache (Capybara::Simulated.clear_http_cache is the same call for a hook
that runs before any session exists).
336 |
# File 'lib/capybara/simulated/driver.rb', line 336 def clear_http_cache = Browser.clear_http_cache |
#close_window(h) ⇒ Object
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 |
# File 'lib/capybara/simulated/driver.rb', line 740 def close_window(h) return if h == PRIMARY_HANDLE @aux_windows.reject! {|w| next false unless w[:handle] == h # HTML "close a browsing context": the teardown events (pagehide+unload, # this window and every nested frame, parent-first) fire while the VM # still works — a nested iframe's unload keepalive beacon depends on it. w[:browser].fire_document_teardown if w[:browser].respond_to?(:fire_document_teardown) if w[:browser].sw_registrations_active? # Still hosting a live service-worker registration — park (see @sw_parked): # tear down the document-scoped machinery, retire older parked browsers # whose scopes this one re-registered (the newest registration for a scope # is THE registration, as in a real profile), and keep the browser alive. w[:browser].park_for_service_workers! retire_shadowed_parked(w[:browser]) @sw_parked << w[:browser] else drop_blob_partitions_for(w[:browser]) # don't leave entries pointing at a disposed VM w[:browser].dispose rescue nil end true } @active_handle = nil if @active_handle == h end |
#cross_partition_blob?(url, accessor) ⇒ Boolean
Is this blob: URL in a different storage partition than accessor's top-level
site? Unknown blobs (no entry) are treated as same-partition (no extra gating
beyond the existing same-origin behaviour).
549 550 551 552 |
# File 'lib/capybara/simulated/driver.rb', line 549 def cross_partition_blob?(url, accessor) site = blob_partition_site_of(url) !site.nil? && site != accessor.blob_partition_site end |
#current_browser ⇒ Object
Active window's Browser. Primary by default; switches when the
test calls switch_to_window(aux_handle). Every DOM / URL /
JS-touching driver method routes through here so per-window
state (DOM, sessionStorage, history) stays window-scoped.
165 166 167 168 169 |
# File 'lib/capybara/simulated/driver.rb', line 165 def current_browser return @browser unless @active_handle w = @aux_windows.find {|win| win[:handle] == @active_handle } w ? w[:browser] : @browser end |
#current_trace ⇒ Object
157 |
# File 'lib/capybara/simulated/driver.rb', line 157 def current_trace = browser.trace || browser.pending_trace |
#current_url ⇒ Object
391 |
# File 'lib/capybara/simulated/driver.rb', line 391 def current_url = current_browser.current_url || '' |
#current_window_handle ⇒ Object
422 |
# File 'lib/capybara/simulated/driver.rb', line 422 def current_window_handle = @active_handle || PRIMARY_HANDLE |
#dismiss_modal(type, **options, &block) ⇒ Object
914 |
# File 'lib/capybara/simulated/driver.rb', line 914 def dismiss_modal(type, **, &block) = run_modal(type, accept: false, **, &block) |
#dispose ⇒ Object
Full teardown of the whole driver: aux windows AND the primary browser's
V8 isolate. reset_windows! deliberately keeps the primary alive (the
per-test reset path rebuilds only its page); this is for permanently
DROPPING a session. A caller that nils its session without this leaks the
primary isolate — with its heap, canvas pixel buffers, and worker threads —
into V8Runtime's process-wide @@live until at_exit. The WPT runner recycles
the cross-origin session per .sub./.https. file, so that leak is ~one
isolate per cross-origin file (hundreds over the suite); disposing here
incrementally is what reset_windows! already does for aux windows.
375 376 377 378 379 380 381 382 383 |
# File 'lib/capybara/simulated/driver.rb', line 375 def dispose return if @disposed @disposed = true # Drop out of the live registry FIRST: everything below tears down the runtime this driver # would be asked to step if `each_live_on_thread` still yielded it. @@live_lock.synchronize { @@live.reject! {|ref| (ref.__getobj__ rescue nil).equal?(self) } } reset_windows! @browser.dispose rescue nil end |
#disposed? ⇒ Boolean
Has this driver been permanently dropped? (A reset! between examples does NOT set this —
that rebuilds the page on a live runtime.)
387 |
# File 'lib/capybara/simulated/driver.rb', line 387 def disposed? = @disposed == true |
#drain_background_requests ⇒ Object
Join every window's background app-request threads (async loads,
keepalive fetches) without resetting anything else. The test harness calls
this ahead of the app's own after-hooks: cleanup that bypasses
ActiveRecord's per-connection lock (Discourse's mini_sql
DB.exec) must
not interleave with a still-running background request on the same raw
socket — reset!'s drain alone runs after those hooks.
344 345 346 347 |
# File 'lib/capybara/simulated/driver.rb', line 344 def drain_background_requests @aux_windows.each {|w| w[:browser].drain_app_request_threads rescue nil } browser.drain_app_request_threads end |
#evaluate_async_script(script, *args) ⇒ Object
855 856 857 |
# File 'lib/capybara/simulated/driver.rb', line 855 def evaluate_async_script(script, *args) unwrap(current_browser.evaluate_async_script(script, args)) end |
#evaluate_script(script, *args) ⇒ Object
840 841 842 |
# File 'lib/capybara/simulated/driver.rb', line 840 def evaluate_script(script, *args) unwrap(current_browser.evaluate_script(script, args)) end |
#execute_script(script, *args) ⇒ Object
Capybara's execute_script contract is "run it, discard the
return". Route through a no-return JS path so a script that
returns a non-marshallable value (jQuery $('…').text('…')
returns a chainable jQuery object that the engine's value
filter recurses into until it stack-overflows) doesn't blow
up on the way back.
850 851 852 853 |
# File 'lib/capybara/simulated/driver.rb', line 850 def execute_script(script, *args) current_browser.execute_script(script, args) nil end |
#find_css(query, **_) ⇒ Object
402 403 404 |
# File 'lib/capybara/simulated/driver.rb', line 402 def find_css(query, **_) current_browser.find_css(query).map {|id| Node.new(self, id) } end |
#find_xpath(query, **_) ⇒ Object
398 399 400 |
# File 'lib/capybara/simulated/driver.rb', line 398 def find_xpath(query, **_) current_browser.find_xpath(query).map {|id| Node.new(self, id) } end |
#fire_aux_window_load(handle) ⇒ Object
Cross-window remote-ref RPC: route a node/object proxy op to the window that owns the ref (handle), executing in that window's VM.
659 |
# File 'lib/capybara/simulated/driver.rb', line 659 def fire_aux_window_load(handle) = ((b = window_browser(handle)) && b.fire_own_window_load) |
#fullscreen_window(handle) ⇒ Object
834 |
# File 'lib/capybara/simulated/driver.rb', line 834 def fullscreen_window(handle) = restore_window_size(handle) |
#go_back ⇒ Object
388 |
# File 'lib/capybara/simulated/driver.rb', line 388 def go_back = current_browser.go_back |
#go_forward ⇒ Object
389 |
# File 'lib/capybara/simulated/driver.rb', line 389 def go_forward = current_browser.go_forward |
#header(name, value) ⇒ Object
396 |
# File 'lib/capybara/simulated/driver.rb', line 396 def header(name, value) = current_browser.set_header(name, value) |
#html ⇒ Object
392 |
# File 'lib/capybara/simulated/driver.rb', line 392 def html = current_browser.html |
#invalid_element_errors ⇒ Object
870 |
# File 'lib/capybara/simulated/driver.rb', line 870 def invalid_element_errors = [Capybara::Simulated::StaleElement, Capybara::Simulated::ClickIntercepted] |
#javascript_enabled? ⇒ Boolean
172 |
# File 'lib/capybara/simulated/driver.rb', line 172 def javascript_enabled? = true |
#js_engine ⇒ Object
Which JS engine is behind this driver (:v8 / :quickjs), for a trace's metadata.
148 |
# File 'lib/capybara/simulated/driver.rb', line 148 def js_engine = browser.js_engine |
#maximize_window(handle) ⇒ Object
Both restore the window to the display it lives on (Browser#screen_size), which is where
it started — so they undo a resize_to rather than doing nothing. Coarse: we model no
window chrome, so a maximized window and a fullscreen one end up the same size (a real
browser's fullscreen is taller by the chrome it hides).
833 |
# File 'lib/capybara/simulated/driver.rb', line 833 def maximize_window(handle) = restore_window_size(handle) |
#needs_server? ⇒ Boolean
171 |
# File 'lib/capybara/simulated/driver.rb', line 171 def needs_server? = false |
#no_such_window_error ⇒ Object
871 |
# File 'lib/capybara/simulated/driver.rb', line 871 def no_such_window_error = Capybara::WindowError |
#open_aux_window(url = nil, name: nil, opener_handle: nil, source: nil, blob_snapshot: nil, post: nil, opener: false, referrer: nil, defer_load: false) ⇒ Object
Open (or, by name, reuse) an auxiliary window. target="_blank"
clicks and window.open both land here. A non-empty name that
matches an existing window navigates that window instead of opening a
new one (HTML window-name targeting); opener_handle records the
opener so the new window's window.opener resolves back to it.
defer_load: the caller will fire the new window's load itself, one task
later, so that a handler either side registers right after window.open() is
in place first (platform-globals' fireAuxLoadSoon). ONLY the JS window.open
path does that — a target=_blank click or open_new_window hands nobody a
reference to hook, so their window announces its own load like any other.
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 |
# File 'lib/capybara/simulated/driver.rb', line 453 def open_aux_window(url = nil, name: nil, opener_handle: nil, source: nil, blob_snapshot: nil, post: nil, opener: false, referrer: nil, defer_load: false) name = name.to_s # A blob: URL opened from a different storage partition is forced noopener # (cross-partition-navigation), overriding an explicit rel=opener — the new # top-level window is the blob's own partition (so the blob still loads), but # the opener relationship is severed. opener = false if opener && url.to_s.start_with?('blob:') && source && cross_partition_blob?(url, source) # A `<form target>` keeps its opener by default (unlike a `target=_blank` # LINK, which is noopener) — resolve the opener handle from the source. opener_handle ||= handle_for(source) if opener && source if !name.empty? && (existing = @aux_windows.find {|w| w[:name] == name }) if post existing[:browser].navigate_post(url, post[:body], post[:content_type], referer: referrer, initiator: source&.raw_current_url) else navigate_window(existing[:browser], url, source: source) end return existing[:handle] end @next_window_seq += 1 handle = "csim-window-#{@next_window_seq}" aux = build_window_browser aux.defer_window_load = defer_load aux.window_handle = handle # Register BEFORE visiting: the opened document's own boot scripts read # `window.opener`, which resolves through this entry — so the entry # (with its opener) must exist before `visit` runs those scripts. @aux_windows << {handle: handle, browser: aux, name: name, opener: opener_handle} if url && !url.empty? if post # A `<form target=_blank method=post>` loads the new window via POST, # carrying the opener's URL as referrer (unless rel=noreferrer → ''). aux.navigate_post(url, post[:body], post[:content_type], referer: referrer, initiator: source&.raw_current_url) # A blob: URL isn't rack-navigable and its bytes live in the OPENER's # isolate — load the document directly from a click-time snapshot (a # deferred target=_blank nav may revoke the URL first) or, failing that, # the opener's blob store. elsif !(url.to_s.start_with?('blob:') && load_blob_into_window(aux, url, source, snapshot: blob_snapshot)) # A form submission carries a referrer (the opener's URL) unless the # form opted out via rel=noreferrer (referrer: ''). # The OPENER's document is the navigation initiator — it seeds the popup # load's Sec-Fetch-Site (the SameSite cookie gate reads it). # The OPENER's document is the navigation initiator — it seeds the popup # load's Sec-Fetch-Site (the SameSite cookie gate reads it). raw_current_url: # the ticking `current_url` must not run re-entrantly inside window.open. aux.visit(url, referer: referrer, initiator: source&.raw_current_url) end end handle rescue StandardError => e # Aux window URL-load failure (binary content, network error, …) # shouldn't tear down the test — the handle is already recorded so # `window_opened_by` succeeds; within_window assertions on # `current_url` may still pass through whatever `visit` managed to set # before raising. warn "[csim] open_aux_window(#{url.inspect}) raised: #{e.class}: #{e.[0, 200]}" handle end |
#open_new_window(_kind = :tab) ⇒ Object
Capybara Session#open_new_window(:tab) entry point — opens at
about:blank (so current_url/title match a real new tab) and the
test then switch_to_window + visits the real URL. We don't
distinguish :tab from :window (no window-chrome semantics here).
729 730 731 |
# File 'lib/capybara/simulated/driver.rb', line 729 def open_new_window(_kind = :tab) open_aux_window('about:blank') end |
#open_window_from_js(opener_browser, url, name, opener_realm_id = 0, about_base = nil, about_origin = nil) ⇒ Object
window.open(url, name) from the opener window's JS. Resolves the URL
against the opener's document and records the opener relationship.
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'lib/capybara/simulated/driver.rb', line 517 def open_window_from_js(opener_browser, url, name, opener_realm_id = 0, about_base = nil, about_origin = nil) resolved = url.to_s.empty? ? nil : opener_browser.resolve_document_url(url) # Opening a blob: URL whose storage partition differs from the opener's # top-level site is forced NOOPENER (cross-partition-navigation): the new # auxiliary window is its own top-level context in the blob's partition, so the # blob still loads — but there is no opener relationship and `window.open` # returns null. Same-partition keeps the normal opener. if resolved.to_s.start_with?('blob:') && cross_partition_blob?(resolved, opener_browser) open_aux_window(resolved, name: name, source: opener_browser) # no opener_handle ⇒ window.opener null return nil # window.open(...) === null end # Same-origin window → a realm in the opener's isolate (shared heap); the # returned realm-id context becomes a native WindowProxy on the JS side, so # cross-window scripting/adoption need no cross-isolate RPC. The opener's realm # id wires the popup's window.opener. Falls through to the separate-VM aux path # (cross-origin, or a URL we don't yet realm-load). if (rid = opener_browser.open_window_realm(resolved, name: name, opener_realm_id: opener_realm_id, about_base: about_base, about_origin: about_origin)) return rid end open_aux_window(resolved, name: name, opener_handle: handle_for(opener_browser), source: opener_browser, defer_load: true) end |
#opener_handle_of(browser) ⇒ Object
695 696 697 698 |
# File 'lib/capybara/simulated/driver.rb', line 695 def opener_handle_of(browser) handle = handle_for(browser) window_entries.find {|w| w[:handle] == handle }&.fetch(:opener) end |
#peek_script(expr) ⇒ Object
Clock-free read of a JS expression in the active browsing context (no virtual-time advance, unlike evaluate_script) — for polling page state between event-loop frames without perturbing the clock.
319 |
# File 'lib/capybara/simulated/driver.rb', line 319 def peek_script(expr) = current_browser.peek_script(expr) |
#refresh ⇒ Object
322 |
# File 'lib/capybara/simulated/driver.rb', line 322 def refresh = current_browser.refresh |
#register_blob_partition(url, browser, site) ⇒ Object
Record / drop a blob URL's storage partition (called by Browser#blob_register /
#blob_unregister). site is the creating context's top-level site.
569 570 571 |
# File 'lib/capybara/simulated/driver.rb', line 569 def register_blob_partition(url, browser, site) @blob_partitions_lock.synchronize { @blob_partitions[url.to_s] = {browser: browser, site: site.to_s} } end |
#reset! ⇒ Object
323 324 325 326 |
# File 'lib/capybara/simulated/driver.rb', line 323 def reset! reset_windows! browser.reset! end |
#reset_history! ⇒ Object
390 |
# File 'lib/capybara/simulated/driver.rb', line 390 def reset_history! = current_browser.reset_history! |
#reset_windows! ⇒ Object
Dispose every auxiliary window and return focus to the primary — a fresh
browsing context has no sibling windows. Disposing each aux Browser tears
down its worker / SSE / websocket threads and its V8 isolate eagerly; left
alone they pile into V8Runtime's process-wide @@live set and are only
reclaimed by the at_exit hook, which on a long-lived multi-file session
(the WPT runner) means a slow — sometimes minutes-long — process exit. Split
out of reset! so a caller can drop windows WITHOUT resetting the primary's
page state (the WPT runner rebuilds the primary itself, per file, via visit).
357 358 359 360 361 362 363 364 |
# File 'lib/capybara/simulated/driver.rb', line 357 def reset_windows! @aux_windows.each {|w| w[:browser].dispose rescue nil } @aux_windows.clear @sw_parked.each {|b| b.dispose rescue nil } @sw_parked.clear @active_handle = nil @blob_partitions_lock.synchronize { @blob_partitions.clear } end |
#resize(w, h) ⇒ Object
Forem's ahoy-tracking spec calls driver.resize(w, h) directly
rather than through current_window.resize_to.
828 829 830 831 832 |
# File 'lib/capybara/simulated/driver.rb', line 828 def resize(w, h) = current_browser.(w, h) # Both restore the window to the display it lives on (`Browser#screen_size`), which is where # it started — so they undo a `resize_to` rather than doing nothing. Coarse: we model no # window chrome, so a maximized window and a fullscreen one end up the same size (a real # browser's fullscreen is taller by the chrome it hides). |
#resize_window_to(handle, w, h) ⇒ Object
825 826 827 |
# File 'lib/capybara/simulated/driver.rb', line 825 def resize_window_to(handle, w, h) = window_browser!(handle).(w, h) # Forem's ahoy-tracking spec calls `driver.resize(w, h)` directly # rather than through `current_window.resize_to`. |
#response_headers ⇒ Object
395 |
# File 'lib/capybara/simulated/driver.rb', line 395 def response_headers = current_browser.response_headers |
#revoke_blob_partitioned(url, source) ⇒ Object
A user URL.revokeObjectURL(url) from source. Storage-partitioned: a revoke
from a different top-level site than the blob's is a NO-OP (cross-partition.https
"shouldn't be revocable from a cross-partition iframe/worker"). A same-partition
revoke drops the Driver entry AND invalidates the blob in the CREATOR's isolate
(the blob may have been created in another window), so every window stops
resolving it. Returns false when vetoed (caller leaves its local copy intact).
583 584 585 586 587 588 589 590 |
# File 'lib/capybara/simulated/driver.rb', line 583 def revoke_blob_partitioned(url, source) entry = @blob_partitions_lock.synchronize { @blob_partitions[url.to_s] } return false if entry && entry[:site] != source.blob_partition_site @blob_partitions_lock.synchronize { @blob_partitions.delete(url.to_s) } creator = entry && entry[:browser] creator.drop_local_blob(url.to_s) if creator && creator.respond_to?(:drop_local_blob) && !creator.equal?(source) true end |
#run_event_loop_frame(frame_ms) ⇒ Object
Run one real-cadence event-loop frame and return the loop's observable
state. Drives "advance the page one frame" without the full poll tick
evaluate_script would incur per read; the wpt_runner uses it to drain a
page to completion at browser cadence.
EVERY live window steps, not just the active one: an auxiliary window is a separate VM, but the cross-context orchestration the dispatcher framework builds on (a popup running an executor that polls a shared queue while the opener waits) needs those background windows to make progress autonomously. The active window's state leads; each aux window folds its progress / raf / async / nearest-timer in so the caller keeps pumping while any window works.
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/capybara/simulated/driver.rb', line 267 def run_event_loop_frame(frame_ms) state = current_browser.run_event_loop_frame(frame_ms) # Iterate a SNAPSHOT: a window's drain can open or close windows mid-loop (a # popup spawning another, or window.close disposing one). Re-check each window # is still open before stepping it, and isolate a per-window failure so one # dead/half-torn-down VM can't abort the whole frame pump. @aux_windows.dup.each do |w| b = w[:browser] next if b.equal?(current_browser) next unless @aux_windows.include?(w) # closed earlier this loop → skip begin state = merge_frame_state(state, b.run_event_loop_frame(frame_ms)) rescue StandardError next end end state end |
#save_screenshot(path, full: false, **_opts) ⇒ Object
A real raster of the laid-out page (see js/src/paint.js), not a serialization of it: the
painter reads the same boxes every geometry query reads, so a screenshot can only show
what the driver already believes. full: true paints the whole document rather than the
viewport.
877 878 879 880 881 882 883 |
# File 'lib/capybara/simulated/driver.rb', line 877 def save_screenshot(path, full: false, **_opts) data = current_browser.screenshot_png(full: full) raise Capybara::Simulated::ScreenshotFailed, 'screenshot: the page painted nothing' if data.nil? File.binwrite(path, data) path end |
#send_keys(*keys) ⇒ Object
899 900 901 902 903 904 905 906 907 908 909 910 911 |
# File 'lib/capybara/simulated/driver.rb', line 899 def send_keys(*keys) # Selenium contract: top-level modifier symbols (`send_keys( # :shift, :enter)`) press the modifier *and hold it* over the # following key, releasing at the end of the call. Nested # arrays (`send_keys([:control, "/"])`) are chords — modifiers # combined with the final key in one press. Pass the whole # batch to `Browser#send_session_keys` in one call so the # JS-side handler can build a `combo` atom from the held # modifiers + the next key. Iterating per-key would split the # chord across calls and drop the modifier flags. current_browser.send_session_keys(keys) nil end |
#set_geolocation(latitude: nil, longitude: nil, accuracy: 10, denied: false, **rest) ⇒ Object
CDP-ish geolocation override (Capybara driver-level API).
page.driver.set_geolocation(latitude: 35.6, longitude: 139.7)
page.driver.set_geolocation(denied: true) # PERMISSION_DENIED
page.driver.set_geolocation # clear -> POSITION_UNAVAILABLE
895 896 897 |
# File 'lib/capybara/simulated/driver.rb', line 895 def set_geolocation(latitude: nil, longitude: nil, accuracy: 10, denied: false, **rest) current_browser.set_geolocation(latitude: latitude, longitude: longitude, accuracy: accuracy, denied: denied, **rest) end |
#start_tracing(**metadata) ⇒ Object
Per-test trace recording. Mirrors capybara-playwright-driver's
start_tracing / stop_tracing shape so suites can swap
drivers without rewriting hooks.
138 |
# File 'lib/capybara/simulated/driver.rb', line 138 def start_tracing(**) = browser.start_trace() |
#status_code ⇒ Object
394 |
# File 'lib/capybara/simulated/driver.rb', line 394 def status_code = current_browser.status_code |
#stop_tracing(path: nil) ⇒ Object
140 141 142 143 144 145 |
# File 'lib/capybara/simulated/driver.rb', line 140 def stop_tracing(path: nil) active = current_trace or return nil result = path ? browser.finish_trace_to(path, active) : active browser.clear_trace! result end |
#storage_broadcast(source_browser, kind, key, old, new, url) ⇒ Object
A localStorage change fans out to every OTHER window (localStorage spans same-origin
browsing contexts). Every window shares the Driver's one @local_storage jar, so all
windows are same-origin peers here (cross-origin storage partitioning is a separate
backlog item); a nil source realm reaches each target's every realm. sessionStorage is
per-context and never reaches this path.
641 642 643 644 645 646 |
# File 'lib/capybara/simulated/driver.rb', line 641 def storage_broadcast(source_browser, kind, key, old, new, url) window_entries.each do |w| next if w[:browser].equal?(source_browser) w[:browser].enqueue_storage_event(kind, key, old, new, url, nil) end end |
#sw_navigation_fetch(url, **kw) ⇒ Object
The fetch-event round-trip for a controlled NAVIGATION, resolved across the whole window set: a service-worker registration is profile-wide, so the Browser hosting the controlling SW may be any window's — or a parked one whose window already closed (searched newest-parked first: a re-registered scope's newest worker is the active registration). Returns the owner's respondWith wire hash, or nil (uncontrolled → load from the network).
799 800 801 802 803 804 805 806 807 808 809 810 811 |
# File 'lib/capybara/simulated/driver.rb', line 799 def (url, **kw) sweep_dead_parked owner = ([@browser] + @aux_windows.map {|w| w[:browser] } + @sw_parked.reverse).find {|b| b.(url) } return nil unless owner resp = owner.(url, **kw) # Nobody pumps a parked browser — drop what the handler queued for its dead # clients so the outbox can't grow across a long test. owner. if @sw_parked.include?(owner) resp end |
#switch_to_frame(frame) ⇒ Object
Capybara within_frame / switch_to_frame. frame is the iframe
Capybara::Node::Element (its .native is our driver Node), or the
:parent / :top symbols. The block's finds + actions then route into
the frame's own V8 realm via the Browser's @current_realm_id.
410 411 412 413 |
# File 'lib/capybara/simulated/driver.rb', line 410 def switch_to_frame(frame) target = frame.is_a?(Symbol) ? frame : frame.native.handle_id current_browser.switch_to_frame(target) end |
#switch_to_window(h) ⇒ Object
821 822 823 824 |
# File 'lib/capybara/simulated/driver.rb', line 821 def switch_to_window(h) window_browser!(h) # unknown / already-closed handle → WindowError @active_handle = (h == PRIMARY_HANDLE ? nil : h) end |
#title ⇒ Object
393 |
# File 'lib/capybara/simulated/driver.rb', line 393 def title = current_browser.title |
#trace_screenshot ⇒ Object
The ACTIVE window's page, painted for the trace's final state (TracePersistence) —
current_browser, like every other user-facing read here, not the primary browser: a
test that ended inside switch_to_window would otherwise be handed a picture of the
window it was not looking at.
154 |
# File 'lib/capybara/simulated/driver.rb', line 154 def trace_screenshot = current_browser.trace_screenshot |
#tracing? ⇒ Boolean
156 |
# File 'lib/capybara/simulated/driver.rb', line 156 def tracing? = !current_trace.nil? |
#unregister_blob_partition(url) ⇒ Object
573 574 575 |
# File 'lib/capybara/simulated/driver.rb', line 573 def unregister_blob_partition(url) @blob_partitions_lock.synchronize { @blob_partitions.delete(url.to_s) } end |
#visit(path) ⇒ Object
321 |
# File 'lib/capybara/simulated/driver.rb', line 321 def visit(path) = current_browser.visit(path) |
#wait? ⇒ Boolean
Dynamic wait?: only poll when there's pending timer work that
real-time advancement could resolve. With no timers queued,
polling can't change anything, so we fail fast via the
wait? = false synchronize path.
254 |
# File 'lib/capybara/simulated/driver.rb', line 254 def wait? = current_browser.polling? |
#window_browser(handle) ⇒ Object
The Browser backing a handle, or nil if the window is closed/unknown.
433 434 435 |
# File 'lib/capybara/simulated/driver.rb', line 433 def window_browser(handle) window_entries.find {|w| w[:handle] == handle }&.fetch(:browser) end |
#window_browser!(handle) ⇒ Object
Same, but for the operations that ADDRESS a window rather than probe for one: a closed or
unknown handle is Capybara's WindowError, never a silent fall back to the current window.
439 440 441 |
# File 'lib/capybara/simulated/driver.rb', line 439 def window_browser!(handle) window_browser(handle) or raise Capybara::WindowError, "Unknown window handle: #{handle}" end |
#window_closed?(handle) ⇒ Boolean
694 |
# File 'lib/capybara/simulated/driver.rb', line 694 def window_closed?(handle) = window_browser(handle).nil? |
#window_handles ⇒ Object
423 424 425 |
# File 'lib/capybara/simulated/driver.rb', line 423 def window_handles [PRIMARY_HANDLE] + @aux_windows.map {|w| w[:handle] } end |
#window_history_go(handle, delta) ⇒ Object
A cross-window w.history.back()/forward()/go(n): traverse the target
window's history. The opener's VM is the one executing, so a non-active
target can rebuild eagerly (like navigate_window); an active target
(e.g. opener.history.back() from an aux) defers to avoid tearing down
the running VM mid-call. Returns true when the traversal crossed a
document boundary — the JS proxy then fires the target's deferred load
(the same deferral as navAux) so the restored page's window.onload
runs after the opener's current task. False for a same-document
(pushState) traversal — popstate already fired — or a no-op.
681 682 683 684 685 686 687 688 689 690 691 692 693 |
# File 'lib/capybara/simulated/driver.rb', line 681 def window_history_go(handle, delta) b = window_browser(handle) or return false if b.equal?(current_browser) # `opener.history.back()` targeting the active window: defer (can't # rebuild the running VM mid-call) and return false — the active # window's load fires through its own navigation path when the pending # traversal drains, NOT via the aux-load deferral the caller would run. b.history_go(delta) false else b.history_go(delta, force: true) == :cross_document end end |
#window_location(handle) ⇒ Object
raw_: an identity read — this runs inside host-fn callbacks (a popup's boot script reading opener.location), where the ticking current_url must not re-enter.
650 651 652 |
# File 'lib/capybara/simulated/driver.rb', line 650 def window_location(handle) = (window_browser(handle)&.raw_current_url).to_s # A cross-window property read (`win.foo` / `win.document.foo`) — read the # primitive off the target window's VM. |
#window_post_message(source_browser, target_handle, data, target_origin, sender_origin) ⇒ Object
targetWindow.postMessage(data, targetOrigin) — queue on the target window's
Browser, tagged with the source window's handle. The targetOrigin travels with
the message and gates delivery in the target VM (where its current origin is
known); the sender's origin becomes the delivered event.origin.
614 615 616 617 |
# File 'lib/capybara/simulated/driver.rb', line 614 def (source_browser, target_handle, data, target_origin, sender_origin) target = window_browser(target_handle) or return target.(data, target_origin, sender_origin, handle_for(source_browser)) end |
#window_read(handle, prop, doc: false) ⇒ Object
A cross-window property read (win.foo / win.document.foo) — read the
primitive off the target window's VM.
653 654 655 656 |
# File 'lib/capybara/simulated/driver.rb', line 653 def window_read(handle, prop, doc: false) b = window_browser(handle) or return nil b.read_property(prop, doc: doc) end |
#window_ref_call(handle, id, method, args) ⇒ Object
662 |
# File 'lib/capybara/simulated/driver.rb', line 662 def window_ref_call(handle, id, method, args) = (b = window_browser(handle)) ? b.remote_ref_call(id, method, args) : nil |
#window_ref_get(handle, id, prop) ⇒ Object
660 |
# File 'lib/capybara/simulated/driver.rb', line 660 def window_ref_get(handle, id, prop) = (b = window_browser(handle)) ? b.remote_ref_get(id, prop) : nil |
#window_ref_set(handle, id, prop, value) ⇒ Object
661 |
# File 'lib/capybara/simulated/driver.rb', line 661 def window_ref_set(handle, id, prop, value) = ((b = window_browser(handle)) && b.remote_ref_set(id, prop, value)) |
#window_set_location(handle, url) ⇒ Object
663 664 665 666 667 668 669 670 671 |
# File 'lib/capybara/simulated/driver.rb', line 663 def window_set_location(handle, url) b = window_browser(handle) or return # Per HTML, `w.location = url` parses `url` relative to the ENTRY settings # object — the document of the script doing the assignment (the active # window) — NOT the target window's current document. So a cross-window # `w.location.href = 'resources/x.html'` resolves against the opener's # base, not the aux's (which would double a shared path segment). navigate_window(b, current_browser.resolve_document_url(url), source: current_browser) end |
#window_size(handle) ⇒ Object
Every window has its own viewport, so these address the window the
handle names — not the active one. Capybara::Window#resize_to on a
background window must resize THAT window and leave the current one
(and Capybara's idea of which window is current) alone.
736 737 738 739 |
# File 'lib/capybara/simulated/driver.rb', line 736 def window_size(handle) b = window_browser!(handle) [b., b.] end |
#with_playwright_page {|FakePlaywrightPage.new(current_browser)| ... } ⇒ Object
Playwright-driver compatibility shim. Discourse's system-spec
before(:each) calls page.driver.with_playwright_page to
install a JS-console logger, apply a CDP setTimezoneOverride,
and (in dev_tools_spec) evaluate window.enableDevTools().
Yield a FakePlaywrightPage that delegates evaluate(js) to
our JS engine and silently no-ops every other Playwright-only
method via method_missing → self. Chained accessors like
pw.context.new_cdp_session(pw).send_message("…") therefore
propagate as a no-op rather than NoMethodError, while
pw.evaluate("…") runs the JS where it matters.
184 185 186 |
# File 'lib/capybara/simulated/driver.rb', line 184 def with_playwright_page yield FakePlaywrightPage.new(current_browser) if block_given? end |
#worker_drive_pending? ⇒ Boolean
Worker cross-thread work in flight in ANY window — the same aggregate scope
as run_event_loop_frame's merged async (which folds every window in), so a
caller distinguishing "waiting on a worker thread" from other async channels
(the wpt_runner's clock-hold) sees a popup-hosted worker/SW round trip too,
not just the active window's.
291 292 293 294 |
# File 'lib/capybara/simulated/driver.rb', line 291 def worker_drive_pending? return true if current_browser.worker_drive_pending? @aux_windows.any? {|w| !w[:browser].equal?(current_browser) && w[:browser].worker_drive_pending? } end |