API testing
Hover tests your API layer in the same run as the UI. As the agent drives a flow, Hover watches what the app actually calls — and turns the calls worth pinning into a plain @playwright/test *.api-test.spec.ts.
Passive capture, no proxy
While the agent drives the UI, Hover passively captures the app's xhr / fetch traffic off the same CDP connection — no MITM proxy, no cert to install, nothing in front of your network. The requests the browser makes as you exercise a flow are simply observed.
The honest trade-off: this sees browser-originated requests (what the page's JS fetches), not server-to-server calls that never touch the browser. If the check you want to assert is a request the page actually makes, Hover can see it.
The flow
- Drive the UI with the grounded tools, as usual.
capture_requeststo see what the flow hit — method, url, status, content-type, request body, response shape. Filter withurlContains/methodto narrow a busy page.- The agent picks the checks worth keeping — a contract (a GET returns the expected shape), a data mutation (a POST persists), an authz boundary (an endpoint rejects an unauthenticated caller). Most flows need 0–1 API specs; this is selective, not mechanical.
- Verify with
replay_requestbefore crystallizing, so no status code is confabulated. To prove a "requires auth" boundary, re-send withauthenticated: false— a fresh context with no session — and confirm it returns 401 / 403. crystallize_api_specwrites the verified checks to__vibe_tests__/<slug>.api-test.spec.tsusing Playwright'srequestfixture.
record == replay holds for the API layer too: the captured call is the asserted call. The agent decides whether an API spec is worth it during test_app Phase 3.5 — a distinct phase from the UI crystallize.
What lands on disk
A short *.api-test.spec.ts — a GET that should return 200, plus an authz check that an unauthenticated caller is rejected:
import { test, expect } from '@playwright/test';
test('GET /api/orders returns the current user\'s orders', async ({ request }) => {
const res = await request.get('/api/orders');
expect(res.status()).toBe(200);
const body = await res.json();
expect(Array.isArray(body.orders)).toBe(true);
});
test('GET /api/orders rejects an unauthenticated caller', async ({ request }) => {
// fresh context, no session — replay_request confirmed this before crystallizing
const res = await request.get('/api/orders', {
headers: { cookie: '' },
});
expect([401, 403]).toContain(res.status());
});
Credentials and sessions are never embedded — the authenticated case runs under the suite's stored auth state (see Login & credentials); the unauthenticated case deliberately carries none.
Still plain Playwright
An *.api-test.spec.ts is standard @playwright/test using the built-in request fixture. It runs in CI with npx playwright test, no agent and no Hover runtime in the loop.