What happened
A run with `as: "admin"` fails at login with:
```
auth persona 'admin': no input matching field 'password' on http://localhost:5173/login. Rename the key to the field's name/id/type/data-testid/aria-label/placeholder, or use a selector as the key
```
The field exists — the key `password` matches `name="password"` — but the login form renders after an async capability check (the SPA mounts, fires a `GET /api/admin/auth/methods` query, and only then renders the form). The persona matcher runs before that query resolves, so any app whose login form is conditional on an async read is unloggable via `auth` personas. This is likely the common case for SPAs (feature flags, capability probes, tenant checks).
Root cause
`dist/services/login.js` — `performLogin`:
```js
await adapter.open(loginUrl, budget);
...
for (const [key, value] of Object.entries(persona.fields)) {
const node = await findFirst(adapter, fieldCandidates(key));
if (!node) throw new AuthError(...)
```
`findFirst` iterates the candidate queries and returns the first hit, but no candidate is ever waited on — after `open` resolves (networkidle), each field is queried exactly once. Solid/React apps frequently mount their form a tick after networkidle (the methods fetch itself may even start after the idle window).
The same file already has the right primitive for the post-submit side: `persona.expect` goes through `adapter.waitFor({ query, timeout })`. The field lookup needs the same treatment.
Suggested fix
Wrap the field (and submit) lookup in the existing `adapter.waitFor` with the remaining login budget, per candidate or across candidates — e.g. try `waitFor` on each candidate most-specific-first until one resolves or the budget expires, then throw the same `AuthError`. Config surface stays unchanged.
Context
Hit on v1.8.0 (stdio server via npx), web target `adapter: "browser"` (managed headless Chrome for Testing 149), against a SolidJS SPA whose `/login` renders the password form only after `GET /api/admin/auth/methods` resolves (`{"oidc":true,"local":true}`). Workaround in the meantime: omit `as` and make "log in" the driver's first goal step — the driver's own observe/wait loop beats the race.
What happened
A run with `as: "admin"` fails at login with:
```
auth persona 'admin': no input matching field 'password' on http://localhost:5173/login. Rename the key to the field's name/id/type/data-testid/aria-label/placeholder, or use a selector as the key
```
The field exists — the key `password` matches `name="password"` — but the login form renders after an async capability check (the SPA mounts, fires a `GET /api/admin/auth/methods` query, and only then renders the form). The persona matcher runs before that query resolves, so any app whose login form is conditional on an async read is unloggable via `auth` personas. This is likely the common case for SPAs (feature flags, capability probes, tenant checks).
Root cause
`dist/services/login.js` — `performLogin`:
```js
await adapter.open(loginUrl, budget);
...
for (const [key, value] of Object.entries(persona.fields)) {
const node = await findFirst(adapter, fieldCandidates(key));
if (!node) throw new AuthError(...)
```
`findFirst` iterates the candidate queries and returns the first hit, but no candidate is ever waited on — after `open` resolves (networkidle), each field is queried exactly once. Solid/React apps frequently mount their form a tick after networkidle (the methods fetch itself may even start after the idle window).
The same file already has the right primitive for the post-submit side: `persona.expect` goes through `adapter.waitFor({ query, timeout })`. The field lookup needs the same treatment.
Suggested fix
Wrap the field (and submit) lookup in the existing `adapter.waitFor` with the remaining login budget, per candidate or across candidates — e.g. try `waitFor` on each candidate most-specific-first until one resolves or the budget expires, then throw the same `AuthError`. Config surface stays unchanged.
Context
Hit on v1.8.0 (stdio server via npx), web target `adapter: "browser"` (managed headless Chrome for Testing 149), against a SolidJS SPA whose `/login` renders the password form only after `GET /api/admin/auth/methods` resolves (`{"oidc":true,"local":true}`). Workaround in the meantime: omit `as` and make "log in" the driver's first goal step — the driver's own observe/wait loop beats the race.