Skip to content

fix(auth): persona login races async-rendered login forms — fields are queried once with no wait #53

Description

@ivndev001

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions