Skip to overview

Overview

Direct gives browser agents deterministic app states

Browser tools control a page. Direct makes the state behind it quick to reach and repeatable without claiming to test the external systems it replaces.

A browser agent can open a page, click a control, and inspect the result. It cannot make the state behind that page quick to reach. A signed-in account, a particular database record, a device permission, a model response, or a failure at the right moment may still take longer to arrange than the interface takes to review.

Hraness Direct separates those two jobs. A browser tool controls the page. The product connects Direct's named, repeatable states to its existing interface and feature logic through deterministic adapters below a small product-owned boundary. Direct speeds up development and review; it does not drive the browser or prove that replaced systems work.

The same interface cycles through named scenes, instant resets, and repeatable checks.

Hraness Direct keeps the interface while making scenes quick to reset and check.

Browser control and app state are different jobs

agent-browser gives coding agents a compact command-line interface for opening pages, reading accessibility snapshots, and interacting with elements. Playwright and other browser drivers solve the same broad problem with different APIs. If the state you need is already fast and reliable to reach, a browser tool by itself is the smaller and better choice.

Direct becomes useful when setup dominates the loop: repeated sign-in, slow seed requests, hard-to-create empty or error states, unavailable native modules, paid model calls, or device permissions that automation cannot reset cleanly. Direct does not click the page. It gives the browser tool a stable page state to act on.

Replace setup below the behavior

A product-owned port is a small interface between product behavior and an external system. A task view might ask a task repository to read and update tasks. Production connects that port to a live service. A Direct composition connects the same port to a deterministic implementation. The interface, reducers, parsing, navigation, and feature decisions above the port stay on their normal code paths.

The boundary can be pictured without knowing the package API:

Conceptual Direct boundary

agent-browser or Playwright
            │
   real interface + feature state
            │
     product-owned port
        ┌───┴────┐
  live system   Direct world

A Direct world is validated JSON that describes one starting state. A scenario gives that world a name and route. It does not contain browser actions. The browser check still decides what to click and what outcome to assert.

The public Todo example uses one TodoPort in both compositions. The component receives whichever implementation the entry point owns:

One product port, two compositions

export interface TodoPort {
  readTodos(): Promise<readonly TodoItem[]>;
  setCompleted(id: string, completed: boolean):
    Promise<readonly TodoItem[]>;
}

const port = isDirect
  ? createDeterministicTodoPort(world)
  : createLiveTodoPort();

<TodoApp port={port} />

The interface speaks in product terms: todos and completion. It contains no Direct types and does not know whether storage is live or deterministic. Use the lowest port that preserves the behavior under review. If the Direct adapter must copy the logic named by the claim, the boundary is too high and the fixture would imitate its subject instead of testing it.

Direct owns one deterministic session

Direct gives the development composition one lifecycle instead of a collection of unrelated fixture helpers:

  • A definition validates the named worlds, routes, and evidence claims.
  • A session activates one world and owns its state, logical time, tracked work, reset generation, and cleanup.
  • A browser installation publishes the scenario catalog, active-state identity, coverage contract, probe, and reset action while blocking unmapped application requests by default.

That default network policy matters. A deterministic page should not silently call a live service when a fixture misses a case. The product can allow exact URLs when needed, but unknown application calls fail visibly. Direct and its fixture worlds also stay outside the production dependency graph.

The published manifest makes the page self-describing. An agent can discover valid scenario IDs and routes, confirm which activation the current probe belongs to, and check that it is the requested scenario and route without reading a product-specific source file. The browser tool still owns navigation and interaction; Direct does not turn scenarios into commands.

Wait for the app, not a guess

A fixed delay says, “wait 500 milliseconds and hope.” Direct exposes a quiescence snapshot: no tracked operation is active, and each product-named pending counter is zero. The product's browser verifier must poll that snapshot until its generation, revision, and counters remain stable for a bounded interval before checking the interface.

Browser check using a named Direct scenario

await page.goto(
  "/direct/?__direct_scenario=todos.populated",
);
await waitForQuiescence(page);

await page.getByRole("checkbox", {
  name: "Write the public guide",
}).check();

await waitForQuiescence(page);
await expect(page.getByRole("checkbox", {
  name: "Write the public guide",
})).toBeChecked();

Here, waitForQuiescence is product-owned verifier code around Direct's snapshot, not a Direct browser driver. Quiescence proves only that the work Direct knows about has settled. It does not prove that the screen is correct. The verifier must still reject relevant console, runtime, and unhandled-request errors, then make product-specific assertions or visual checks.

Choose the smallest tool that covers the risk

  • Use browser automation alone when the required state is already quick to reach, or when the live backend and browser assembly are part of the check.
  • Pair Direct with agent-browser or Playwright when setup and reset dominate the loop and the substituted systems can sit behind a small product-owned port.
  • Use unit or component tests when the subject is isolated logic or rendering that does not need the full application composition.
  • Keep live integration and end-to-end tests when the backend, native host, browser assembly, filesystem, operating system, or device is the subject.

Direct records the same distinction in coverage claims. A fixture claim stops at deterministic ports. A mixed claim combines fixture evidence with a named live check. A direct claim requires the real system. The labels do not create evidence; they keep a fast development check from being reported as proof of a system it never touched.

Carry the workflow with the package

The package includes two Agent Skills under skills/. direct-setup guides the product-port boundary, deterministic composition, and production-exclusion check. direct-verify guides scenario review, quiescence, cleanup, and evidence classification. The skills carry the technical detail an agent needs without forcing every human reader through an API manual.

Package installation leaves the skills inactive because coding-agent runners use different discovery directories. Copy or link the desired skill into the runner's configured location, then invoke it by name. The package does not run a postinstall script or edit agent configuration.

Use Direct when the state behind the interface is the bottleneck and a small product-owned port can replace that setup without copying the behavior under review. Use the browser tool alone when it can already reach the state cheaply. In either case, the browser driver supplies the actions and assertions. Direct never exercises the systems behind replaced ports; cover those boundaries separately with live integration or end-to-end tests when their risk requires it.