---
title: "Steel"
description: "Run agent-qa web tests in Steel cloud browsers or a self-hosted steel-browser: install the Steel plugin, set lifetimes, profiles and proxies, and fix refusals."
canonical_url: "https://vostride.com/docs/agent-qa/plugins/steel"
md_url: "https://vostride.com/docs/agent-qa/plugins/steel.md"
last_updated: "2026-11-07T15:25:39+05:30"
---

> [Documentation index](https://vostride.com/docs.md) · [Site index](https://vostride.com/llms.txt)
> Follow the Markdown links to read individual pages; the indexes list the available documentation.

# Steel

> Run agent-qa web tests in Steel cloud browsers or a self-hosted steel-browser: install the Steel plugin, set lifetimes, profiles and proxies, and fix refusals.

The Steel plugin, `@vostride/agent-qa-plugin-steel`, gives each web test a [Steel](https://steel.dev) browser session. Before the test connects, it creates the session through Steel's API and hands agent-qa its CDP address. After the test it releases the session. It also drives a [steel-browser](https://github.com/steel-dev/steel-browser) you host yourself.

Steel runs Chromium only, so tests on a Steel remote use `use.browser.name: chromium`. CAPTCHA solving, ad blocking, Steel's proxies and Steel's fingerprint injection are off unless a setting turns them on, so a test sees your app the way your users do.

## Install
Install the plugin at exactly the version of agent-qa your project runs:

```bash
npm install --save-dev --save-exact @vostride/agent-qa-plugin-steel@$(npx agent-qa --version)
```

## Configure
```yaml
plugins:
  - name: steel
    package: "@vostride/agent-qa-plugin-steel"
    options:
      apiKey: "{{secret:STEEL_API_KEY}}"

registry:
  remotes:
    - name: steel
      plugin: steel
      settings:
        idleMinutes: 5

use:
  browser:
    name: chromium
    headless: true
    remote: steel
```

Put the key in your secrets file, `.env.secrets.local` by default, as `STEEL_API_KEY=...`.

## Options
Options belong to the `plugins` entry and describe the account.

| Option | Default | Meaning |
| --- | --- | --- |
| `apiKey` | | The Steel API key, a credential. Write it as a `{{secret:NAME}}` reference. Leave it out for a self-hosted steel-browser. |
| `apiUrl` | `https://api.steel.dev` | Steel's API. It must use `https://` with no user or password, unless it points at this machine. |

The key is sent as the `steel-api-key` header and in the connect URL, which agent-qa masks. It never appears in logs, run records or `doctor` output.

## Settings
Settings belong to each remote and describe one place.

| Setting | Default | Meaning |
| --- | --- | --- |
| `timeoutMinutes` | `1440` | Minutes before Steel ends the session, from 1 to 1440. Your Steel plan may cap it lower. |
| `idleMinutes` | `5` | Minutes with no browser command before Steel ends the session, from 1 to 60. |
| `solveCaptcha` | `false` | Steel's CAPTCHA solving. |
| `blockAds` | `false` | Steel's ad blocking. |
| `fingerprint` | `false` | Steel's browser fingerprint injection, which changes what pages see. |
| `proxy` | `false` | Steel's proxy: `true`, or Steel's proxy object. |
| `proxyUrl` | | Your own proxy, `http(s)://user:password@host:port`, a credential. Sent instead of Steel's proxy. |
| `dimensions` | | The browser window, `{ width, height }` in pixels. |
| `region` | | The Steel region, such as `us-east`. |
| `profile` | | A Steel profile ID, loaded read-only. Needs `context: reuse`. |
| `extensions` | | Steel extension IDs to load into the browser. |
| `extra` | | Any other field of Steel's create request, merged last. |

## How the request is built
The plugin builds Steel's create request in this order, each part overriding the one before:

1. a session ID the plugin chooses, so it can release the session even if Steel's response never arrives;
2. the lifetimes, from `timeoutMinutes` and `idleMinutes`;
3. the features that change the page: CAPTCHA solving, ad blocking, fingerprint injection and Steel's proxies, each sent off unless a setting turns it on. Steel injects fingerprints unless told not to, so the plugin always says so explicitly;
4. the other settings, in Steel's field names. With `proxyUrl`, the plugin sends your proxy and not Steel's, so a remote that sets both `proxyUrl` and `proxy` is refused;
5. `extra`, last, so a field there overrides both a default and a setting. It cannot set `sessionId`.

For example, `settings: { extra: { solveCaptcha: true, headless: true } }` turns CAPTCHA solving on and asks for a headless browser.

## Session lifetime
By default the plugin asks for a session of up to 1440 minutes that ends after 5 idle minutes. agent-qa releases every session itself after its test; the lifetimes only bound a session agent-qa could not release.

- **Plans cap the lifetime.** Steel's Launch plan allows 15 minutes, Scale 60 and Enterprise up to 1440. When Steel refuses to create a session with a 400 or 422 that may be about the lifetime, the line ends `if your Steel plan caps sessions below <n> minutes, set settings.timeoutMinutes to the cap`. Set `timeoutMinutes` to your plan's cap, such as `15` on Launch.
- **Idle time.** Any browser command resets it, and agent-qa sends a heartbeat every 30 seconds while a test holds the session, so a running test never goes idle.
- **Killed jobs.** A session left behind by a killed CI job, where agent-qa and the plugin die together, ends within the idle time, so it stops billing within minutes.

## Recording
Steel records every session and keeps the replay on its dashboard, behind its login. agent-qa's masking does not reach that replay, so a secret a test types may show in it. Steel has no switch to turn recording off, so the plugin has no `recording` setting. Use test credentials.

The session link is the session's page in Steel's dashboard. Steel's debug URL lets anyone who has it watch and drive the session, so agent-qa masks it and never shows it.

## Signed-in profiles
`profile` loads a Steel profile by ID. The plugin never asks Steel to save it, so nothing a test does is kept. A profile lives in the browser's first context, so the remote needs `context: reuse`:

```yaml
registry:
  remotes:
    - name: steel-signed-in
      plugin: steel
      context: reuse
      settings:
        profile: <Steel profile ID>
```

## Self-hosted steel-browser
The plugin drives a steel-browser you run yourself:

```yaml
plugins:
  - name: steel-local
    package: "@vostride/agent-qa-plugin-steel"
    options:
      apiUrl: "http://localhost:3000"

registry:
  remotes:
    - name: steel-local
      plugin: steel-local

use:
  workers: 1
```

- Point `apiUrl` at it. Plain `http://` is allowed only for this machine.
- Leave out `apiKey`, and the plugin sends no key.
- Set `use.workers: 1`. A steel-browser serves one session at a time, and releasing a session ends whatever session is active, so two tests at once would end each other's browser.
- Its session page is plain `http://`, so tests on it show no link.

## Account check
`agent-qa doctor` runs the plugin's account check, which lists sessions and opens none:

- an accepted key reads `the API key was accepted`;
- without `apiKey`, a self-hosted steel-browser that responds reads `Steel answered without an API key`, and a Steel that wants a key warns `Steel asked for an API key; set options.apiKey`;
- a refused key warns `the API key was refused`.

## When a remote is refused
| Kind | Cause | Fix |
| --- | --- | --- |
| `auth` | Steel responded with 401. | Check the key in your secrets file. |
| `capacity` | Steel responded with 429: no free sessions, or a rate limit. | Wait, or lower `use.workers`. |
| `refused`, ending `set settings.timeoutMinutes to the cap` | Your plan may cap sessions below the lifetime asked for. | Set `timeoutMinutes` to the plan's cap. |
| `refused` at `settings.profile` | The remote sets `profile` without `context: reuse`. | Add `context: reuse` to the remote. |
| `refused` at `settings.proxyUrl` | The remote sets both `proxy` and `proxyUrl`. | Keep one. |
| `refused` at `settings.extra.sessionId` | `extra` sets `sessionId`. | Remove it. |
| `refused` with `Unrecognized key: "recording"` | Steel cannot turn recording off. | Remove `recording`. |
| `refused` naming a browser | Steel runs Chromium only. | Use `use.browser.name: chromium`, or another remote. |
| `refused` at `options.apiUrl` | The endpoint is not `https://` and not this machine. | Use `https://`, or a loopback address. |
| `unreachable` | Steel did not respond. | Check the network, or that your steel-browser is running. |

Each refusal is one line with Steel's status, the request and Steel's message, and the wait Steel asked for on a 429. If Steel refuses the create request with a 4xx, no session exists and nothing needs releasing. After any other failure, agent-qa still releases whatever Steel may have created.

`npx agent-qa plugins describe steel --json` lists every option and setting with its type, default and description.

## Documentation navigation

- [All documentation](https://vostride.com/docs.md)
- Parent: [Plugins](https://vostride.com/docs/agent-qa/plugins.md)
- Previous: [Kernel](https://vostride.com/docs/agent-qa/plugins/kernel.md)
- Next: [Write a plugin](https://vostride.com/docs/agent-qa/plugins/write-a-plugin.md)
