>_EXECUTIVE SUMMARY
stryke-selenium is the browser automation connector in the stryke ecosystem. Chrome / firefox / safari / edge (headless or visible), navigation, element queries (css / xpath / id / name / tag / class / link-text), click / send_keys / clear, attribute / property / CSS reads, JavaScript execution, screenshots (full-page + per-element), window + frame control, and cookie management — a selenium-python-equivalent surface for stryke. Opt-in package, kept out of the stryke core binary. The stryke side is a thin JSON-over-FFI wrapper; the WebDriver client code (thirtyfour + tokio) lives in the libstryke_selenium cdylib loaded in-process by stryke via dlopen.
Architecture is shaped by Selenium's session model: a WebDriver is an HTTP keep-alive pool plus a server-side browser process — both expensive to set up. Re-creating per call would make any non-trivial script unusable. The cdylib holds the WebDriver in a process-global registry so one Selenium::open serves the whole script; element handles get the same treatment.
~ARCHITECTURE
cdylib + dlopen design: the stryke side is a thin .stk library that calls into a shared library loaded once per process. Each selenium__* export is a JSON-string-in / JSON-string-out wrapper around the driver / element / script / capture / window modules. A process-global tokio runtime bridges thirtyfour's async API to the sync FFI.
| Layer | Implementation |
|---|---|
stryke library (lib/Selenium.stk) | Thin wrapper exposing typed functions in the Selenium:: namespace; builds a JSON args dict, calls the registered FFI symbol, parses the JSON response, dies on {"error":…} payloads |
FFI surface (src/lib.rs) | #[no_mangle] extern "C" fn selenium__*(args_json: *const c_char) -> *const c_char; ffi_call wrapper handles JSON parse, panic catch, error serialization; stryke_free_cstring plugs the return-allocation leak |
Async bridge (src/common.rs::runtime) | Process-global tokio::runtime::Runtime in OnceCell, built on first FFI entry; every handler is runtime.block_on(async { … }) |
Session registry (src/common.rs) | OnceCell<Mutex<HashMap<u64, WebDriver>>> — monotonic u64 ids keyed to live WebDriver handles; first successful open promotes to active session |
Element registry (src/common.rs) | OnceCell<Mutex<HashMap<u64, WebElement>>> — same shape; the cdylib never re-finds an element a script already has a handle to |
| WebDriver client | thirtyfour 0.35 with rustls-tls — pure-Rust TLS so the cdylib doesn't drag the openssl system library into the build matrix |
| Async runtime | tokio 1.x (multi-thread, rt-multi-thread + macros + time) |
| Install path | s pkg install -g github.com/MenkeTechnologies/stryke-selenium — fetches prebuilt cdylib tarball for host triple from GitHub Releases, verifies SHA-256, extracts into ~/.stryke/store/stryke-selenium@<version>/ |
| Tests | Cargo unit tests under src/ (FFI plumbing, JSON-shape coverage, registry round-trips) plus t/test_selenium.stk for end-to-end FFI dispatch via permission-free queries (supported_browsers, locator_strategies, sessions) |
| CI | GitHub Actions .github/workflows/ci.yml — cargo check + clippy (-D warnings) + fmt + test + doc on ubuntu + macos |
| Release | GitHub Actions .github/workflows/release.yml — per-triple cdylib build matrix on tag push; uploads .tar.gz + .sha256 to GitHub Release |
$WHY OPT-IN (NOT BUILTIN)
The thirtyfour + tokio dependency tree is non-trivial: a full async runtime, HTTP keep-alive pool, rustls TLS stack, and the WebDriver client itself. Baking that into stryke core would inflate the daily-driver install for every user, GUI or not.
The trade-off is intentional. The core stryke binary stays slim precisely because each connector ships separately. Daily-driver work (one-liners, awk replacement, data scripting) doesn't need a browser session pool linked in.
Unlike sidecar-binary connectors (stryke-aws v1, etc.), stryke-selenium can't fork(2) + exec(2) per call — the WebDriver session is too expensive to re-establish. So it ships as a cdylib that stryke dlopens once per process, holding session state across calls.
&FFI PROTOCOL
Every selenium__* export takes a JSON-encoded args dict as *const c_char and returns a JSON-encoded result as *const c_char. The stryke FFI bridge copies the return into a stryke string and calls back into stryke_free_cstring to release the allocation. Errors come back as {"error": "<message>"} — the .stk wrapper recognizes this shape and dies.
| Export | Args → Response |
|---|---|
selenium__open | {browser, url?, headless} → {session: u64} |
selenium__quit | {session?} → {session, closed: true} |
selenium__quit_all | {} → {closed: usize} |
selenium__sessions | {} → {sessions: [u64], active: u64?} |
selenium__goto | {session?, url} → {} |
selenium__title | {session?} → {title: string} |
selenium__find | {session?, by, selector} → {element: u64} |
selenium__find_all | {session?, by, selector} → {elements: [u64]} |
selenium__wait_for | {session?, by, selector, timeout: f64} → {element: u64} |
selenium__element_click | {element} → {} |
selenium__element_send_keys | {element, text} → {} |
selenium__element_text | {element} → {text: string} |
selenium__element_attr | {element, name} → {value: string?} |
selenium__element_rect | {element} → {x, y, width, height: f64} |
selenium__execute_script | {session?, script, args: [Value]} → {value: Value} |
selenium__screenshot | {session?, output?} → {path} or {png: [u8]} |
selenium__element_screenshot | {element, output?} → {path} or {png: [u8]} |
selenium__window_rect | {session?} → {x: i64, y: i64, width: u32, height: u32} |
selenium__window_handles | {session?} → {handles: [string]} |
selenium__switch_window | {session?, handle} → {} |
selenium__cookies | {session?} → {cookies: [Value]} |
selenium__add_cookie | {session?, cookie: {name, value, …}} → {} |
selenium__execute_async_script | {session?, script, args: [Value]} → {value: Value} — callback-completed |
selenium__action_click | {session?, element} → {} — pointer move + left-click |
selenium__action_double_click | {session?, element} → {} |
selenium__action_context_click | {session?, element} → {} — right-click |
selenium__action_send_keys | {session?, text} → {} — key-down/up sequence |
selenium__print_page | {session?, output} → {path} — page to PDF |
selenium__accept_alert | {session?} → {} |
selenium__dismiss_alert | {session?} → {} |
selenium__alert_text | {session?} → {text: string} |
selenium__send_alert_text | {session?, text} → {} |
selenium__get_named_cookie | {session?, name} → {cookie: Value} |
selenium__supported_browsers | {} → [string] |
selenium__locator_strategies | {} → [string] |
To pass a Selenium WebElement as an execute_script arg, wrap it as { __element__: <id> }. The cdylib detects this shape, resolves the element from the registry, and serializes it with thirtyfour's WebElement serde impl — which emits the W3C-spec element reference ({"element-6066-11e4-a52e-4f735466cecf": "<id>"}) that the WebDriver server unmarshals back into a real DOM element on the JS side.
/SCOPE
The surface is a selenium-python-equivalent — the same shape ports directly with Selenium:: in place of webdriver.. Coordinates use the WebDriver spec's top-left origin. Locator strategies (css, id, name, xpath, tag, class, link_text, partial_link_text) map 1:1 to thirtyfour's By::* arms.
wait_for polls every 200 ms — same default as selenium-python's WebDriverWait. Implicit-wait, page-load, and script timeouts are also exposed (set_implicit_wait, set_page_load_timeout, set_script_timeout) and apply server-side.
Multi-browser is first-class: every Selenium::open() returns a fresh integer session id. The first open is set as the active session; subsequent calls without an explicit $sid arg route to it. Cross-session work threads $sid as the trailing argument (mirrors how Python's webdriver.Chrome() instances are passed around).
Beyond the basic click/send_keys path, action chains (action_click, action_double_click, action_context_click, action_send_keys in src/actions.rs) drive pointer/keyboard sequences for cases plain element ops can't express — a pointer move to the element's center before clicking defeats overlay click-interceptors, and right-click opens a native context menu. Alert handling (accept_alert / dismiss_alert / alert_text / send_alert_text) covers alert/confirm/prompt dialogs. Async JavaScript via execute_async_script blocks until the script invokes its injected completion callback or the script timeout elapses.
A second surface needs no browser at all: the pure helpers parse / build / validate locators (parse_locator, build_locator, locator_to_w3c, w3c_to_locator, valid_locator_strategy), cookies (parse_cookie, build_cookie, cookie_domain_matches / cookie_path_matches per RFC 6265), CSS selectors (css_escape / css_unescape / css_escape_string / build_css_selector / parse_css_selector), XPath literals (build_xpath, xpath_literal), and WebDriver special-key code points (key_code / key_name). These run without opening a session, so they execute unattended in CI alongside the static-dispatch queries.
See the README API reference for the authoritative function list. The stryke side exposes 102 Selenium:: functions over 99 selenium__* cdylib exports (the extra stryke-side functions are aliases such as get→goto and convenience wrappers like set_window_size / set_window_position that re-shape arguments to a shared export).
!WEBDRIVER SERVER REQUIREMENTS
stryke-selenium is a client. The user is responsible for launching the WebDriver server (chromedriver / geckodriver / safaridriver / msedgedriver / selenium-server-standalone) before calling Selenium::open. The cdylib has no opinion on which server — Selenium::open(url => "http://…") connects wherever you point it.
| Server | Browser | Default URL |
|---|---|---|
chromedriver --port=9515 | Chrome / Chromium | http://localhost:9515 (the Selenium::open() default) |
geckodriver --port 4444 | Firefox | http://localhost:4444 |
safaridriver -p 4444 | Safari (macOS only) | http://localhost:4444. One-time safaridriver --enable to opt in. |
msedgedriver --port=9515 | Microsoft Edge | http://localhost:9515 |
selenium-server standalone --port 4444 | All of the above through one server | http://localhost:4444 |
The CI test suite (t/test_selenium.stk) is permission-free — it exercises only the static-dispatch surface (supported_browsers, locator_strategies, sessions) and the empty-registry shapes, so it runs unattended without a WebDriver server. Live browser ops are exercised by the examples/selenium_*.stk demos against a locally-launched chromedriver.
#PROJECT METADATA
| Item | Value |
|---|---|
| License | MIT |
| Author | MenkeTechnologies |
| Repository | github.com/MenkeTechnologies/stryke-selenium |
| Parent language | strykelang |
| Sibling: GUI automation | stryke-gui |
| Meta umbrella | MenkeTechnologiesMeta |
| Issues | github.com/MenkeTechnologies/stryke-selenium/issues |