# Secrets and sign-in

> Type passwords and codes without the test holding them, and sign in once for many tests.

Source: https://rehearsal.dev/retest/essentials/secrets-and-sign-in

A test types a password the way a person does, into the field on the page. But the test file never holds the password.

## Name a secret

The config says where each secret comes from. The test names it with `secret()`.

```ts filename="retest.config.ts"
secrets: { password: env('TASK_APP_PASSWORD') },
```

```ts filename="tests/sign-in.retest.ts"
await page.getByLabel('Password').fill(secret('password'))
```

- The test's process sends the secret's name. Retest's own process reads the value and types it.
- The variable an `env` source reads is removed from the test's environment.
- An `env` source is read once, when the run starts. A value that is missing, empty or shorter than four characters stops the run with exit code 2.
- However it is printed, the secret reads `{{password}}`.

## Secrets from a function

A source can be a function, for values that change, such as a one-time code. Retest calls it each time a `fill` uses the secret.

```ts filename="retest.config.ts"
secrets: {
  password: () => vault.read('password'),
  code: () => inbox.latestCode(),
},
```

- The function is called with `{ signal }`. Retest aborts the signal once it stops waiting, so pass it on, as to `fetch`.
- If the function throws or gives no text, that fill fails `setup_failed` and names the secret.
- The function is called as the fill begins. Wait for the page that asks for the code first, as in `await expect(page.getByLabel('Code')).toBeVisible()`.

## Where a secret may be typed

A secret is bound to the origins of the base URLs of the test's apps. `secretOrigins` adds more.

```ts filename="retest.config.ts"
secretOrigins: { password: ['https://auth.example.com'] },
```

On a page of any other origin, the fill fails `not_actionable` at once and nothing is typed. Retest checks the origin before it reads the value, and again just before it types.

## What is hidden

- Retest writes `{{name}}` in place of every value. That covers events, results, logs, app server output, browser logs, the terminal and every address it records.
- Page text the test reads is hidden before it reaches the test, so a check against it compares `{{password}}`.
- A command whose locator holds a whole secret value is refused, and never sent to the page.

> [!NOTE]
> Screenshots are not redacted. A secret the page shows appears in its screenshot. [Reports and evidence](https://rehearsal.dev/retest/essentials/reports-and-evidence.md) explains what Retest holds back.

## Sign in once

`test.setup(state, fn)` signs in the way a person would. When it passes, Retest saves the browser's cookies and the `localStorage` of the origins it visited, under the state's name.

```ts filename="tests/sign-in.retest.ts"
import { expect, secret, test } from '@rehearsal-labs/retest'

test.setup('signed-in', async ({ page }) => {
  await page.goto('/login')
  await page.getByLabel('User name').fill('alice')
  await page.getByLabel('Password').fill(secret('password'))
  await page.getByRole('button', { name: 'Sign in' }).click()
  await expect(page.getByTestId('account')).toHaveText('Signed in as alice')
})

test('shows the account', { state: 'signed-in' }, async ({ page }) => {
  await page.goto('/account')
  await expect(page.getByText('Signed in as alice')).toBeVisible()
})
```

- A setup runs before the first test that needs its state, once for each target those tests use.
- A test with `state` starts with the state restored. A test without it starts signed out.
- A setup that fails makes every test that needs it `not_run`, with the setup's failure as the reason.
- When you run some files only, Retest finds the setups they need in the other files and runs those too.
- The saved state holds session cookies, so Retest deletes it when the run ends. `sessionStorage` is not saved.

## Several people

Each app a test names gets its own browser context, with its own cookies and storage. Two apps on one site are two people, each signed in with their own setup.

```ts
test.setup('owner-account', { apps: ['owner'] }, async ({ owner }) => { /* sign in as the owner */ })
test.setup('member-account', { apps: ['member'] }, async ({ member }) => { /* sign in as the member */ })

test('the member reads the record the owner made', {
  apps: ['owner', 'member'],
  state: { owner: 'owner-account', member: 'member-account' },
}, async ({ owner, member }) => {
  const reference = 'record-' + randomUUID()
  // the owner makes the record under reference; the member opens it by reference
})
```

Find a shared record by a value the test made, such as `reference` above. A title another run also used could match the wrong record.
