// STRYKE-SELENIUM — BROWSER AUTOMATION FOR STRYKE // WEBDRIVER + DOM + JS + COOKIES

stryke package · cdylib libstryke_selenium · loaded in-process via dlopen on first use Selenium · opt-in (kept out of stryke core)

Report GitHub Issues
// Color scheme

>_STRYKE-SELENIUM

selenium-python, one stryke pipe away. Browser automation for stryke — 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, opt-in as a package so the stryke core binary stays slim. The stryke side is a thin JSON-over-FFI wrapper; the WebDriver client code (thirtyfour + tokio) lives in the libstryke_selenium cdylib.

Install

thirtyfour ships with rustls-tls in the default feature set, so there are no OS-side build deps beyond a working Rust toolchain — no openssl, no Wayland / X11 stack. Just:

s pkg install -g github.com/MenkeTechnologies/stryke-selenium

Pin a specific release:

s pkg install -g github.com/MenkeTechnologies/stryke-selenium@v0.17.0

This fetches the prebuilt tarball for your host triple (aarch64-apple-darwin, x86_64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu), verifies its SHA-256, extracts into ~/.stryke/store/stryke-selenium@<version>/, and registers the cdylib for use Selenium. No cargo, no rustc, no per-target build step on the user's machine.

You also need a WebDriver server (chromedriver / geckodriver / safaridriver) running — see the WebDriver server section below.

Quick start

use Selenium

# launch a browser
Selenium::open(browser => "chrome", headless => 1)

# navigate + read
Selenium::goto("https://example.com")
p "title: " . Selenium::title()
p "url:   " . Selenium::current_url()

# find + interact
val $h1 = Selenium::find("h1")
p "h1 text: " . Selenium::text($h1)

# wait + click
val $btn = Selenium::wait_for("button.submit", "css", 10)
Selenium::click($btn)

# screenshot
Selenium::screenshot("/tmp/page.png")

# clean up
Selenium::quit()

API: Selenium::*

Every function lives in the Selenium:: namespace (use Selenium). The last optional argument on most browser-bound calls is $sid — the session id returned by Selenium::open. Omit it to use the active session. Element ops take the element id $eid returned by find / find_all / wait_for. The pure helpers at the end open no browser session at all — they are string parsing / validation that runs without a WebDriver server.

Session lifecycle

Selenium::open(%opts)browser (chrome/firefox/safari/edge, default chrome), url (WebDriver server URL, default http://localhost:9515), headless (1/0, default 0). Returns the session id.
Selenium::quit($sid?)Close one session.
Selenium::quit_all()Close every open session. Returns count closed.
Selenium::sessions()List of open session ids.
Selenium::active()Active session id, or undef.
Selenium::set_active($sid)Set the active session.
Selenium::supported_browsers()("chrome", "firefox", "safari", "edge")
Selenium::locator_strategies()Every name accepted as $by.

Navigation & timeouts

Selenium::goto($url, $sid?) · Selenium::get(...)Navigate.
Selenium::current_url($sid?)Current URL.
Selenium::title($sid?)Document title.
Selenium::source($sid?)Full HTML.
Selenium::back($sid?) · forward · refreshHistory navigation.
Selenium::set_implicit_wait($s, $sid?)Server-side implicit-wait timeout.
Selenium::set_page_load_timeout($s, $sid?)Page-load timeout.
Selenium::set_script_timeout($s, $sid?)Script timeout.
Selenium::get_timeouts($sid?){ script, page_load, implicit } in seconds (a field is undef when unset).
Selenium::status($sid?){ ready, message } WebDriver server status.

Element queries

Selenium::find($sel, $by="css", $sid?)Returns one element id, or dies.
Selenium::find_all($sel, $by="css", $sid?)Returns a list of ids.
Selenium::wait_for($sel, $by="css", $timeout=10, $sid?)Polls every 200 ms until found or timeout.
Selenium::active_element($sid?)Element id of document.activeElement (the focused element).

$by values: css (default), id, name, xpath, tag, class, link_text, partial_link_text. Each has short aliases (css_selector, tag_name, class_name, link, plink) — see Selenium::locator_strategies().

Element ops

Selenium::click($eid)Click the element.
Selenium::send_keys($eid, $text)Type into the element.
Selenium::clear($eid)Clear a form field.
Selenium::text($eid)Visible text.
Selenium::attr($eid, $name)HTML attribute or undef.
Selenium::prop($eid, $name)Live DOM property.
Selenium::css($eid, $name)Resolved CSS value.
Selenium::tag($eid)Lowercase tag name.
Selenium::class_name($eid)class attribute as one string, or undef.
Selenium::value($eid)Live value DOM property (form fields), or undef.
Selenium::inner_html($eid)innerHTML (children markup).
Selenium::outer_html($eid)outerHTML (own tag + children).
Selenium::parent($eid)Parent node → a fresh element id.
Selenium::rect($eid)($x, $y, $w, $h).
Selenium::is_displayed($eid) · is_enabled · is_selected1 / 0.
Selenium::is_clickable($eid)1/0 — displayed and enabled.
Selenium::is_present($eid)1/0 — handle still attached (not stale).
Selenium::focus($eid)Focus the element via DOM focus() (no click).
Selenium::drop($eid)Drop the client-side handle.

JavaScript

Selenium::execute_script($js, $args?, $sid?)Runs $js with $args as arguments[0..N]. Returns whatever return ... produced, parsed from JSON. To pass a Selenium element, wrap: { __element__ => $eid }.
Selenium::execute_async_script($js, $args?, $sid?)Async variant: $js gets a completion callback as its last argument (arguments[arguments.length - 1]); the value passed to it is the return. Blocks until the callback fires or the script timeout elapses.

Action chains

Low-level pointer/keyboard sequences for cases a plain click/send_keys can't express.

Selenium::action_click($eid, $sid?)Move pointer to the element's center, then left-click (defeats click interceptors).
Selenium::action_double_click($eid, $sid?)Move + double-click.
Selenium::action_context_click($eid, $sid?)Move + right-click (context menu).
Selenium::action_send_keys($text, $sid?)Type against the focused element via a key-down/key-up sequence.

Screenshots & scrolling

Selenium::screenshot($path?, $sid?)$path if given, else \@png_bytes.
Selenium::element_screenshot($eid, $path?)Same shape, scoped to one element.
Selenium::print_page($path, $sid?)Writes the page as PDF, returns $path.
Selenium::scroll_to_element($eid)Scroll element into the viewport.

Window / frame

Selenium::window_rect($sid?)($x, $y, $w, $h).
Selenium::set_window_rect($x?, $y?, $w?, $h?, $sid?)Partial accepted.
Selenium::set_window_size($w, $h, $sid?)Keeps position.
Selenium::set_window_position($x, $y, $sid?)Keeps size.
Selenium::window_handles($sid?)Every tab/window handle.
Selenium::current_window($sid?)Handle of focused tab.
Selenium::switch_window($handle, $sid?)Switch by handle.
Selenium::new_window($sid?)Open a new top-level window, switch to it, return its handle.
Selenium::new_tab($sid?)Open a new tab, switch to it, return its handle.
Selenium::close_window($sid?)Close the current window/tab (closes the session if it's the last one).
Selenium::set_window_name($name, $sid?)Set window.name for later switch_to_named_window.
Selenium::switch_to_named_window($name, $sid?)Switch by the window.name set above.
Selenium::switch_frame($eid, $sid?)Enter iframe by element.
Selenium::switch_frame_number($n, $sid?)Enter iframe by zero-based index.
Selenium::switch_default_content($sid?)Back to top.
Selenium::switch_parent_frame($sid?)One level out.
Selenium::maximize($sid?) · minimize · fullscreenWindow state.

Alerts

Selenium::accept_alert($sid?)Click OK on alert/confirm/prompt.
Selenium::dismiss_alert($sid?)Click Cancel.
Selenium::alert_text($sid?)Text of the current alert.
Selenium::send_alert_text($text, $sid?)Type into a JS prompt.

Cookies

Selenium::cookies($sid?)Arrayref of cookie hashes.
Selenium::get_named_cookie($name, $sid?)One cookie hash by name, or dies if absent.
Selenium::add_cookie(%fields)name + value required; path, domain, secure, http_only, same_site, expiry optional.
Selenium::delete_cookie($name, $sid?)Delete one cookie.
Selenium::delete_all_cookies($sid?)Delete every cookie.

Pure helpers (no browser)

These open no session — string parsing / validation that runs without a WebDriver server:

Selenium::parse_locator("css=.btn"){ strategy, value } — splits strategy=value, canonicalizes the strategy for find (bare value → css).
Selenium::build_locator($strategy, $value){ locator, strategy } — inverse of parse_locator; rejects unknowns.
Selenium::valid_locator_strategy($s){ strategy, valid, canonical }.
Selenium::locator_to_w3c($strategy, $value?){ using, value, strategy } — the W3C WebDriver protocol pair; id/name/class collapse to a css selector.
Selenium::w3c_to_locator($using, $value){ strategy, value, locator } — inverse for the five W3C using strategies.
Selenium::key_code($key){ key, code_point, codepoint, char } — WebDriver special-key PUA code point (Enter→U+E007, F1F12, aliases).
Selenium::key_name(%opts){ key, code_point, codepoint, char } — PUA code point → canonical key name; inverse of key_code.
Selenium::parse_cookie("a=b; Path=/; Secure"){ name, value, domain, path, secure, http_only, same_site, expires } — feeds add_cookie.
Selenium::build_cookie(%opts)Set-Cookie string — inverse of parse_cookie; truthy secure/http_only become bare flags.
Selenium::cookie_domain_matches($cookie_domain, $host){ cookie_domain, host, matches } — RFC 6265 §5.1.3 domain matching.
Selenium::cookie_path_matches($cookie_path, $request_path){ cookie_path, request_path, matches } — RFC 6265 §5.1.4 path matching.
Selenium::css_escape($value)→ escaped string — CSSOM serialize-an-identifier (browser CSS.escape).
Selenium::css_unescape($escaped)→ decoded string — inverse of css_escape (CSS Syntax §4.3.7).
Selenium::css_escape_string($value)→ quoted string — escape + double-quote a CSS attribute-selector value ([attr="…"]).
Selenium::build_css_selector(%parts)→ selector string — compose tag/id/classes/attributes into a CSS selector.
Selenium::parse_css_selector($selector){ tag, id, classes:[…], attributes:{…} } — inverse of build_css_selector.
Selenium::build_xpath(%parts)→ xpath string — compose tag/id/classes/attributes/text/contains_text into a // locator.
Selenium::xpath_literal($value)→ literal string — quote an arbitrary string as an XPath 1.0 literal; uses concat() when it contains both ' and ".

The same list, with full per-argument notes, lives in the README API reference.

WebDriver server

stryke-selenium is a client. You launch the WebDriver server yourself. One-liners per browser:

# Chrome — default. Matches Selenium::open() with no url arg.
brew install --cask chromedriver
chromedriver --port=9515 &

# Firefox — pass url => "http://localhost:4444" to Selenium::open.
brew install geckodriver
geckodriver --port 4444 &

# Safari — macOS only; one-time enable:
safaridriver --enable
safaridriver -p 4444 &

# Edge — install msedgedriver matching your Edge version.
msedgedriver --port=9515 &

# Selenium Grid 4 (all browsers via one server):
brew install selenium-server
selenium-server standalone --port 4444 &

To verify the install + FFI roundtrip without launching a browser:

selenium-test            # installed at ~/.stryke/bin/selenium-test by `s pkg install -g`

Why a cdylib, not a sidecar binary

Two competing models for stryke connectors:

  • Sidecar binary — the connector ships a regular Rust binary; the .stk wrapper fork(2) + exec(2)s it per call, parsing JSON over a pipe. Simple, ABI-stable, isolates crashes — but every call pays a process-creation cost plus any per-call session-init cost the binary has (Enigo handshake, WebDriver session resume, …).
  • cdylib (this package) — the connector ships a shared library; stryke dlopens it once per process and registers each export as a stryke-callable function. Calls are direct function calls with no fork / exec — just JSON-in / JSON-out across the FFI boundary.

For Selenium the second model is mandatory: a WebDriver session is an HTTP keep-alive pool plus a server-side browser process, both of which take hundreds of milliseconds to set up. Paying that per Selenium::* call would make any non-trivial script unusable. The cdylib holds the WebDriver handle in a process-global OnceCell<Mutex<HashMap<u64, WebDriver>>> so one Selenium::open serves the whole script.

thirtyfour is async (tokio), but the stryke FFI is sync. The cdylib bridges via a single process-global tokio::runtime::Runtime built on first FFI entry; every handler is runtime.block_on(async { … }). A fresh runtime per call would be ~1 ms of pure tokio bring-up overhead — fine in isolation, ruinous across thousands of Selenium::* calls.

FFI protocol

Every selenium__* export takes a JSON-encoded args dict and returns a JSON-encoded result. The lib/Selenium.stk wrapper builds the args, calls the FFI symbol, and parses the return. Errors come back as {"error": "<message>"} — the wrapper recognizes this shape and dies with a descriptive message.

selenium__open{browser, url, headless} → {session}
selenium__sessions{} → {sessions: […], active: u64?}
selenium__find{session?, by, selector} → {element}
selenium__find_all{session?, by, selector} → {elements: […]}
selenium__wait_for{session?, by, selector, timeout} → {element}
selenium__element_text{element} → {text}
selenium__element_attr{element, name} → {value: string?}
selenium__execute_script{session?, script, args: […]} → {value}
selenium__screenshot{session?, output?} → {path} or {png: [u8, …]}
selenium__cookies{session?} → {cookies: […]}
selenium__supported_browsers{} → […]
selenium__locator_strategies{} → […]

To pass a Selenium WebElement as a JS arg, wrap it as { __element__ => $eid }. The cdylib detects this shape and unmarshals it into a real DOM element on the JS side. Mirrors selenium-python's execute_script(…, web_element) convention.

Examples

open chrome, get title, quits examples/selenium_basic.stk
same, no visible windows examples/selenium_headless.stk
find input, send_keys, submits examples/selenium_form_fill.stk
full-page + per-element captures examples/selenium_screenshot.stk
wait_for with 10s timeouts examples/selenium_wait.stk
add / list / delete cookiess examples/selenium_cookies.stk
execute_script with WebElement argss examples/selenium_js.stk
multi-tab switchs examples/selenium_windows.stk
two browsers in parallels examples/selenium_multi_session.stk

Layout

stryke-selenium/
├── stryke.toml             # stryke package manifest with [ffi] table
├── Cargo.toml              # stryke_selenium cdylib crate manifest
├── src/
│   ├── lib.rs              # selenium__* FFI exports + ffi_call wrapper
│   ├── common.rs           # tokio runtime + session + element registries
│   ├── driver.rs           # open / quit / navigation / locator parsing
│   ├── element.rs          # find / wait_for / click / text / attr / ...
│   ├── script.rs           # execute_script / execute_async_script with WebElement arg unmarshaling
│   ├── actions.rs          # action-chain pointer/keyboard sequences (click / double / context / send_keys)
│   ├── capture.rs          # page + per-element screenshots
│   └── window.rs           # window rects / handles / new-window / frames / cookies
├── lib/Selenium.stk        # stryke wrappers (JSON args → FFI symbol → JSON return)
├── bin/selenium-test.stk   # installable smoke-test launcher
├── examples/               # runnable .stk demos
├── t/test_selenium.stk     # plumbing tests (permission-free FFI surface)
├── Makefile                # `make install` builds + installs
└── docs/                   # this site (GitHub Pages)

Sibling packages

Part of the stryke package family. Browse the others via the MenkeTechnologiesMeta umbrella repo: