← All posts
·environmentscredentialsciplaywright

I stopped hardcoding test logins: multi-environment accounts with @account

Every end-to-end test I've ever written opens the same way: log in. And the login step is exactly where my test suites have always rotted.

The first version is innocent. You're iterating locally, so you type the dev account straight into the spec:

await page.getByLabel('Email').fill('admin@test.dev');
await page.getByLabel('Password').fill('hunter2');

It works. You commit it. Six weeks later that password is in the git history forever, the spec only runs against localhost, and the moment someone wants to run it against staging they copy the whole file and swap two strings. Now you have two specs that drift apart. Multiply by every flow that needs auth.

The "proper" fix — process.env.PASSWORD plus a .env file — is better, but it pushes the problem around rather than solving it. You still have to wire the env var, remember which name maps to which environment, keep the .env out of git, and re-do all of it in CI. It's busywork that nobody enjoys and everybody gets subtly wrong.

What I actually wanted

I wanted to say "log in and add a todo" and have the test sign in as the right user for whatever environment I'm pointing at — without me ever putting the password in the file the agent writes.

That's what the @account feature in Hover does, and it's become the part of the VS Code extension I'd miss most.

How it works

In the extension's Environments view I define a target once — say staging — with a URL and a test account. The password goes into VS Code's SecretStorage, not a file. Then in chat I just mention it:

@account log in, open the orders page, and check the latest order is visible

The agent reads the account for the active environment, logs in, and explores the flow. So far that's just convenience. The part that changed how I work is what lands in the spec when I hit Save as Spec:

test('view latest order', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.HOVER_STAGING_EMAIL!);
  await page.getByLabel('Password').fill(process.env.HOVER_STAGING_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  // ...
});

The credentials are parameterized into process.env references. The real email and password are never written into the spec, never into the JSDoc header, never into the .hover sidecar. The thing that checks into git carries the shape of the login, not the secret.

One spec, three environments

Because the account is keyed to an environment and the spec only references env-var names, the same file runs anywhere:

# local
BASE_URL=http://localhost:5173 npx playwright test

# staging
BASE_URL=https://staging.example.com npx playwright test

You point BASE_URL at a target, supply the matching HOVER_<ENV>_* values, and the same spec drives local, staging, or a PR preview. No forked files, no per-environment copy. The thing that was a maintenance tax — keeping N copies of the login flow in sync — just isn't there anymore.

CI without a credential-shuffling ritual

The names are the contract, so moving to CI is mechanical. The extension can export the HOVER_<ENV>_* names straight to your CI secrets, and your workflow reads them like any other env var:

- name: E2E
  env:
    BASE_URL: ${{ vars.STAGING_URL }}
    HOVER_STAGING_EMAIL: ${{ secrets.HOVER_STAGING_EMAIL }}
    HOVER_STAGING_PASSWORD: ${{ secrets.HOVER_STAGING_PASSWORD }}
  run: npx playwright test __vibe_tests__

And — this is the whole point of the architecture — there's no AI in that step. The agent authored the spec once, parameterized the secrets out of it, and stepped away. CI runs plain @playwright/test with no model, no tokens, no key.

The payoff

The discipline I always meant to follow — secrets out of the repo, one spec per flow, the same artifact across environments — is now the path of least resistance instead of the thing I cut corners on at 6pm. I say @account, the agent logs in, and the file it leaves behind is clean.

If you've ever grepped your repo for a leaked test password, you know why that matters.

Try Hover on your own app.

Install the VS Code extension. Author tests with AI, ship plain Playwright.

Install on VS Code Marketplace →