Plugins
Run tests in hosted browsers and on device clouds through official plugins, one package per service.
Some browser and device services need more than a URL: a session has to be created before each test and deleted afterwards, the connection signed, your app uploaded or a tunnel opened. A plugin does that work for one service, and agent-qa does the rest: connecting, masking credentials, recording evidence and cleaning up.
Each plugin is one npm package. You enable it once in plugins with your account's options, and each remote that runs tests on the service names it.
Browsers and devices
| Plugin | Web tests | Mobile tests | Browsers | Session link | Marks the session |
|---|---|---|---|---|---|
| BrowserStack | Over Playwright | Android and iOS, over Appium | Chrome, Edge, Chromium, Firefox, WebKit | Yes | Passed or failed |
| Hyperbrowser | Over CDP | No | Chromium | Yes | No |
| Kernel | Over CDP | No | Chromium | No | No |
| Steel | Over CDP | No | Chromium | Yes, except self-hosted | No |
BrowserStack runs web tests only on the Playwright versions it lists, and agent-qa 0.3.2's Playwright 1.64 is not on that list yet. Device tests are not affected. See BrowserStack.
A Playwright server, a CDP endpoint or an Appium server that only needs a URL and credentials needs no plugin. List it under registry.remotes with its url. See Remote browsers and devices.
Model sign-in
The subscription plugin runs inside agent-qa and needs no remote. Only vostride's own @vostride/agent-qa-* packages may run inside agent-qa; every browser and device plugin runs as a separate process.
Add a plugin
Install the package
Install a first-party plugin at exactly the version of agent-qa your project runs. Plugins are released with agent-qa and share its version:
npm install --save-dev --save-exact @vostride/agent-qa-plugin-kernel@$(npx agent-qa --version)npx agent-qa plugins listprints this line, with the version filled in, when the package is missing or its version differs from agent-qa's.Declare it
Add an entry to
pluginswith the account'soptions. Write each credential as a{{secret:NAME}}reference, and put the value in the fileworkspace.secretsFilenames,.env.secrets.localby default:plugins: - name: kernel package: "@vostride/agent-qa-plugin-kernel" options: apiKey: "{{secret:KERNEL_API_KEY}}"# .env.secrets.local KERNEL_API_KEY=sk_your_kernel_keyAdd a remote that names it
A remote is one place tests run. It names the entry with
pluginand holds that place'ssettings:registry: remotes: - name: kernel plugin: kernel settings: headless: truePick the remote
A web test runs there when
use.browser.remotenames it, in the config, a suite or the test. A mobile test runs there when its device'sremotenames it:use: browser: name: chromium headless: true remote: kernelCheck it
npx agent-qa plugins list npx agent-qa doctorplugins listreads each package's manifest.doctorruns each plugin's account check, which opens no paid session.
Entries and remotes
A plugins entry is the account. A remote is one place on that account. One entry can serve several remotes, such as a BrowserStack remote for Chrome on Windows, another for WebKit on macOS and a third for devices.
plugins entry key | Meaning |
|---|---|
name | The entry's name, a lowercase slug unique in the list. Remotes name it. |
package | The plugin's npm package name, exactly as installed. agent-qa never adds a prefix or guesses a name. |
path | A plugin folder inside the project, instead of package. For plugins you are developing. |
options | The account's values: keys, endpoints, tunnels. Checked against the plugin's own schema. |
| Plugin remote key | Default | Meaning |
|---|---|---|
name | The remote's name, a lowercase slug. local is reserved for this machine. | |
plugin | The plugins entry that serves it. | |
settings | This place's values: operating system, region, pool, profile and so on. Checked against the plugin's own schema. | |
connectTimeout | 5m | How long the plugin may take to hand over a session, including a queue. |
uploadTimeout | 10m | How long an app upload may take, for device remotes. |
context | new | new opens agent-qa's own browser context. reuse uses the browser's first context, which signed-in profiles need. |
capabilities | Appium capabilities for device sessions on this remote. |
A plugin remote has no url, query, headers or basicAuth, because the plugin supplies the connection. An entry's values go under options, and a remote's under settings. validate refuses each mix-up with a sentence that says where the key belongs.
agent-qa plugins describe <entry> prints every option and setting a plugin takes, read from the schemas the package ships. Read it before you write options or settings:
$ npx agent-qa plugins describe kernel
kernel @vostride/agent-qa-plugin-kernel first-party 0.3.2 protocol 1
serves: cdp
methods: check
receives: session facts only
skill: ./skill
Options
field type required default credential description
apiKey string required - credential The Kernel API key
apiUrl string optional "https://api.onkernel.com" - Kernel's API, https:// unless it points at this machine
Settings
field type required default credential description
headless boolean optional true - Run a headless browser; Kernel offers a live view only for a headful one
timeoutSeconds integer optional 60 - Seconds with no connection before Kernel deletes the browser
stealth boolean optional - - Kernel's stealth mode
region string optional - - The Kernel region, for example eu-west
proxy object optional - - Kernel's proxy, sent as given
viewport object optional - - The browser window size
viewport.width integer required - -
viewport.height integer required - -
viewport.refreshRate integer optional - -
pool string optional - - A Kernel browser pool, by id or name, to acquire from instead of creating a browser
profile string | object optional - - A Kernel profile loaded read-only, by name or as { id }; needs context: reuse
extra object optional - - Fields merged last into the create or pool acquire request, overriding the rest--json prints the same as JSON.
Check your plugins
agent-qa plugins list shows each entry with its origin, version, protocol, the remotes that name it and its state. It reads manifests only and runs no plugin code:
$ npx agent-qa plugins list
subscription @vostride/agent-qa-subscription-auth first-party 0.3.2 protocol - remotes: none ok
kernel @vostride/agent-qa-plugin-kernel first-party 0.3.2 protocol 1 remotes: kernel ok
steel @vostride/agent-qa-plugin-steel first-party - protocol - remotes: steel not_installed
Plugin entry steel names @vostride/agent-qa-plugin-steel, which is not installed. Run npm install --save-dev --save-exact @vostride/agent-qa-plugin-steel@0.3.2.| State | Meaning |
|---|---|
ok | Installed, with a valid manifest that this agent-qa can run. |
not_installed | The package is missing, or path has no package.json inside the project. The line says what to run. |
version_mismatch | A first-party plugin at another version than agent-qa. Install the version the line names. |
protocol_mismatch | A third-party plugin that speaks a protocol this agent-qa does not. Update agent-qa or rebuild the plugin. |
bad_manifest | The package has no valid plugin manifest, or is an alias of another package. |
unused | No remote names the entry. Name it from a remote, or remove it. |
agent-qa doctor adds a row for each plugin and each remote: its origin, version and protocol, the result of its account check, any {{secret:NAME}} it uses that your secrets file does not declare, and the install line when it is missing. The checks read the account, such as free sessions or a pool, and never open a paid session. doctor --remote <name> shows only that remote.
agent-qa validate checks entries and plugin remotes without running plugin code: each entry's options and each remote's settings against the schemas the plugin ships, for the remotes your tests and suites would use.
During a run
- agent-qa starts the plugin as its own Node process, once per entry, the first time a test needs it.
- Before a test connects, agent-qa asks the plugin for a session. The plugin creates one, takes one from a pool or waits in a queue, and tells agent-qa what to connect to. Its progress, such as
waiting for pool shoporuploading app (84 MB), prints in the run and on the dashboard's live run page. Progress never extendsconnectTimeout. - agent-qa connects with Playwright or WebdriverIO and runs the test. A plugin that returns a link to the session, such as a BrowserStack dashboard page, has it printed as
Remote <name>: <link>when the test starts, and recorded with the run. - When the test ends, the plugin is told the outcome if it wants it, which is how BrowserStack marks sessions, and then releases the session. That happens whether the test passed, failed, timed out or was cancelled.
- When the run ends, agent-qa stops each plugin process.
A suite holds one session for all of its tests.
Sessions are released even when a run is stopped. On Ctrl+C, plugin sessions get up to five seconds to release before agent-qa exits with code 130. If agent-qa is killed outright, the plugin notices its parent is gone and releases what it holds before it exits.
When a remote cannot serve a test, the test is refused before it starts, with one sentence that says why. Only the tests placed on that remote fail; the rest of the run goes on, and the run exits 2. The sentence names one of these failure kinds:
| Kind | Meaning |
|---|---|
auth | The service refused the credentials. |
capacity | No free session, a full pool, or a rate limit. |
unreachable | The service could not be reached, or a tunnel did not start. |
version | The service does not support this agent-qa's Playwright version. |
refused | The service refused the request, or an option or setting is wrong. A path such as settings.profile points at the line to fix. |
timeout | The session did not arrive within connectTimeout. |
ended, lost | The session ended, or the connection dropped, while the test ran. |
The run's analysis counts a refused test as an environment problem, not a flaky one.
Security
- A plugin is a separate process. Its code never runs inside agent-qa. It talks to agent-qa over its standard input and output, and anything else it writes there stops it.
- Credentials reach it only in messages. A plugin's environment holds only the path, home, temp, locale, time zone, proxy and certificate variables. Your shell's other variables never reach it.
- What it sends back is masked. Connection URLs, tokens and live-view links a plugin marks are masked in logs, records and output, in their encoded forms too. A plugin's own log is masked and shown only at the
debuglog level. - Connection secrets stay connection secrets. A secret used in
options,settingsorcapabilitiesis used only to connect to the service. Steps, expected values, target URLs and hooks cannot use it. - Only you add plugins. The dashboard and MCP show a plugins entry without its options, and neither can add, change or remove an entry or a plugin remote; you edit those in the config file. The one exception is the subscription entry, which
config.auth.setcan add. - Vendors keep their own records. Some services record sessions and keep the replay on their dashboard, behind their login. agent-qa's masking does not reach those replays. Use test credentials.
Third-party and your own plugins
Any npm package with an agent-qa plugin manifest can be an entry. Name it exactly with package, install it with your package manager, and agent-qa checks that it speaks a plugin protocol it supports. While you build one, point path at its folder. See Write a plugin.
Skills for coding agents
Each first-party plugin ships a skill with its service's settings and refusals, in the package's skill/ folder. plugins describe names the folder. Copy it next to agent-qa's own skills, such as .claude/skills/agent-qa-plugin-kernel/ for Claude Code or .agents/skills/agent-qa-plugin-kernel/ for Codex. See Skills.