Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/steerlabs/opensteer/llms.txt

Use this file to discover all available pages before exploring further.

OpenSteer provides a sophisticated selector resolution system that enables deterministic replay of browser automation scripts. By combining description-based targeting with intelligent persistence, your automation scripts become more maintainable and resilient to DOM changes.

Resolution Process

When you perform an action with a description parameter, OpenSteer follows a 5-step resolution process:
  1. Reuse persisted selector - Check if a selector was previously saved for this description
  2. Snapshot counter - Use the element counter from the current snapshot
  3. Explicit CSS selector - Use the provided selector parameter
  4. LLM resolution - Ask the configured AI model to locate the element from the description
  5. Actionable error - Return a detailed error with diagnostic information
import { Opensteer } from 'opensteer';

const opensteer = new Opensteer({ name: 'my-app' });

await opensteer.launch();
await opensteer.goto('https://example.com');

// Take an action snapshot before interactions
await opensteer.snapshot({ mode: 'action' });

// Use description-based targeting
await opensteer.click({ description: 'login button' });

// The selector is automatically persisted for future runs

Selector Persistence

When OpenSteer successfully resolves an element through steps 2-4 and a description is present, the resolved selector path is automatically saved to disk.

Storage Location

Selectors are stored in .opensteer/selectors/<namespace>/:
.opensteer/
└── selectors/
    └── my-app/              # Namespace from Opensteer({ name: 'my-app' })
        ├── index.json       # Registry metadata
        ├── login_button.json
        ├── search_field.json
        └── submit_form.json

Selector File Structure

Each persisted selector file contains:
{
  "id": "login_button",
  "method": "click",
  "description": "login button",
  "path": {
    "matches": [
      {
        "tagName": "button",
        "attributes": {
          "id": "login-btn"
        }
      }
    ]
  },
  "metadata": {
    "createdAt": 1234567890000,
    "updatedAt": 1234567890000,
    "sourceUrl": "https://example.com/login"
  }
}
The namespace comes from the name parameter in new Opensteer({ name: 'my-app' }). For CLI usage, use the --name flag to set the namespace.

Description-Based Targeting

Description-based targeting allows you to identify elements using natural language rather than brittle CSS selectors or XPath expressions.

When to Use Descriptions

Use descriptions for:
  • Semantic targeting - “login button”, “search input”, “user profile link”
  • Dynamic content - Elements that may change position but maintain purpose
  • Maintainable scripts - Scripts that should survive UI refactors
  • AI agent workflows - Autonomous agents that need reliable element identification
// Click actions
await opensteer.click({ description: 'submit button' });

// Input text
await opensteer.input({
  description: 'email address field',
  text: 'user@example.com'
});

// Hover interactions
await opensteer.hover({ description: 'dropdown menu trigger' });

// Select from dropdown
await opensteer.select({
  description: 'country selector',
  value: 'US'
});

Deterministic Replay

The persistence system enables deterministic replay, where subsequent runs of the same script use cached selectors instead of making LLM calls.

Benefits

Faster Execution

Skip LLM resolution on subsequent runs by reusing persisted selectors

Reduced Costs

Avoid repeated LLM API calls for the same element lookups

Consistency

Same description always resolves to the same element across runs

Offline Support

Run scripts without LLM access once selectors are cached

Replay Flow

Fallback Options

While descriptions are powerful, OpenSteer provides explicit fallback options:
// Use snapshot counter directly
await opensteer.snapshot({ mode: 'action' });
await opensteer.click({ element: 42 });

Best Practices

Keep the name parameter the same across runs to reuse cached selectors.
// Good: Consistent namespace
const opensteer = new Opensteer({ name: 'my-app' });
Always call snapshot({ mode: 'action' }) before interactions to ensure fresh element counters.
await opensteer.snapshot({ mode: 'action' });
await opensteer.click({ description: 'submit button' });
Write clear, semantic descriptions that uniquely identify the target element.
// Good
await opensteer.click({ description: 'primary navigation login button' });

// Bad
await opensteer.click({ description: 'button' });
Check .opensteer/selectors/ into git to share cached selectors across team members and CI/CD environments.
git add .opensteer/selectors/
git commit -m "Add cached selectors for login flow"

Snapshot Modes

Learn about different snapshot modes and when to use them

Actions API

Complete API reference for click, input, hover, and more

Build docs developers (and LLMs) love