moli-webdriver-smoke
This is a standalone WebDriver smoke project for moli. It is a peer of
moli-cdp-smoke/: the runner starts a local fixture server, starts
moli serve, and exercises the public WebDriver wire surface from outside
the Rust test harness.
The default group uses raw WebDriver Classic HTTP. The optional BiDi group uses
raw WebSocket messages. The optional Selenium group uses Selenium Python's
webdriver.Remote client against the same moli serve endpoint, so this
project now covers both protocol-level wire shape and a real Selenium client
workflow.
The optional semantics group contains isolated cross-engine contracts. It can
run against Moli's native WebDriver endpoint or an external ChromeDriver;
Moli uses standard W3C capabilities without Chrome vendor options.
The optional url-policy group is a Moli hosted-product contract. It
uses isolated Classic and BiDi sessions to verify the exact local-file
navigation error envelopes, unchanged browsing contexts, and absence of BiDi
navigation lifecycle events. It is deliberately skipped for desktop Chromium,
whose browser-granted local-file capability is a different product policy.
The optional navigation-errors group is a cross-engine Chromium/WPT contract.
It runs the same 37-case public-wire matrix against Moli or ChromeDriver:
ten Classic malformed-body/URL cases, 22 BiDi invalid-argument cases, two
missing-context cases, and three real address failures. It also checks the W3C
HTTP and BiDi error envelopes, unchanged Classic URL, and post-failure session
liveness. The baseline was executed on 2026-08-09 with Debian Chromium
145.0.7632.116 and matching ChromeDriver 145.0.7632.117.
The script-timeout boundary has two deliberately separate optional groups:
script-timeout-chromiumis the ChromeDriver 147 oracle. A yielding async script times out near its deadline, while a bounded non-yielding loop first returns to the renderer and only then producesscript timeout.script-interruptis Moli's renderer-IO extension. It runs actual infinite loops through both Classic sync and async execution, requires timeout-drivenRuntime.terminateExecutionnear the configured deadline, and repeats recovery work in the same window after every termination.
The split is intentional: the Moli group proves preemption, while the ChromeDriver group records that ChromeDriver itself does not offer the same non-yielding WebDriver timeout behavior.
Coverage
- WebDriver Classic status/session/delete value envelopes.
- Classic navigation, current URL, title, page source, CSS/XPath element lookup and identity, element
text/tag/displayed/enabled/rect/attribute/property/computed label/computed
role, send keys, clear form controls with input/change events, file upload, click, execute script,
key actions whose handler performs a top-level Page replacement while the action request is completing,
explicit screenshot unsupported errors, cookies, alerts, unhandled prompt behavior, window-scoped prompt
switching, shadow roots, page-side SharedWorker probe without polluting window
handles,
document.open()replacement stale-element behavior, and headless window state surface. - WebDriver BiDi
session.status,session.new,browsingContext.create,session.subscribe,browsingContext.navigate, DOMContentLoaded lifecycle events,input.performActions,input.releaseActions, key actions that trigger a top-level Page replacement and remain successful through the replacement lifecycle, element-origin input action routing without asserting layout hit-test effects,input.setFiles,network.getData,browser.setDownloadBehavior,browsingContext.downloadWillBegin/downloadEnd,network.setCacheBehavior, SharedWorkerbrowsingContext.contextCreated, livescript.realmCreated(type = "shared-worker"), andscript.getRealms(type = "shared-worker"), user context creation/removal, user-context viewport inheritance, explicitbrowsingContext.captureScreenshotunsupported errors,emulation.setUserAgentOverride,emulation.setLocaleOverride,emulation.setTimezoneOverride,emulation.setNetworkConditions, storage isolation,storage.setCookie,storage.getCookies, andsession.end. - Selenium Python Remote WebDriver session creation, Selenium BiDi facade
webSocketUrlattachment withbrowsing_contextcreate/navigate/get_tree/ locate_nodes, browsing context fragment/history event handlers,scriptevaluate/console/javascript-error handlers, add/remove preload scripts with context filtering, channel arguments, sandbox realms, and userContext filtering,getRealms, root-owned script handles,callFunction,disown, special number/BigInt serialization, nested handle/local-value arguments,network.add_request_handler()request interception,network.add_auth_handler()Basic Auth continuation,browseruser context/client-window, andemulationuser-context UA/locale/timezone/viewport/network-conditions workflows, navigation, title/current URL, page source, element text/tag/attribute/property/css/displayed/enabled/ rect, link text locators, CSS/XPath user-facing locator workflows, accessible name, computed ARIA role, label-associated controls including link/img/select/textarea/input-button naming, send keys, clear, execute sync/async scripts with primitive/null/array/WebElement results, WebElement arguments, timeout/error recovery, and nested WebElement return values, Selenium client-side script pin/list/unpin workflows, Selenium text handling for child/block/inline content, whitespace, NBSP, hidden/script text exclusion, Selenium relative locators vialocate_with(), fetch/XHR through async scripts, localStorage, sessionStorage, IndexedDB, Selenium remote downloads via/se/files, frame switching by index/name/id/WebElement with nested parent/default-content recovery and relative lookup errors, deleted-frame recovery, SeleniumSelectsingle/multi-select option lookup, select/deselect, disabled/hidden option errors, form submit via SeleniumWebElement.submit()and submit-button click, checkbox/radio selection, text input/textarea typing and clear input/change events, all-printable key input, text-controlchangecommit on blur, WebDriver private-key caret navigation/PageUp/PageDown, arrow-key legacyKeyboardEvent.keyCode, selection/delete/backspace, numpad, and function-key send keys, direct stale element reference errors after navigation/DOM removal/document replacement, implicit waits for single and multiple element lookup, WebDriverWait expected conditions for presence/visibility/text/clickability/invisibility/staleness/ selection/value/attribute/frame/alert, W3C keyboard actions, and explicit unsupported-operation errors for pointer/wheel actions including hover/double-click/context-click/drag-drop, Selenium remote file upload via/se/filewith FileReader content reads, action key input with Shift modifiers, explicit screenshot/element screenshot and print_page unsupported errors, cookie management including SameSite, expired cookies, delete-all, and exact-name deletion, window handles/new/switch/close/rect/ maximize/minimize, anchor/script popup windows, named window target reuse,window.open(..., "_self"), SeleniumShadowRootlookup/scoped find/ execute_script equality/no-such-shadow-root errors, alert/confirm/prompt text, and alert accept/dismiss/send keys. - Cross-engine Selenium semantics for capability matching, same-document realm and WebElement identity, cross-document stale elements, top-level and popup storage ownership, nested frame recovery, shadow-frame Window named access, dialog return values, and standard W3C error classes. Each scenario uses an independent session and emits a structured contract record.
- Hosted local-file navigation rejection as Classic HTTP
500with W3Cunknown error, and as a BiDiunknown error; both retainabout:blank, and the BiDi path emits no immediate or delayed navigation lifecycle event. - Chromium/WPT navigation failure shapes: Classic HTTP
400invalid argumentenvelopes for null/missing/non-string/relative/malformed URLs; BiDiinvalid argumentfor context, URL, and wait validation;no such framefor missing context ids;unknown errorfor unsupported schemes, DNS failure, and unsafe ports; and a usable session after the complete failure matrix.
Known remaining worker-surface gap: the BiDi SharedWorker smoke now requires
target context creation, live realm creation, and script.getRealms; richer
lifecycle, error, and teardown coverage is still pending.
Usage
Build moli first when no binary exists:
cargo build -p moli
Then run the default smoke:
cd moli-webdriver-smoke
uv sync
uv run moli-webdriver-smoke
CI explicitly runs all six groups with --continue-on-failure:
classic,bidi,selenium,semantics,url-policy,navigation-errors
The local default remains the smaller Classic-only loop for focused development.
List available groups:
uv run moli-webdriver-smoke --list-groups
Run focused groups:
uv run moli-webdriver-smoke --group classic
uv run moli-webdriver-smoke --group bidi
uv run moli-webdriver-smoke --group selenium
uv run moli-webdriver-smoke --group semantics --continue-on-failure
uv run moli-webdriver-smoke --group url-policy --continue-on-failure
uv run moli-webdriver-smoke --group navigation-errors --continue-on-failure
uv run moli-webdriver-smoke --group script-interrupt
MOLI_WEBDRIVER_SMOKE_GROUPS=classic,bidi,selenium uv run moli-webdriver-smoke
Collect all subgroup failures in one diagnostic run:
uv run moli-webdriver-smoke \
--group classic,bidi,selenium,semantics,url-policy,navigation-errors \
--continue-on-failure
Run the same semantics group against a matching local Chrome/ChromeDriver
build:
~/chromium/src/out/Default/chromedriver --port=9515
uv run moli-webdriver-smoke \
--endpoint http://127.0.0.1:9515 \
--browser-name chrome \
--browser-binary ~/chromium/src/out/Default/chrome \
--group semantics --continue-on-failure
Run the bounded ChromeDriver script-timeout oracle against the local Chromium 147 pair:
~/chromium/src/out/Default/chromedriver --port=9515
uv run moli-webdriver-smoke \
--endpoint http://127.0.0.1:9515 \
--browser-name chrome \
--browser-binary ~/chromium/src/out/Default/chrome \
--group script-timeout-chromium
Run Moli's non-yielding sync/async termination contract against moli serve:
uv run moli-webdriver-smoke --group script-interrupt
Run the Chromium/WPT navigation failure matrix against any matching local Chrome/ChromeDriver pair (the driver and browser major versions must agree):
/path/to/chromedriver-145.0.7632.117 --port=9516
uv run moli-webdriver-smoke \
--endpoint http://127.0.0.1:9516 \
--browser-name chrome \
--browser-binary /usr/bin/chromium \
--group navigation-errors
Useful environment variables:
MOLI_BIN: path to themolibinary under test.MOLI_WEBDRIVER_PORT: WebDriver server port. Defaults to a free local port.MOLI_WEBDRIVER_SMOKE_GROUPS: comma-separated smoke group list. CLI--grouptakes precedence.MOLI_WEBDRIVER_SMOKE_CONTINUE_ON_FAILURE=1: equivalent to--continue-on-failure.MOLI_WEBDRIVER_SMOKE_TRACE_BG=1: print backgroundmoli servelogs.
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.