Software Engineering

Building ArkenDriver: Lessons Learned from Writing a WebDriver Client in Rust

Published June 1, 2026 Rust Browser Automation

ArkenDriver started as a way to understand browser automation below the Selenium client layer. The project became a practical lesson in protocol design, Rust API boundaries, async HTTP, driver setup, and the difference between automating a browser and building an automation framework.

ArkenDriver hero image representing a Rust browser automation framework

Why build a WebDriver client from scratch?

Browser automation can feel familiar when you use a polished client library. You create a driver, find an element, click a button, and move on. That is useful, but it hides the shape of the system underneath.

ArkenDriver was built to remove that layer of comfort. Instead of depending on an existing Selenium client, the framework talks directly to the W3C WebDriver protocol using Rust, async HTTP requests, typed request and response structures, and explicit session management.

The goal was not to outbuild mature automation tools. The goal was to learn what those tools have to solve: sessions, capabilities, protocol payloads, element references, driver processes, configuration, errors, and the API design choices that make automation code feel trustworthy.

Lesson 1: browser automation is a protocol problem first

At the user level, automation looks like commands: navigate, find, click, type, quit. Under the hood, those commands are HTTP calls against a browser driver that follows a protocol.

ArkenDriver made that concrete. Starting a browser session means sending a new-session payload with capabilities. Finding an element means sending a locator strategy and value. A returned element is not a DOM node in your process; it is a protocol reference identified by the W3C element key.

Public API shape Rust
let mut driver = Driver::new("http://localhost:4444");

driver.start().await?;
driver.navigate("https://example.com").await?;

let button = driver.find_element(By::id("submit")).await?;
button.click().await?;

driver.quit().await?;

The nice API is only the surface. The real work is translating that surface into correct protocol requests and validating that the responses contain the fields the framework needs.

Lesson 2: Rust forces better boundaries

Rust made the framework design more intentional. The project separates driver session state, navigation, element behavior, error handling, configuration, and infrastructure code into focused modules.

That structure mattered because a WebDriver client has several different responsibilities. The main driver owns the HTTP client, base URL, and current session. Element objects need enough context to send element commands later. Configuration belongs outside the protocol layer. ChromeDriver installation and process management are infrastructure concerns, not browser command concerns.

Rust's ownership model pushed those boundaries into the design. Instead of passing loose data around, the code has to be clear about who owns the session ID, what can be cloned safely, and when an operation should fail because no active session exists.

Lesson 3: small abstractions matter

Locator design is a good example. A framework could expose only raw protocol strings, but that would make user code easier to misuse. ArkenDriver exposes a By type with helpers like By::css, By::id, By::name, and By::xpath.

That is a small abstraction, but it teaches an important framework lesson: good APIs reduce the number of protocol details the caller has to remember while still mapping honestly to the underlying system.

The same idea shows up in the WebElement type. Once the driver finds an element, the caller should not have to manually rebuild the session URL or remember the element ID format. The element can hold the context it needs and expose behavior like click and send_keys.

Lesson 4: setup is part of the framework

Automation examples often skip over the most annoying part: getting the browser driver installed, launched, and reachable. ArkenDriver includes infrastructure for downloading or reusing ChromeDriver, mapping platform names to Chrome for Testing packages, starting the local driver process, and reading configuration from ArkenDriver.config.

That work is not glamorous, but it is what makes automation usable. A framework that sends perfect protocol requests still feels fragile if every user has to manually manage driver binaries, paths, ports, and process startup details.

This was one of the bigger lessons from the project: developer experience lives in the edges. The core commands are important, but the surrounding setup often decides whether the tool feels reliable.

Lesson 5: testing protocol code requires a different mindset

Testing a browser automation client can become slow or flaky if every test launches a real browser. For ArkenDriver, the more useful pattern was to test protocol behavior directly.

Some tests verify serialization and deserialization: the session request, navigation payload, locator mapping, element response parsing, and send-keys payload. An integration-style test uses a small local TCP server to assert that the public API sends the expected HTTP requests and correctly reads a mock WebDriver response.

That approach keeps the tests fast while still checking the important contract: when user code calls the framework API, ArkenDriver should produce the right WebDriver traffic.

Lesson 6: error handling is framework design

A browser automation framework fails in many ways. The driver might not be running. The session might not exist. The browser can return an unexpected response. The network request can fail. An element can be missing, stale, or not interactable.

ArkenDriver's current error model is still early, but even the first version made one thing clear: error handling is part of the API. A framework should not only fail. It should fail in a way that gives the caller enough information to understand what happened.

That is especially important for automation because failures are often diagnostic. The error message is not just for a developer reading logs. It may be the first clue in a test run, CI failure, or debugging session.

Lesson 7: a roadmap keeps experiments honest

ArkenDriver is intentionally early. It already covers core WebDriver client behavior like session creation, navigation, element lookup, clicking, typing, ChromeDriver setup, and a terminal runner. The roadmap points toward screenshot support, wait utilities, locator abstractions, Page Object Model support, logging, a test runner, parallel execution, reporting, and CI/CD integration.

Writing that roadmap mattered because it turned the project from a pile of experiments into a framework direction. It also made tradeoffs easier. Not every feature belongs in the first pass, but the design should leave room for the features that are clearly coming.

What ArkenDriver demonstrates

ArkenDriver is a learning project, but it exercises practical engineering skills that apply far beyond browser automation.

  • Rust API design around ownership, async behavior, and module boundaries
  • Direct W3C WebDriver protocol communication through typed HTTP requests
  • Browser session management, navigation commands, and element interactions
  • ChromeDriver installation, platform mapping, process startup, and configuration
  • Fast protocol-level testing without requiring a real browser in every test
  • CI-backed build and test checks for a Rust automation project

The larger lesson is that frameworks are not magic. They are layers of careful translation between a human API and a lower-level system with strict rules. Building one from scratch made those layers visible.

The bigger takeaway

ArkenDriver taught me that learning a technology deeply often means rebuilding a small version of the tools I already use. Not because the world needs another automation client immediately, but because the process reveals what production tools are quietly solving.

Browser automation is not only clicking buttons. It is protocol communication, session lifecycle, process management, typed API design, testability, configuration, and clear failure behavior.

That is the value of the project: it turned WebDriver from a black box into an architecture I can reason about.