moli-cdp-smoke
This is a standalone CDP smoke project for moli. The default suite uses Playwright and raw CDP because those dependencies are managed by uv; the Puppeteer group uses the pinned puppeteer-core development dependency in this directory. Optional agent-browser, chrome-remote-interface, cdp-use, and Stagehand groups exercise their real published clients when the corresponding local dependencies are installed. High-value protocol contracts should also be mirrored as focused Rust tests near their owning moli-protocol boundary so they run under cargo nextest.
The suite covers real CDP-client workflows: connect_over_cdp, concurrent browser and direct-page clients, multiple pages and contexts, navigation, history, Page.setDocumentContent, popup target creation, JavaScript dialog events, Network/Fetch routing, workers, WebSocket, file upload, downloads, locator/input flows, DOM/handle flows, Chromium-derived CDP protocol samples, Playwright-upstream-derived route/CDPSession samples, screenshots, Page.printToPDF, viewport, and storage/profile behavior. It is not a replacement for unit tests. Its job is to answer whether moli is usable as a Playwright/Puppeteer-style CDP endpoint.
Chromium Behavior Evidence
Any smoke assertion described as Chromium-compatible must be verified against a real Chromium binary. Do not infer observable behavior solely from the specification, Chromium source code, an existing Moli test, or intuition.
Before changing a Chromium-derived expectation:
- Run a minimal CDP or Playwright probe against a local Chromium build, normally
<chromium-src>/out/Default/chrome. - Record the Chromium revision or executable, the exact probe, the observed event/result order, and the repetition count when timing is involved.
- Run the equivalent probe against Moli.
- Update the smoke assertion only after the two observations explain the intended compatibility boundary.
Source inspection remains useful for explaining why Chromium behaves as observed, but it does not replace the executable probe when one can be run. If Chromium cannot be tested, document that limitation explicitly instead of presenting an inferred expectation as verified behavior.
The history-worlds group was calibrated on 2026-09-25 against macOS Google
Chrome 154.0.8037.57 using a fresh headless profile. It uses a real CDP WebSocket
and Page.createIsolatedWorld to cover the PR #818 regressions: entry wrapper
identity and expando/method isolation, one currententrychange in each world,
isolated navigate cancellation, and the realm of History SecurityErrors.
It also verifies that a later main-world mutation updates the isolated world's
structured state and preserves its local state cache. Event checks cover local
AbortSignal, FormData, File, and DOM wrappers; live FormData mutations; and
synthetic Event/CustomEvent identity, interface type, and shared cancellation.
Abort delivery for a synthetic NavigateEvent is covered by renderer Rust tests:
Chrome 154 crashes when aborting the signal passed to that synthetic event.
Run it with
uv run moli-cdp-smoke --group history-worlds, or add --endpoint URL to use an
existing browser process.
The detailed emulation-storage locale/timezone matrix was calibrated on
2026-09-01 against Debian Chromium 145.0.7632.116. In addition to resolved
locale, timezone, local getters, and formatted strings, the executable probe
now covers Intl and Date subclass newTarget, constructor reflection, options
accessor count/receiver identity, winter/summer and DST-gap Date construction,
local versus date-only/offset parsing, local setters with millisecond
preservation, and explicit locale/timezone precedence. The invalid-zone probe
is deliberately performed before installing a valid override: Chromium 145
returns InvalidParams on initial admission, while its active override
change() path silently retains the old zone for an invalid replacement.
Moli additionally locks down the latter state-preservation case in a focused
Rust protocol test.
The default locale-timezone-inputs group adds input-boundary regressions for
PR #369. It was calibrated on 2026-09-10 against /usr/bin/chromium
145.0.7632.116 using the group unchanged: four timezone overrides cover Unicode
and lone-surrogate rejection, explicit legacy timezone tokens, ISO date-only
versus local parsing, weekday names and ignored comments, and frozen Intl
options. Option getter order and receiver identity are compared with the
same engine before emulation. Seven ICU locale IDs cover underscore/script
forms, POSIX default normalization, and calendar/collation/numbering keywords;
explicit page locales still use native BCP47 validation. Clearing overrides
must restore the original defaults. The group has its own Moli process because
the Unicode regression previously panicked in a native callback.
For example, Chromium resolves Playwright
page.goto(..., wait_until="load") after Page.loadEventFired but before the
later Page.frameStoppedLoading delivery. A smoke scenario that starts a new
event trace after goto() must therefore synchronize with the exact
frameStartedLoading / frameStoppedLoading pair first; treating the goto()
return as that synchronization point is incorrect.
The url-policy XHR expectations were calibrated on 2026-08-08 with Debian's
/usr/bin/chromium 145.0.7632.116 and raw CDP Network/Fetch observation. The
system binary produced the same synchronous NetworkError, DONE, reset
response fields, no progress events, and request-to-loadingFailed terminal
pair. With Fetch interception enabled, Chromium pauses a blocked-port request
before net::ERR_UNSAFE_PORT; Moli intentionally rejects that request at
its earlier admission boundary, so the smoke records no pause as a hosted
security contract rather than claiming identical interception placement. The
local ~/chromium/src/out/Default/chrome 147 binary remains unusable for this
probe because its V8 snapshot does not match the executable.
The default raw multi-client group was calibrated on 2026-08-14 against
Debian /usr/bin/chromium 145.0.7632.116 and then run unchanged against Moli.
The group covers 2-, 3-, and 7-client browser and direct-page WebSocket fan-out
against one target. Every connection can reuse the same command ids without
response crossover and observe the same target runtime through distinct
sessions. For each connection, Target.attachedToTarget precedes its attach
response and four-command bursts preserve both response order and runtime
side-effect order; ordering between different WebSockets is intentionally
unconstrained. Target discovery enabled on alternating browser connections does
not leak to their peers, and subscribers observe Target.targetCreated before
their attach event and response. Chromium rejects a foreign flattened session with
-32001 and a foreign
Target.detachFromTarget session reference with -32602. Closing either kind
of peer connection leaves the other client's root and target sessions usable.
The final matrix passed five consecutive runs against one Chromium process and
ten runs against freshly started Moli processes.
The default xhr-sync-semantics group was calibrated on 2026-08-09 against the
same executable Chromium and then run unchanged against Moli. It ports 14
Chromium-vendored XHR WPT contracts into one public-process matrix: nine success
variants, ten network/redirect failures, ten document responseType cases,
timeout restrictions, repeat-send and reopen/reset behavior, pending-async
cancellation, main-thread blocking, progress totals, upload-event silence, and
the CDP Network success/failure terminal skeleton. The local Chromium source
checkout remains useful for locating the WPT assets but is not counted as an
executable pass while its snapshot and binary are mismatched.
The Puppeteer existing-Page reconnect contract was calibrated on 2026-08-19
with the pinned puppeteer-core 24.30.0 against Debian /usr/bin/chromium
145.0.7632.116. One complete Puppeteer-group run created a second Page through
PUT /json/new, evaluated in the original Page through one puppeteer.connect()
session, disconnected that session, and evaluated the same target and retained
main-world marker through a replacement connection. Chromium returned 42 in
both sessions. The identical probe timed out at the replacement evaluation after
10,000 ms when Moli's parent-session renderer-inspector detach fix was removed,
and passed after the fix was restored.
The navigation-outcomes group was calibrated on 2026-08-23 against Debian
/usr/bin/chromium 145.0.7632.116 and then run unchanged against Moli. It
directly drives Page.navigate and correlates the result with the matching
Document request/response identity. The matrix covers text, renderable HTML,
empty, redirected, and HTTP-error attachments; unsupported binary MIME; an
ordinary HTML 502 document; 204 No Content; and a reset before response
headers. Chromium reports successful attachments as isDownload=true plus
net::ERR_ABORTED while retaining the old Document, but a 204 reports the same
error with isDownload=false; the smoke therefore treats isDownload, not the
error string, as authoritative. Moli currently commits binary responses and
204 responses as external Documents where Chromium retains the old Document;
those two cases assert internally coherent response/lifecycle evidence and
record the engine-selected outcome rather than claiming parity. For a 404
attachment, both engines preserve isDownload and the HTTP response evidence,
while Chromium commits an error Document with net::ERR_INVALID_RESPONSE and
Moli retains the prior Document with net::ERR_ABORTED; that difference is
also recorded rather than hidden behind an exact error-string assertion.
The expanded target-semantics group was calibrated on 2026-08-26 against
Debian /usr/bin/chromium 145.0.7632.116 and then run unchanged against Moli.
In addition to target creation, attachment, close, and browser-context
isolation, its raw WebSocket contracts verify Chromium's TargetHandler access
modes, the browser-level flattened-auto-attach requirement, repeated Tab
auto-attach reconciliation without duplicate sessions, stable Tab-to-Page
ownership across foreground changes, Target.createTarget foreground and
background behavior, Page visibility changes, screencast visibility
true -> false -> true across foreground changes and fallback selection after
close, and a real
default discovery target that is not recreated after close. It also drives a
real middle-button press/release through Input.dispatchMouseEvent and checks
that the trusted auxclick anchor default action leaves the source visible and
creates a hidden background Page. The original 12-contract matrix passed three
consecutive fresh Chromium processes and five consecutive fresh Moli processes;
the middle-click contract was then run unchanged against both engines.
Chromium source revision a03603fe9af6 was also inspected to explain the
access-mode and auto-attach machinery; the executable probe remains the
behavioral authority.
The expanded multi-page group was calibrated again on 2026-08-31 against
Debian /usr/bin/chromium 145.0.7632.116 and local Chromium source revision
a03603fe9af6. Five new executable contracts cover target-local Debugger
pause/resume, same-origin localStorage fan-out versus target-local
sessionStorage, target-owned navigation-history entry ids, attachment-ordered
union and detach cleanup for Network.setBlockedURLs, and OR aggregation for
Network.setCacheDisabled. The cache probe also records Chromium's two cache
layers: a bypass load replaces the owning Page's retained resource and the
shared HTTP-cache entry, an already-live peer Page retains its earlier
resource, and a newly created Page sees the replacement. The exact same full
group passed three consecutive fresh Chromium profiles and ten fresh Moli
server processes, with the Moli runs scheduled four at a time to expose
cross-target ownership races. This calibration also corrected two older
assumptions: wire and live UA overrides both follow session attachment order,
and an activation-churn preload probe must enable the Page agent before
registering its new-Document script.
Every multi-page case runs even when an earlier case fails, so one race does
not hide later failures. For focused reproduction, set
MOLI_MULTI_PAGE_CASES to a comma-separated list of case names. Parallel
workers can partition that selected list with the one-based
MOLI_MULTI_PAGE_SHARD=INDEX/COUNT setting.
Current Coverage
The current suite is a strong core smoke gate, not a complete Playwright compatibility suite.
The dom-input keypress contract was calibrated on 2026-09-08 against
headed Debian Chromium 145.0.7632.116 through CDP. It checks the full trusted
keydown -> keypress -> beforeinput -> input -> keyup sequence for Playwright
typing, cancellation at keypress, the distinct rawKeyDown and char paths,
and the absence of keyboard events for Input.insertText.
The default webgl-viewport group was calibrated on 2026-09-07 against
Debian /usr/bin/chromium 145.0.7632.116. It uses the same fixture as the
renderer tests to check WebGL1/2 on HTML and
Offscreen canvases: viewport initialization, setter/conversion errors, copied
Int32Array queries, context isolation, context reacquisition, resize retention,
and clamping to the advertised maximum. It is not a GPU rendering test.
The default svg-rect group checks the detached SVGRect interface used by
SVG capability detection, sharing the renderer fixture for prototype, identity,
and restricted-float conversion contracts. It was calibrated on 2026-09-07
against Debian /usr/bin/chromium 145.0.7632.116 and runs independently of
IndexedDB startup coverage.
The default target-lifecycle process group locks down Moli's resource lifetime,
not a Chromium-specific FD count. In one server it closes 800 default-context
targets with Target.closeTarget, 800 with Page.close, 128 after detaching,
and 64 by disposing explicit contexts. Foreground/background creation alternates.
It waits for each exact Target.targetDestroyed, records Linux /proc/<pid>/fd
and thread counts every batch, and checks a fixed post-warmup resource budget.
Each phase must still load a real HTTP document and preserve a live peer Page.
This catches closed renderer wakers retaining Tokio I/O drivers without changing
the test to use a fresh context for each default-context Page. Batch progress,
resource samples, and the usual server logs are retained on failure. With an
external endpoint or on non-Linux systems, protocol churn/navigation still run;
the artifact explicitly reports that FD sampling was unavailable. CI uses the
managed Linux server, so the resource assertions are mandatory there.
Covered well:
- The default raw
debugger-breakpoints,runtime-exception, andfile-choosergroups preserve focused Lexbench regressions at the public process boundary, including multi-attachment Runtime exception contracts. They dispatchDebugger.getPossibleBreakpoints,setBreakpoint,removeBreakpoint, andsetBreakpointByUrlwhile the Page is normally running, require an uncaught timer error to publishRuntime.exceptionThrownwithout a follow-up command, verify that each Runtime-enabled attachment receives the target-owned exception while a disabled peer does not, keepRuntime.enablefrom making Error stack cost track JavaScript stack depth, and require a user-gesture file-input activation to publish the session-scopedPage.fileChooserOpenedevent. - The default raw
url-policygroup holds the hosted local-file boundary at the public process edge. It requires an exact session-routedPage.navigate-32000error with no lifecycle or document replacement, verifies pagefetch()plus asynchronous/synchronous XHR error surfaces, and requires each script request to terminate asNetwork.requestWillBeSentfollowed byNetwork.loadingFailedwithoutFetch.requestPaused, a response, or transport completion. The same group exercises non-URL-policy synchronous XHR with a blocked port, including the Chromium-shapedNetworkError,DONE, reset response fields, and absence of XHR/upload progress events. - The default
xhr-sync-semanticsgroup runs unchanged against Chromium and Moli. It covers Content-Length/no-length/204/data responses, POST with and without a body, GET/HEAD body suppression, redirects, connection reset, malformed data URLs, blocked ports, unsupported redirect schemes, 302/303 DNS failures, redirect loops, exact Window timeout/responseType exceptions,open()state reset, duplicatesend(), synchronous event order, pending asynchronous request cancellation, and suppression of asynchronous events and timers while a synchronous request owns the main thread. - The focused
layout-screenshotgroup drives a real raw WebSocket session through target creation/attachment, fixed viewport, lifecycle-gated navigation, DevTools-style PNG capture (quality: 100), paint/layout mutations, page clips,captureBeyondViewport, and the Chromium DevTools node-screenshot chain (DOM.getBoxModel+Page.getLayoutMetrics+ page clip). Its Moli-only TreeScope fixture also captures 104 open/closed Shadow Roots, nested roots, and 24 roots in an iframe twice, requiring stable pixels and completion within the protocol timeout. The group covers Moli's generation-gated 1 FPS JPEG screencast as well: initial-frame delivery, clean-state frame suppression without ACK backpressure, 400x300 scaling, metadata/session routing, mutation freshness, stop cleanup, and a separately restarted default-Mock boundary without--layout. The screenshot surface sequence can also run against Chromium as a coarse reference; the TreeScope, fixed-1-FPS, and default-Mock branches are Moli-only. - The default
layout-policygroup locks Moli's first-demand screen-layout lifecycle through raw CDP. Geometry reads and coordinate input initialize a cold Document once, then reuse its snapshot after DOM/CSS/viewport changes. Screenshots and actual screencast frames publish fresh screen layout; PDF uses temporary print layout without publishing. Plain DOM/Runtime reads, computed style (including used size and Grid), stylesheet reads/writes, snapshots, and emulation changes neither initialize nor refresh it. The matrix also checks live viewport/scroll versus frozen content size, missing boxes, invalid zero clips, focus/touch branches that need no layout, and four Document replacement paths. Each case uses a new target and mutates a known box before its assertion reads geometry, proving whether the command under test published layout without accidentally warming the page.Performance.getMetrics.LayoutCountis not used as a layout oracle. DOMSnapshot placeholder bounds are not asserted as a stable contract. This group tests Moli's policy and explicitly skips other browsers; it does not claim Chromium freezes layout after mutations. - The default
playwright-compatgroup takes viewport, clipped, full-page, and full-page clipped screenshots throughpage.screenshot()on fresh pages, without a geometry probe or raw CDP capture first. DOM geometry reads initialize Moli's first layout on demand; subsequent reads consume that published layout until a screenshot or new screencast frame refreshes it. The tall-page fixture checks the first full-page image's height and bottom pixels, then repeats the full-page capture. It setsscrollbar-width: noneexplicitly to match Playwright's Chromium--hide-scrollbarslaunch default. With visible native scrollbars, both Chromium and Moli produce a 305px-wide full-page capture for this narrow-content, 320px-viewport fixture. Empty clips remain invalid regardless of layout state. CLImoli fetch --layout --dump screenshot_fulldirectly requests full-document output and works on the first capture without a DOM-size preflight. The four first-capture modes were checked with the same test against/usr/bin/chromium145.0.7632.116 on 2026-09-27 (one run per mode). - The default raw
action-windowgroup holds Moli's on-demand input policy at the public CDP boundary. Three acknowledgedInput.dispatchMouseEventwheel commands remain delayed until one fixed one-second deadline, preserve event order, defer their microtask checkpoint, and publish one derived IntersectionObserver transition. Coordinate-targeted vertical and horizontal wheels must update an inneroverflow: autocontainer'sscrollTopandscrollLeftwithout moving the page.Page.captureScreenshotmust flush pending wheel work before paint and retire that window so later input receives a fresh deadline. A wheel handler that callsdocument.open()must also stop the remainder of its batch from entering the replacement Document. These are Moli scheduling contracts rather than Chromium-compatibility claims; the group records a non-applicable result when pointed at Chromium. - The focused
dom-hit-testgroup drivesDOM.getNodeForLocationthrough the real layout hit index and requires the same option-aware topmost node, backend node id, and frame id shape from Moli and Chromium. - Node creation stack capture retains at most the newest 1,024 traces per Inspector session; focused renderer tests cover FIFO eviction, shared payloads, document replacement, and session detach while the wire smoke retains enable/disable/session semantics and verifies that
document.open()preserves capture for replacement-document nodes. - Optional chrome-remote-interface 0.34.0 and cdp-use 1.4.5 coverage for verified browser/page sessions, multiple targets, local/session storage ownership, history traversal, child-frame isolated worlds, Fetch fulfillment with page-session event routing, complete Network terminals, and the declared position-click boundary.
- Optional Stagehand 3.7.0 deterministic coverage for explicit CDP binding, navigation/evaluate, locator fill, attached-state waiting, shadow piercing, multiple pages and storage ownership, history, frame registry/deep locators, Network extra headers plus page fetch, and the declared position-click boundary. LLM
act/extract/observeand a route API are not part of this group. - Optional agent-browser CLI coverage for explicit CDP binding identity, navigation/read/evaluate, fill and keyboard input, media override, Network route/request observation, tab lifecycle, trace/profiler transport, and the declared position-click boundary. The group uses a unique daemon namespace and an empty config so it cannot silently attach to or launch a different browser.
- CDP discovery and
chromium.connect_over_cdp(). - Concurrent 2-, 3-, and 7-client raw browser and direct-page WebSocket fan-out against the same target, including colliding per-client command ids, distinct session ids, alternating discovery subscriptions, attach-event-before-response ordering, four-command per-client FIFO bursts, shared target state without response/event crossover, foreign flattened and legacy session rejection, and staged peer-disconnect isolation.
- Raw CDP websocket command flow for
Runtime.evaluate(awaitPromise=true)resolving pagefetch(), timer-triggeredfetch(), and WebSocket echo work without any follow-up client command; emittedRuntime.executionContextCreated.uniqueIdround-tripping through DevTools-shapedRuntime.evaluateandRuntime.callFunctionOn; Chromium-calibrated pre-commit navigation suspension where DOM/Runtime/Debugger main-thread commands wait whilePerformance.getMetrics,Runtime.terminateExecution, and browser commands remain dispatchable;Debugger.pauseresponding beforeDebugger.paused, interrupting an in-flightRuntime.evaluate, and resuming that evaluation; commands queued behind a winningDebugger.resumecompleting through normal owner dispatch rather than synthetic cancellation; deterministic nested-functionDebugger.stepOutresponse/resumed/caller-pause ordering; browser-global Tracing ownership across independent browser/page WebSocket frontends, including exactly one response for a synchronously completed start and the stop-before-start-ackend response -> start error -> data -> completesequence; shared worker target discovery throughTarget.getTargets, worker-sessionRuntime.executionContextCreated/ console log replay, andProfiler.enable/Profiler.start/Profiler.stopthroughTarget.setAutoAttach; plus Chromium-calibrated DedicatedWorker target creation/update/attach ordering, exact worker-isolate Runtime/Console routing,Inspector.workerScriptLoaded, terminate, and owner-navigation cleanup. - The default raw
inspector-routinggroup is the executable DevToolsSession boundary matrix. It covers per-session Main/IO FIFO and exactly-once completion, IO preemption of non-yielding JavaScript, DedicatedWorker and SharedWorker interrupt overtaking plus FIFO recovery, all 13 methods in Chromium 147'sShouldSendOnIO, normal debugger-pause pumping of one mixed V8/Page/DOM Main receiver, instrumentation-pause IO-only behavior, navigation replacement, attached-session detach, BrowserContext teardown with interrupts in flight, andPage.crash. Every scenario runs in an isolated target and records its Chromium-derived contract. - The focused raw
agent-episodegroup copies the recorded RLRuntime.evaluate(awaitPromise=true)observe/fill/click path. It requires the action response before destructive cross-document realm events, observes only the replacement Document, and then proves that a transport reset commits a Runtime-usable error Document instead of returningPromise was collectedor permanentNoDocumentLoaded. Same-document fill state is asserted through live DOM text; sampledinnerTextis recorded but may remain stale until the next layout refresh by design. - The focused raw
dom-parser-mutationsgroup holds a parser-blocking head script after an early head-onlyDOM.getDocumentand requires Chromium's exact root-agent sequence: commitDOM.documentUpdated, parser-tail BODYDOM.childNodeInserted, DCLDOM.documentUpdated, thenPage.domContentEventFired. It also proves that the early frontend node id is stale after the DCL barrier and that a refreshed snapshot contains the complete BODY. - Optional Puppeteer over CDP group for
puppeteer.connect(), reconnecting an existing background Page and selecting it through a fresh browser session, browser-target and page-targetCDPSession,page.goto(),page.reload(), selector-backed DOM activation navigation, same-document hash and History API navigation,page.evaluate(fetch), keyboard input viapage.type(), CSS /$eval/ XPath element selection, and DOM interactions across text input, textarea, label/checkbox, radio, select, details/summary, disabled button, and form submission. It also coversElementHandle.boundingBox()/evaluate()/uploadFile(), DedicatedWorkerworkercreated/WebWorker.evaluate()/ exact-once worker-session console routing / explicit terminate / navigation-destroy lifecycle, current-viewportpage.screenshot({captureBeyondViewport:false}), alert and console events, browser-session download behavior/events/artifact with peer-session event isolation, request interceptionrespond()/continue(), page-scopedCDPSessionNetwork event observation, and layout-backedpage.click()dispatch. browser.new_context(),context.new_page(), multiple pages in one context, target switching, popup-scopedpage.route()plusevaluate(fetch), popup CDPSession response-stage body / stream / fulfill / fail flows, and held multi-context route / response-stage resume without cross-context Network event bleed.- Top-level navigation, redirect final URL/response, reload-like click navigation, and history back/forward.
- Reload, same-document hash navigation,
history.pushState()observation, Playwrightadd_init_script()page/context injection,page.exposeFunction()/page.exposeBinding()plus context-level exposed functions, and basicdomcontentloaded/load/networkidleload-state waits with a parser-discovered delayed image. - Basic popup target linkage through Playwright's
page.expect_popup(), anchortarget="_blank"popup activation, named popup target reuse, reserved-targetwindow.open(..., "_self")current-page navigation, basic alert dialog handling through Playwright'spage.expect_event("dialog")anddialog.accept(), and raw CDP prompt event/close shape. - Basic iframe frame-tree consumption through
Frame.text_content(). page.wait_for_function()string predicates, timer readiness, return JSHandle values, primitive/ElementHandle arguments, and timeout errors, plus Playwrightpage.wait_for_selector()/locator.wait_for()attached, visible, hidden, detached, and enabled-click auto-wait behavior.- Chromium inspector-protocol derived samples for
Page.domContentEventFiredbeforePage.loadEventFired,Page.frameStartedLoading/Page.frameStoppedLoading, non-emptyPage.frameAttached.parentFrameId,Page.getFrameTree, dynamic and nested child-frame event fan-out across attached sessions with session-local lifecycle/disable state,Page.navigatefragment navigation,Page.getAppManifestdefault/loading/parsing/error/redirect/dynamic-link contracts, successful-result caching and link invalidation, plus itsManifestNetwork request and terminal lifecycle,Page.getLayoutMetrics,Runtime.executionContextCreated,Runtime.evaluate(returnByValue)and exception details, session-localInput.setIgnoreInputEventsaggregation/navigation/detach behavior,Input.insertTextbypass, idleInput.cancelDragging,Audits.issueAddedQuirks/CSP shape, replay ordering, navigation storage reset and session-local enable/disable,Log.entryAddednetwork metadata, buffered replay ordering, session-local delivery cursors, target-sharedLog.clear, violations-report state and validation,IO.resolveBlobsession-local object resolution and reopenableblob:<uuid>streams, session-localPerformance.enable/disable, strict time-domain transitions, disabledPerformance.getMetrics, Moli'sEmulation.setCPUThrottlingRateunsupported-rate errors and neutral resets across pages,Profiler.start/Profiler.stopCPU profiles and continued recording after rejected CPU throttling, error contracts, attached CDP-session profiler isolation across navigation and detach/reattach,console.profile/console.profileEnd, precise coverage / best-effort coverage including not-started error, counter reset, and detailed block coverage,DOM.getAttributes,DOM.querySelector(All)including default-depth node-path publication through orderedDOM.setChildNodesevents, deep ancestry expansion, repeat suppression, and the chromedpNodeReadycontract, liveDOMDebugger.getEventListeners, session-owned event-listener breakpoint pause/re-pause/navigation/detach behavior, session-owned XHR/fetch breakpoint URL matching, synchronous pause data, navigation/child-frame/worker scope, multi-owner sequencing and detach behavior, live-node DOM mutation breakpoints for single-node and multi-childDocumentFragmentinsertion batches, connected-node removal/insertion phases with one pre-pauseDOM.childNodeRemoved, same-value attributes and node removal with owner/peer data, parser mutation no-pause behavior, unbound-node path ordering, disable/navigation cleanup, and theDOM.getNodeForLocationhit-test capability boundary. - Raw
Input.dispatchKeyEventandInput.dispatchMouseEventcommands whose DOM handlers initiate a top-level Page replacement; both command responses must remain successful and the replacement Page must immediately accept follow-up CDP work. Focused Rust coverage separately holds the renderer ACK so the cleanup branch itself is deterministic. - Chromium-calibrated
Tracingbrowser-global ownership, duplicate start and peer end errors, data-source start acknowledgement before theTracing.startresponse, exactly-once synchronous start responses, stop-before-ack response/error ordering, response-before-data ordering, cross-session clock markers, boundedReportEvents, JSONReturnAsStreamthroughIO.read, and owner-detach cleanup. The CPU-profiler configuration additionally requires real V8Profile/ProfileChunkevents, non-empty samples with alignedtimeDeltas, and named hot functions across navigation replacement, dedicated/shared worker teardown, and closed page targets. The same content contract is applied to the real agent-browser profiler artifact. Proto, gzip, Perfetto, system tracing, and periodic buffer reporting remain explicit unsupported boundaries rather than mock output. - Chromium-calibrated live DOM mutation mirroring and editing: shallow/deep
DOM.getDocumentprojections,DOM.characterDataModified,DOM.childNodeCountUpdated,DOM.childNodeInserted,DOM.childNodeRemoved, and event-before-response contracts forDOM.moveTo,DOM.setAttributesAsText,DOM.setNodeName,DOM.setNodeValue, andDOM.setOuterHTML, including same-value character-data writes, processing-instructionxmlrenaming, and returned frontend node identity. - Chromium-calibrated Inspector depth-boundary projection:
DOM.getDocumentandDOM.requestChildNodesstill publish a container's only text child at depth zero, including the common<title>Example Domain</title>shape, while containers with multiple children remain collapsed. - Chromium-calibrated Inspector DOM projection through the focused
dom-whitespacegroup. Its fixture preserves the indentation-heavywidget_plate/blog/blog_chunkshape ofldm0.top: defaultDOM.enableomits whitespace-only text children, whileincludeWhitespace=allexposes them and keepschildNodeCountaligned with the projected tree. It verifies first-enable locking and disable/reset, child-frame default/all projection, visibility-transition insert/remove versus character-data events, and independent-sessionpushNodesByBackendIdsToFrontend,requestNode,describeNode, and XPath search identity. CSS/manual UA-shadow search is option-gated while XPath remains document-scoped; search results stay0before document publication and reuse a positive ID afterwards. The same group proves that node creation stack capture is session-local, non-retroactive, preserved across disable, and applied to fragment-created nodes. - A focused
computed-stylegroup runs unchanged against Chromium and Moli. It checks JavaScript computed-style enumeration andCSS.getComputedStyleForNodebreadth, representative layout-independent values, custom properties, longhand-only unique names, repeated-read stability, and mutation freshness while recording both property counts as diagnostics. - Chromium-calibrated
DOM.getOuterHTML(includeShadowDOM=true)through the focuseddom-shadow-outer-htmlgroup: nodeId, backendNodeId, objectId, and a detached object return identical recursive author-shadow markup; omitted/false remain shadow-free; declarative and closed roots are included; user-agent roots and Moli-only shadow-template attributes are excluded; child-frame host/document references retain the option; and serialization emits no DOM mutation events. - Chromium-calibrated
Autofill.triggercard behavior on a detected payment form: live values withoutvalueattribute mutation,:autofillstate, preserved focus, trustedinput/changeordering, and successful no-op behavior for an ordinary unclassified field. - Playwright upstream derived samples for route request metadata,
page.route()precedence overcontext.route(), context fallback,times, fallback chaining, terminal fulfill/abort routing,route.fulfill()cookies/headers, successfulpage.pdf()stream transport, page/main-frame/browserCDPSession.send(), session Network event delivery, unknown command errors, and post-detach command rejection. - Request interception for main documents, page
fetch()/XMLHttpRequest, workerfetch()/XMLHttpRequest, pagefetch()response-stage pause / continue / fulfill / fail, page XHR response-stageFetch.getResponseBody, page fetch response-stageFetch.takeResponseBodyAsStream/IO.read, and basic page/worker fetch plus worker XHRFetch.authRequired/continueWithAuthflows.CancelAuthcoverage requires the challenged401response and body to remain observable, allows a configured response-stage pause, completes withNetwork.loadingFinishedrather thanNetwork.loadingFailed, and includes a top-level navigation driven by a separate Playwright CDP session. - Dedicated worker
postMessageround trips and same-URL/same-name SharedWorker port reuse, including worker-global identity and connection-count assertions. - Attached
CDPSessionobservation forNetwork.requestWillBeSent,Network.responseReceived,Network.loadingFinished, andNetwork.loadingFailed, including fetch/XHR POST body, request/response-header fidelity, and redirect-chain event ordering. - Chromium-derived
Network.requestWillBeSentExtraInfo/Network.responseReceivedExtraInfocorrelation for normal HTTP documents, no-cookie redirects, cache revalidation, ordinary pageFetch.continueRequest, page and worker Basic auth, and requests sent before the server resets the connection without response bytes, both on the initial request and after a completed redirect hop. Worker coverage includes Fetch and XHR, with XHR held through a response-stage pause; workerCancelAuthrequires one initial unauthenticated request ExtraInfo and one raw401response ExtraInfo. Coverage includes transport-generatedHost/Accept-Encoding, per-hop exchange counts and status sequence, path-mismatched cookies using the publicNotOnPathblocked reason, auth retries retaining the initial unauthenticated request headers,Fetch.authRequiredrounds correlated only byFetch.requestIdwithout a non-standardnetworkId, a raw304ExtraInfo response paired with the merged cached200response,redirectHasExtraInfo, andhasExtraInfowithout assuming an order CDP does not guarantee. Fetch interception also verifies Chromium's sharedXHRtype for page Fetch, XHR, and EventSource requests under each of theFetch,XHR, andEventSourcefilters, while Network events retain their distinct high-level types. Reset cases require the complete observed request/redirect ExtraInfo set, no final response event, and a successfulPage.navigateresult carryingnet::ERR_CONNECTION_RESETinerrorText; they do not assume ExtraInfo precedesloadingFailedor that Chromium will not later finish its internal error document. Response-stage interception also verifies that original transport ExtraInfo is visible beforeFetch.requestPaused, while aFetch.continueResponsestatus/header override changes the laterNetwork.responseReceivedwithout emitting duplicate ExtraInfo. - The focused
error-documentgroup turns the reset-before-response fixture into an end-to-end failed-navigation gate. It checks the direct and redirected event order, the split between internal frame URL and user-visible Target/history URL,unreachableUrl, independent loader/realm generations, old-global retirement, consecutive failures, successful recovery, and concurrent target isolation. Every scenario requires Runtime to remain usable after the failure, so the pre-fix permanentNoDocumentLoadedstate fails the smoke rather than being hidden by a later successful navigation. - Chromium-derived proxy authentication coverage uses a real local proxy and per-browser-context proxy configuration. HTTP Basic authentication must expose the initial unauthenticated target request headers and final
200response through ExtraInfo without leakingProxy-Authorization; an HTTPSCONNECTchallenge canceled through Fetch must expose proxy origin, a407response withhasExtraInfo=false, andNetwork.loadingFailedwithout treating CONNECT headers as target-request ExtraInfo. - Parser-discovered external script, link stylesheet, and parser-created
@importstylesheet observation plusNetwork.getResponseBody; configured20,000,000 / 2,000,000inspector-cache budgets retain a small body while a2,000,001-byte body keeps its request identity and repeatedly returns the inspector-cache eviction error. - Classic WebSocket echo, WebSocket
Network.webSocket*events, and blocked WebSocket handshake behavior. - The focused
classic-scrollbargroup compares Chromium and Moli for block/flex/gridstable both-edgesnumeric sizing, default-visible root stable gutters, body hidden/auto/clip viewport overflow propagation,overflow-body-propagation-003'sdisplay:contentsexclusion, and an overlay that occludes a lower painted scrollbar. The numeric matrix additionally covers padding/border with both box-sizing modes, auto/min/max block sizing, aspect-ratio, and physical top/bottom gutters invertical-rl. Block, flex, and grid cases also assert that leadingboth-edgesgutters neither create a phantomscrollWidth/scrollHeightnor inflate a real overflow range. It also verifies Moli's consume-only scrollbar corner and screenshot-triggered layout freshness. The final workflow was calibrated on 2026-08-22 against the headed Xvfb build~/chromium/src/out/Default/chrome(Chrome/147.0.7709.0) and then extended with the physical-inset matrix on 2026-08-23 against that same executable before passing unchanged against Moli. Chromium/Xvfb routes raw-CDP clicks on its native scrollbar corner through DOM input, so only that control-consumption assertion remains Moli-specific. - Chromium-calibrated transformed-iframe input routing through raw CDP: exact
used child viewports, hover/click/wheel targets and child-local coordinates,
overflow-container scrolling, focus chains, and cross-frame mouseout
coordinates across both single and nested transformed frames. On 2026-08-18,
uv run moli-cdp-smoke --endpoint http://127.0.0.1:9228 --group iframe-inputpassed three consecutive runs against~/chromium/src/out/Default/chrome(Chromium 147.0.7709.0) under headed Xvfb; the same group then passed three consecutive Moli runs. The nested UA scrollbar control assertion is Moli-only because Chromium/Xvfb does not route raw CDP synthetic mouse input through its platform scrollbar widget. - Dedicated default
Page.setDocumentContentreplacement coverage through Playwright and raw CDP: exact-once, ordered document-open/DOM/load projection to two attached sessions while one session has a pending Runtime command; session-local lifecycle enable state; preserved Document/realm/history/frame identity; repeated replacement and detachedElementHandlebehavior; stylesheet-candidate cleanup; no navigation or realm teardown; deterministic parser pause/resume at a body stylesheet; child-frame replacement; and error atomicity. - Dedicated raw CDP DOM replacement coverage through the
dom-snapshotgroup:document.open()replacement must reject stale frontendnodeId, keep retained oldobjectIddetached from the new document, allocate distinct frontend and backend node ids for the new live node, and makeDOMSnapshot.captureSnapshotreflect only the current document contents. - Basic
page.set_content()static DOM and inline script execution, plus PlaywrightaddScriptTag()/addStyleTag()content, URL, path, and missing-URL rejection workflows. - File upload through
set_input_files, direct file chooser, scriptedshowPicker(), FileReader content reads, file-name-with-spaces replacement, and repeated input/change events. - Locator/input workflows for fill/clear, Playwright user-facing
get_by_test_id()/get_by_text()/get_by_label()/get_by_placeholder()/get_by_alt_text()/get_by_title()/get_by_role()plus role selector selected/checked/pressed/expanded/disabled/level/name/include-hidden filters, Playwrightexpect(locator)text/count/value/attribute/class/visible/hidden/enabled/disabled/checked matchers,has_text/has/filter()locator composition, upstream-derivedfirst()/last()/nth()/and_()/or_()/ Locator-argument composition andFrameLocator.locator()workflows,$eval/$$evalselector evaluation,page.type()selection/focus behavior,fill()input/change events,locator.clear()input event, input type/error/auto-wait behavior,check()/uncheck()/setChecked()state, aria-role, trial, error, and label-retarget behavior, type, press, keyboard modifiers, basic contenteditable editing, hover, checkbox, radio, richer PlaywrightselectOption()value/label/index/handle/multiple/wait behavior, click-triggered navigation, and drag/drop. Raw CDP smoke checks invalid-parameter priority plus explicit layout hit-testing errors for mouse, touch, emulated touch, tap, and drag, and verifies that none dispatch DOM events. Playwrightpage.mouseclick/dblclick/buttons/move/wheel workflows enforce the same boundary. - DOM/handle workflows for
locator.evaluate(),ElementHandlequeries,ElementHandle.content_frame(),ElementHandle.wait_for_element_state()visible/hidden/enabled/editable/timeout/detached behavior,JSHandle.as_element(),JSHandleproperty reads, nested/same-handleevaluate()arguments, console events with JSHandle args, bounding boxes, owner frames, child-frame evaluation, detached-handle behavior after navigation, and isolated-world DOM resolution through Playwright's injected scripts. - Download event, downloaded artifact, and download cancellation.
- Direct
Page.navigateoutcome coverage for attachments, empty and redirected downloads, download HTTP errors, binary MIME responses, ordinary HTTP error Documents, 204 responses, and transport failures, including response/request correlation and retained-Document checks. - Viewport resize, the explicit Playwright-generated screenshot clip boundary, and Chromium-calibrated geolocation position/unavailable/clear behavior across navigation and attached CDP sessions.
- localStorage, sessionStorage, basic IndexedDB, multi-context cookie isolation, Blob DevTools UUID storage-partition isolation, and browser-context profile overrides for user agent, locale, timezone, extra headers, and navigation referer.
- Chromium-calibrated
target-semanticsandbrowser-semanticscontracts for stable Tab/Page ownership, TargetHandler access modes, repeated auto-attach reconciliation, foreground/background activation, Page and screencast visibility, default-target discovery and close, target multi-attach/close/context disposal, real-URL target debugger-wait lifecycle correlation, history entry identity and metadata, frame metadata and detach order, top-level/popup storage, DOMStorage events, resource trees/search, XML, isolated worlds, EventSource, HTTP and CacheStorage caches, CSS Typed OM, and View Transitions. Each contract records its invariant, source, command chain, and observed values independently.
Intentional non-goals:
- The full Web Animations timing/state model, including
finish()/cancel()currentTimeparity. Surface-level API compatibility may remain, but it is not a cross-engine smoke gate.
Important gaps:
- Input precision: richer keyboard composition and deeper editable/contenteditable selection defaults remain in scope. Layout-backed single-pointer mouse/touch/tap and raw drag dispatch are covered; Chromium drag interception (
Input.dragIntercepted), multi-touch dispatch, and mobile/touch emulation remain gaps. - DOM/handle precision: stronger stale-handle matrix, cross-frame isolated-world handle conversion, deeper mutation-tree replacement edge cases, and deeper selector/hit-test accuracy.
- Navigation/lifecycle variants: popup target creation is covered through Target events, Puppeteer
waitForTarget(), and Playwrightpage.expect_popup(); named popup targets reuse an existing CDP target instead of creating duplicates; reserved_self/_top/_parentpopup targets navigate the current page in the current simplified top-level model.window.open()now returns a non-null WindowProxy projection. JavaScript dialogs block the invoking script until an automation client accepts or dismisses them, and prompt return values plus opening/closing protocol fields are covered. Remaining gaps are full WindowProxy/opener scripting behavior, background popup document load/activation after named-target reuse, native browser dialog UI, sharperwait_untiledge cases, same-document history entry precision, and target lifecycle edge cases. - Navigation response precision: Puppeteer selector-backed DOM activation completes and updates
page.url(), while the smoke records whetherwaitForNavigation()exposes the committed document response.page.goto()andpage.reload()response objects are required separately. Coordinatepage.click()is supported when the server runs with--layout; Mock policy still fails explicitly. - Same-document navigation precision: Puppeteer
page.waitForNavigation()is now covered for hash-only and History API navigation through renderer-producedPage.navigatedWithinDocumentevents. Remaining precision work is deeper history traversal/back-forward entry parity and Navigation API interception-style same-document reasons. - Screenshot/layout/emulation: raw full-page/clip/node screenshots and raster-backed
Page.printToPDFare covered, including base64/stream transfer, pagination, page ranges, orientation, print media, and background control. Header/footer templates, CSS page-size preference, tagged PDFs, document outlines, vector/text-preserving output, richer device-scale behavior, and more accurate layout metrics remain gaps. The Chromechrome://inspectcommand menu is a separate DevTools-frontend concern and does not advertise Moli's screenshot capability. - Wider network matrix: broader digest/multi-round proxy auth challenge variants, font/preload resource types, parser-discovered resource interception, and broader response-stage coverage beyond the current page/popup fetch/XHR response-stage smoke.
- Storage/profile depth: cookie delete/clear/domain/path/SameSite, persistent profile writeback, storage partition boundaries, and deeper IndexedDB flows.
- Worker/frame target depth: module-worker target lifecycle, nested DedicatedWorker targets, broader worker exception/debugger coverage, iframe subresource attribution, and future per-frame realm boundaries remain. DedicatedWorker now has first-layer CDP target/runtime/console/lifecycle coverage; SharedWorker has first-layer target/runtime/log/profiler and page-side reuse smoke, but both still need richer error matrices.
- Puppeteer parity: the default managed-client group covers the common connect/navigation/reload/same-document/evaluate/keyboard-input/DOM-selector-activation/coordinate-click/handle/current-viewport-screenshot/alert-dialog/popup-target/interception path and DedicatedWorker lifecycle, but full-page/clip screenshots, drag interception, broader dialog edge cases, and richer lifecycle waits remain.
Runner layout:
runner.py: starts the fixture, startsmoli serve, connects Playwright over CDP, and runs scenario groups.fixture.py: local HTTP/WebSocket fixture server and fixture routes.- Rust CDP smoke scaffold:
tests_cdp_smoke_fixture.rsmirrors the fixture routes for Rust tests, whiletests_cdp_smoke_chromium.rsandtests_cdp_smoke_playwright.rssplit Chromium/Playwright-derived CDP contracts into individual libtest cases. serve.py:moli serveprocess lifecycle and CDP readiness probing.assertions.py,helpers.py,state.py,config.py: shared runner utilities and state.groups/core.py: discovery-adjacent page workflows: navigation, iframe, wait, cookies, redirects, and history.groups/protocol.py: raw CDP protocol workflows that intentionally avoid Playwright helper commands, including shared worker target discovery, Runtime context/log replay, the Chromium/V8Debugger.pauseresponse-event-resume and nested-functionDebugger.stepOutresume/re-pause sequences, and profiler session state.groups/multi_client.py,groups/multi_client_fanout.py, andgroups/multi_client_support.py: Chromium-calibrated 2/3/7-client browser/direct-page WebSocket routing, session ownership, command-id collision, per-client FIFO, attach event/response ordering, and staged disconnect isolation contracts, split into two-client, fan-out, and shared support layers.groups/agent_episode.py: short raw-CDP RL-shaped observation/action, response/realm ordering, and failed-navigation error-Document contract.groups/fetch_runtime_teardown.py: holds an exact module-fetch lease while CDP disposes its BrowserContext, then verifies that callback-thread cancellation, browser commands, and a replacement context survive teardown.groups/chromium_cdp.py: Chromium inspector-protocol derived samples for Page, Runtime, Input, IO Blob streams, Performance, Profiler, and DOM contracts.groups/document_content.py: Playwright and raw-CDPPage.setDocumentContentreplacement identity, parser pause/resume, child-frame, and error-atomicity workflows.groups/error_document.py: failed main-document transport error Document identity, lifecycle, realm replacement, recovery, and multi-target isolation.groups/navigation_outcomes.py: direct Page.navigate download/no-document/error outcomes and matching Network evidence.groups/dom_parser_mutations.py: cross-engine raw-CDP parser-tail mutation publication and commit/DCL DOM binding barriers.groups/layout_screenshot.py: raw current-viewport PNG, DevTools parameter compatibility, paint/layout mutation freshness, open/closed Shadow Root and iframe TreeScope stability, generation-gated 1 FPS JPEG screencast/ACK behavior, and Moli default-Mock restart boundary.groups/layout_policy.py: cold initialization versus warm snapshot reuse, non-publishing reads/writes and print, screen publication, validation, and Document replacement through raw CDP.groups/action_window.py: raw wheel admission/deadline batching, screenshot flush/reset, derived-effect coalescing, and exact-Document retirement.groups/pdf.py: rawPage.printToPDFbase64 andReturnAsStreamtransport,IO.read, pagination, page ranges, orientation, PDF structure, and Chromium-shaped validation errors.groups/dom_snapshot.py: raw CDPdocument.open()replacement identity andDOMSnapshot.captureSnapshotfreshness workflows, intended to run unchanged against a Chromium CDP endpoint before validating moli.groups/playwright_compat.py: Playwright upstream derived route and CDPSession compatibility samples.groups/chrome_remote_interface.py,groups/cdp_use.py, andgroups/stagehand.py: optional published-client workflows, with subprocess and JSON-result handling shared bygroups/external_process.py.groups/puppeteer.pyandpuppeteer_smoke.mjs: pinned Puppeteer workflows driven from the uv runner through Node.groups/agent_browser.py: optional real agent-browser CLI workflows with isolated config, daemon namespace, and endpoint identity verification.groups/network.py: document/fetch/XHR routing, Network event observation, parser script and stylesheet body capture, WebSocket, and downloads.groups/workers.py: dedicated workerpostMessage, SharedWorker port reuse, worker fetch routing/auth, and worker XHR routing.groups/dom_input.py:set_content, file upload, file chooser, scripted picker, click navigation, locator/input workflows, Playwright upstream derived locator composition,page.type(), layout-backed mouse/touch/tap/raw-drag input,fill(),check()/uncheck()/setChecked(), andselectOption()workflows, DOM/handle workflows, plus the explicit high-level drag-interception boundary.groups/iframe_input.py: a Chromium-calibrated raw-CDP regression for transformed single and nested iframe hover, click, wheel, child-local event coordinates, exact used frame viewport propagation, and Moli's nested-frame UA scrollbar routing boundary.groups/emulation_storage.py: viewport and Playwright screenshot-clip boundary, detailed geolocation plus locale/timezone runtime overrides, storage/cookie isolation, IndexedDB baseline, and browser-context profile overrides.groups/media_error.py: process-isolated HTMLMediaElement MediaError lifecycle contracts.groups/target_semantics.py: raw Target-domain identity, attachment, activation, visibility, and lifecycle contracts calibrated against Chromium.groups/browser_semantics.py: page/runtime cross-engine contracts calibrated against Chromium before they are applied to Moli.
Near-term expansion order:
- Deepen popup and dialog fidelity: popup Target linkage is observable by Playwright/Puppeteer, anchor
target="_blank"now enters the same popup activation path, named target reuse no longer creates duplicate targets at Target-discovery level, reserved current-context targets no longer create bogus popup targets,window.open()returns a WindowProxy projection, and automation-controlled JavaScript dialogs block and resume with the handled value. Remaining work includes fuller WindowProxy/opener behavior, complete background popup document loading after named-target reuse, native browser dialog UI, and Chromium's dialog suppression policies. - Add more network/resource-type coverage after that, especially font/preload observation, parser-discovered resource interception, proxy auth, and richer auth variants. Stylesheet link /
@importhas Network smoke coverage without requiring a following parser script, the Chromium-derived matrix covers image/media/text-track/XHR body capture, pagefetch()response-stage pause / continue has a focused smoke, page XHR response-stage coversFetch.getResponseBody, page fetch response-stage coversFetch.takeResponseBodyAsStream/IO.read, and server-auth covers successful credentials plus Chromium-compatibleCancelAuthfor page fetch, worker fetch/XHR, configured response-stage continuation, and top-level navigation. - Keep low-frequency CDP methods out of this smoke unless they block a real Playwright workflow. Use focused Rust tests for narrow protocol shape regressions and keep each fixture-derived CDP contract as a separate test.
Running
Build moli from the repository root first:
cargo build -p moli
If both debug and release binaries exist, the runner uses the newest one by file modification time. Set MOLI_BIN when you need an exact binary.
Then run the smoke project:
cd moli-cdp-smoke
uv sync
npm ci
uv run moli-cdp-smoke
With no --group, the supervisor executes every raw, Playwright page, browser,
and repository-managed external-client group, including inspector-routing
and the pinned Puppeteer client. Every group runs in a separate worker process
with its own Moli process, fixture server, HTTP cache, Playwright context, and
temporary directories. One failed or wedged group therefore cannot retain
state or child processes for the next group.
The default concurrency is one worker. Use bounded parallelism explicitly; CI runs the same unfiltered suite with four workers:
uv run moli-cdp-smoke --jobs 4
Each run writes summary.json plus one log and one result JSON per worker under
target/smoke/cdp/<run-id>/. Use --output-dir for a stable artifact path and
--timeout to change the per-worker wall-clock limit. The worker also retains
its own protocol-operation timeouts, so the wall limit is a final process-level
safety boundary rather than the primary synchronization mechanism. Supervised
worker logs include the live moli serve stdout/stderr stream, so the last
renderer messages survive even when the supervisor terminates a wedged worker.
For failed workers, the supervisor prints the saved error/traceback before the
log tail, so CI output includes the cause even when the worker log only reports
the exception type. The result JSON and summary retain the same diagnostics.
External-client groups whose binaries or dependency environments are not owned by this project remain explicit integration runs.
List available scenario groups:
uv run moli-cdp-smoke --list-groups
Run a focused subset while developing a CDP area:
uv run moli-cdp-smoke --group protocol --group network
uv run moli-cdp-smoke --group multi-client
uv run moli-cdp-smoke --group layout-screenshot
uv run moli-cdp-smoke --group layout-policy
uv run moli-cdp-smoke --group action-window
uv run moli-cdp-smoke --group pdf
uv run moli-cdp-smoke --group agent-episode
uv run moli-cdp-smoke --group fetch-runtime-teardown
uv run moli-cdp-smoke --group network-body-cache
uv run moli-cdp-smoke --group dom-input,emulation-storage
uv run moli-cdp-smoke --group media-error
uv run moli-cdp-smoke --group document-content
uv run moli-cdp-smoke --group dom-snapshot
uv run moli-cdp-smoke --group dom-whitespace
uv run moli-cdp-smoke --group computed-style
uv run moli-cdp-smoke --group error-document
uv run moli-cdp-smoke --group navigation-outcomes
uv run moli-cdp-smoke --group url-policy
uv run moli-cdp-smoke --group inspector-routing
MOLI_SMOKE_GROUPS=protocol,websocket uv run moli-cdp-smoke
The complete Inspector routing group runs by default. Select one or more named
contracts while iterating with
MOLI_INSPECTOR_ROUTING_SCENARIOS:
MOLI_INSPECTOR_ROUTING_SCENARIOS=raw_cdp_active_javascript_main_io_lane_matrix \
uv run moli-cdp-smoke --group inspector-routing
MOLI_INSPECTOR_ROUTING_SCENARIOS=raw_cdp_nested_v8_main_receiver_matrix,raw_cdp_nested_non_v8_main_receiver_matrix \
uv run moli-cdp-smoke --group inspector-routing
MOLI_INSPECTOR_ROUTING_SCENARIOS=raw_cdp_dedicated_worker_active_javascript_interrupt,raw_cdp_shared_worker_active_javascript_interrupt \
uv run moli-cdp-smoke --group inspector-routing
The Chromium 147 IO catalog exercised by the complete group is:
Debugger.getPossibleBreakpoints Debugger.getScriptSource
Debugger.getStackTrace Debugger.pause
Debugger.removeBreakpoint Debugger.resume
Debugger.setBreakpoint Debugger.setBreakpointByUrl
Debugger.setBreakpointsActive Emulation.setScriptExecutionDisabled
Page.crash Performance.getMetrics
Runtime.terminateExecution
Main and IO are ordered independently; the smoke never requires relative ordering between the two routes. A normal debugger pause must pump mixed V8 and non-V8 Main commands from the same session in send order. An instrumentation pause must leave Main blocked and admit only IO work.
Run the same focused group against an already-running Chromium CDP endpoint. The Python workers are still separate, but an explicitly supplied external browser endpoint is shared rather than restarted by the supervisor:
uv run moli-cdp-smoke \
--endpoint http://127.0.0.1:9222 \
--group dom-snapshot
The complete inspector-routing group was calibrated on 2026-08-16 against
the local Chromium build Chrome/147.0.7709.0. Point the same command at a
Chromium remote-debugging endpoint to re-run the oracle before changing a
routing contract. The two Worker active-JavaScript interrupt regressions were
also cross-checked on 2026-08-18 against Chromium/145.0.7632.116:
uv run moli-cdp-smoke \
--endpoint http://127.0.0.1:9222 \
--group inspector-routing
Run the Puppeteer group:
npm ci
uv run moli-cdp-smoke --group puppeteer
If puppeteer-core is installed outside this directory, expose it through Node resolution:
NODE_PATH=/path/to/node_modules uv run moli-cdp-smoke --group puppeteer
PUPPETEER_CORE_MODULE=/path/to/node_modules/puppeteer-core uv run moli-cdp-smoke --group puppeteer
Run the optional agent-browser group:
AGENT_BROWSER_BIN=/path/to/agent-browser uv run moli-cdp-smoke --group agent-browser
The tested CLI must be agent-browser 0.31.1 or newer. The group first runs connect and verifies
get cdp-url against /json/version; it never permits the CLI's browser auto-launch fallback.
Run the optional thin-client and Stagehand groups with the benchmark-pinned clients:
NODE_PATH=/path/to/node_modules \
CDP_USE_PYTHON=/path/to/cdp-use-venv/bin/python \
uv run moli-cdp-smoke \
--group chrome-remote-interface,cdp-use,stagehand
The calibrated versions are chrome-remote-interface 0.34.0, cdp-use 1.4.5, and
Stagehand 3.7.0. Each group verifies the live CDP product against /json/version;
none may launch a fallback browser.
You can also select a specific binary:
MOLI_BIN=../target/release/moli uv run moli-cdp-smoke
Useful environment variables:
MOLI_BIN: path to themolibinary under test.MOLI_CDP_PORT: fixed CDP server port for single-worker debugging. By default every worker uses--port 0and reads the OS-selected endpoint from Moli's bound-listener log. A fixed port is rejected with parallel workers.MOLI_SMOKE_GROUPS: comma-separated smoke group list. CLI--grouptakes precedence.MOLI_INSPECTOR_ROUTING_SCENARIOS: comma-separated scenario names within theinspector-routinggroup.MOLI_SMOKE_TRACE=1: print extra runner-side trace logs.MOLI_SMOKE_TRACE_BG=1: print backgroundmoli servelogs when invoking a worker directly. The supervisor enables this automatically because it persists each worker's combined output as a diagnostic artifact.NODE: Node executable used by optional Node client groups. Defaults tonode.PUPPETEER_CORE_MODULE: module name or path used by the Puppeteer group. Defaults topuppeteer-core.CHROME_REMOTE_INTERFACE_MODULE: module name or package directory used by the optional CRI group. Defaults tochrome-remote-interface.CHROME_REMOTE_INTERFACE_VERSION: exact CRI version gate. Defaults to0.34.0.CDP_USE_PYTHON: Python executable containing cdp-use for the optional group. Defaults to the smoke runner's Python.STAGEHAND_MODULE: module name or package directory used by the optional Stagehand group. Defaults to@browserbasehq/stagehand.STAGEHAND_VERSION: exact Stagehand version gate. Defaults to3.7.0.AGENT_BROWSER_BIN: agent-browser CLI used by the optional external group. Defaults toagent-browseronPATH.
This smoke connects to moli over CDP, so it does not need Playwright-managed browser binaries.
The runner starts moli serve with --layout --resource so screenshot
coverage uses the real renderer and the Chromium-derived Network matrix can
observe every optional resource family, including images, audio, video, and
text tracks. These are smoke-only opt-ins; normal moli defaults remain
Mock layout with optional resources disabled.
Relationship to the Node smoke
The repository still keeps scripts/playwright-cdp-smoke.mjs. The Python/uv project is the forward maintenance path. The Node script remains temporarily as a reference implementation until the Python suite has proven equivalent coverage.