# Test against a production instance

To test your application's signed-in features against your production instance, sign test users in with sign-in tokens instead of enabling [**Test mode**](https://clerk.com/docs/guides/development/testing/test-emails-and-phones.md#set-up-test-mode). Clerk doesn't send these users a verification email. Use three features that already work in production:

- **Test users created with the [Backend API](https://clerk.com/docs/reference/backend-api)**, whose email addresses start out verified.
- **[Sign-in tokens](https://clerk.com/docs/reference/backend/sign-in-tokens/create-sign-in-token.md)**, which the `@clerk/testing` helpers use to sign in from the browser. Only your server can create one, because it takes your Secret Key. The sign-in skips first-factor verification, but not [multi-factor authentication (MFA)](https://clerk.com/docs/guides/configure/auth-strategies/sign-up-sign-in-options.md#multi-factor-authentication) or [session tasks](https://clerk.com/docs/guides/configure/session-tasks.md).
- **[Testing Tokens](https://clerk.com/docs/guides/development/testing/overview.md#testing-tokens)**, which let your test browser through bot protection.

To test Clerk's own sign-up and sign-in flows, such as entering a verification code, use a development instance with **Test mode** enabled instead. See [Test emails and phones](https://clerk.com/docs/guides/development/testing/test-emails-and-phones.md).

## Why you shouldn't enable Test mode in production

Every development instance has **Test mode** enabled by default. You can enable it on a production instance, but this is highly discouraged. On a production instance, it works for anyone who can reach your app, not only your test suite:

- If your instance offers sign-in with an email or SMS code, anyone can sign in as a user whose email address contains `+clerk_test`, or whose phone number is a [fictional test number](https://clerk.com/docs/guides/development/testing/test-emails-and-phones.md#phone-numbers), by entering the code `424242`.
- Anyone can sign up with a `+clerk_test` subaddress of your company's domain, such as `jane+clerk_test@example.com`, and verify it with `424242`. That gives them an account with a verified email address on that domain, without access to its inbox. If your app grants access based on a user's email domain, that's a way in.

## Before you start

- Set up `@clerk/testing` for [Cypress](https://clerk.com/docs/guides/development/testing/cypress/overview.md) or [Playwright](https://clerk.com/docs/guides/development/testing/playwright/overview.md), including the `clerkSetup()` call. For Cypress, also register the [custom commands](https://clerk.com/docs/guides/development/testing/cypress/custom-commands.md) with `addClerkCommands()`. The examples in this guide need `@clerk/testing` 1.11.0 or later.
- Run your tests against a deployment of your app on your production domain or one of its subdomains, such as `https://staging.yourdomain.com`. Production keys work only on your production domain, so your app can't load Clerk from `localhost` with them. There's a workaround for [using production keys in local development](https://clerk.com/docs/guides/development/troubleshooting/using-production-keys-in-development.md), but it isn't officially supported. If you deploy to a subdomain:
  - If [**Allowed subdomains**](https://clerk.com/docs/guides/dashboard/dns-domains/subdomain-allowlist.md) is enabled, add the subdomain to the list.
  - If your app sets [`authorizedParties`](https://clerk.com/docs/reference/backend/authenticate-request.md#authenticaterequestoptions), add the subdomain's origin to it.
- In the environment that runs your tests, set:
  - `CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` to your production instance's Publishable Key and Secret Key.
  - `E2E_CLERK_USER_EMAIL` to your test user's email address. The next step explains which address to choose.
- Install `@clerk/backend`, which the examples in this guide use to manage test users:

  **npm**

  filename: terminal

  ```sh
  npm i @clerk/backend --save-dev
  ```

  **yarn**

  filename: terminal

  ```sh
  yarn add -D @clerk/backend
  ```

  **pnpm**

  filename: terminal

  ```sh
  pnpm add @clerk/backend -D
  ```

> Your production Secret Key gives full access to your production users. Store it as a secret in your continuous integration (CI) provider, expose it only to the jobs that run these tests, and never commit it or load it in browser code. For example, if you're using GitHub Actions, see [Using secrets in GitHub Actions](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions).

1. ## Choose email addresses for your test users

   Even when your tests never open an inbox, Clerk can send email to a test user. For example, it can send a new device sign-in notification when a test signs in from a fresh browser. Give each test user an address that won't bounce:

   - **An address at `example.com`**, such as `e2e-checkout@example.com`. [RFC 2606](https://www.rfc-editor.org/rfc/rfc2606) reserves `example.com`, `example.net`, and `example.org` for documentation and testing, so they can't receive mail. Clerk doesn't send production email to these domains or their subdomains, so nothing bounces.
   - **An address at a domain you control**, such as a subdomain you set aside for testing, if your tests need to read an email (for example, an invitation your app sends). Ensure the domain accepts mail for the address, for example, with a catch-all mailbox.

   Don't use a made-up mailbox on a real domain, such as a random address at a public email provider, and don't use `+clerk_test` subaddresses of real domains. With **Test mode** disabled, Clerk treats them as ordinary addresses and sends them email, which bounces or reaches someone else's inbox.
2. ## Create your test users

   Create test users with the [Backend API](https://clerk.com/docs/reference/backend-api) rather than through your sign-up form. Email addresses that you add with [`createUser()`](https://clerk.com/docs/reference/backend/user/create-user.md) start out verified, so Clerk sends no verification email.

   Keep a small, fixed set of test users and reuse them across runs, rather than creating new users on every run. The following example creates the test user only if it doesn't exist yet, and both sign-in examples later in this guide call `ensureTestUser()`. It sets [private metadata](https://clerk.com/docs/guides/users/extending.md#private-metadata) on the user so your backend can tell test users apart from real ones, for example, in webhook handlers.

   The example also refuses to use an existing user that doesn't have that metadata. Your tests sign in as whichever user has the address, so this check stops them from signing in as a real user if the address is ever wrong.

   filename: e2e/test-users.ts

   ```ts
   import { createClerkClient } from '@clerk/backend'

   export async function ensureTestUser(emailAddress: string) {
     const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })

     const { data } = await clerkClient.users.getUserList({ emailAddress: [emailAddress] })
     if (data.length > 0) {
       if (data[0].privateMetadata.e2eTestUser !== true) {
         throw new Error(`${emailAddress} belongs to a user that isn't a test user`)
       }
       return data[0]
     }

     return clerkClient.users.createUser({
       emailAddress: [emailAddress],
       // The tests sign in with a sign-in token, so the user doesn't need a password.
       // If password is the only sign-in method on your instance, pass a `password` instead.
       skipPasswordRequirement: true,
       privateMetadata: { e2eTestUser: true },
     })
   }
   ```

   If your instance requires other fields to sign up, such as a username or a phone number, pass them to `createUser()` as well. If several CI jobs can start before the test users exist, create the test users beforehand (for example, from a script) so two jobs don't create the same user.
3. ## Sign in your test users

   Sign the test user in with a sign-in token rather than with a password or a verification code. A sign-in token completes the sign-in regardless of your instance's verification or [Device Trust](https://clerk.com/docs/guides/protect/device-trust.md) settings, as long as the test user doesn't have MFA enabled. It doesn't complete session tasks, so see [Limitations](#limitations) if your instance requires MFA setup or an Organization. The `@clerk/testing` helpers also apply your Testing Token to Clerk's requests, so bot protection doesn't block the test browser.

   **Cypress**

   The [`cy.clerkSignIn()`](https://clerk.com/docs/guides/development/testing/cypress/custom-commands.md) command accepts a sign-in token through the `ticket` strategy. Creating a sign-in token takes your Secret Key, so do it in a [Cypress task](https://docs.cypress.io/api/commands/task), which runs in Node.js rather than in the browser.

   The following example registers a `createSignInToken` task in your Cypress config. The task gets the test user with the `ensureTestUser()` helper from the previous step, which creates it if needed, and returns a sign-in token that expires in five minutes.

   filename: cypress.config.ts

   ```ts
   import { createClerkClient } from '@clerk/backend'
   import { clerkSetup } from '@clerk/testing/cypress'
   import { defineConfig } from 'cypress'
   import { ensureTestUser } from './e2e/test-users'

   export default defineConfig({
     e2e: {
       async setupNodeEvents(on, config) {
         on('task', {
           async createSignInToken(emailAddress: string) {
             const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })
             const user = await ensureTestUser(emailAddress)

             const signInToken = await clerkClient.signInTokens.createSignInToken({
               userId: user.id,
               expiresInSeconds: 300,
             })
             return signInToken.token
           },
         })

         // clerkSetup() loads your .env files, so read the email address after it
         const clerkConfig = await clerkSetup({ config })
         clerkConfig.env.E2E_CLERK_USER_EMAIL = process.env.E2E_CLERK_USER_EMAIL

         return clerkConfig
       },
       baseUrl: 'https://staging.yourdomain.com', // Your app, on your production domain
     },
   })
   ```

   The following example signs the test user in with the token, waits for Clerk to load the user, and then checks that a protected page loads. As with every `cy.clerkSignIn()` call, first visit an unprotected page that loads Clerk.

   filename: cypress/e2e/app.cy.ts

   ```ts
   it('signed-in user can open the dashboard', () => {
     // Navigate to an unprotected page that loads Clerk
     cy.visit('/')

     cy.task<string>('createSignInToken', Cypress.env('E2E_CLERK_USER_EMAIL')).then((ticket) => {
       cy.clerkSignIn({ strategy: 'ticket', ticket })
     })
     cy.window().its('Clerk.user').should('not.be.null')

     // Ensure the user can access a protected page
     cy.visit('/dashboard')
     cy.contains('h1', 'Dashboard')

     // Test your app's features here
   })
   ```

   **Playwright**

   Pass the test user's email address to [`clerk.signIn()`](https://clerk.com/docs/guides/development/testing/playwright/test-helpers.md#clerk-signin) with the `emailAddress` parameter. The helper finds the user, creates a sign-in token with your Secret Key, and signs in with it.

   The following example extends the global setup from [Reuse auth state across tests](https://clerk.com/docs/guides/development/testing/playwright/test-authenticated-flows.md). It creates the test user if needed, signs in once, and saves the auth state for your other tests to reuse.

   filename: e2e/global.setup.ts

   ```ts
   import { clerk, clerkSetup } from '@clerk/testing/playwright'
   import { test as setup } from '@playwright/test'
   import path from 'path'
   import { ensureTestUser } from './test-users'

   // Setup must be run serially
   setup.describe.configure({ mode: 'serial' })

   const authFile = path.join(__dirname, '../playwright/.clerk/user.json')

   setup('global setup', async ({}) => {
     await clerkSetup()
     await ensureTestUser(process.env.E2E_CLERK_USER_EMAIL!)
   })

   setup('authenticate and save state to storage', async ({ page }) => {
     // Navigate to an unprotected page that loads Clerk
     await page.goto('/')

     await clerk.signIn({
       page,
       emailAddress: process.env.E2E_CLERK_USER_EMAIL!,
     })

     // Ensure the user can access a protected page
     await page.goto('/dashboard')
     await page.waitForSelector("h1:has-text('Dashboard')")

     await page.context().storageState({ path: authFile })
   })
   ```

   To load the saved auth state in your tests, see [Load the stored auth state in your tests](https://clerk.com/docs/guides/development/testing/playwright/test-authenticated-flows.md#load-the-stored-auth-state-in-your-tests).
4. ## Clean up test users

   If your tests create extra users, for example, to test what a brand-new account sees, delete them when the run finishes with [`deleteUser()`](https://clerk.com/docs/reference/backend/user/delete-user.md). Give these users a recognizable address, such as `e2e-run-1234-new@example.com`, so a cleanup script can find any that a failed run left behind. Have the script check for the `e2eTestUser` private metadata before it deletes a user, so it never deletes a real one.

## Limitations

- **The helpers' code strategies need Test mode.** The `email_code` and `phone_code` strategies of the sign-in helpers require `+clerk_test` email addresses or fictional phone numbers and the `424242` code, which work only in **Test mode**.
- **Password sign-in can be interrupted by Device Trust.** The `password` strategy signs in from a fresh browser on every run. If [Device Trust](https://clerk.com/docs/guides/protect/device-trust.md) is enabled, Clerk asks for a second factor on a new device, which the helpers can't complete.
- **Sign-in tokens don't satisfy MFA.** If a test user has MFA enabled, a sign-in token completes only the first factor, and the helpers can't complete the second. Don't enable MFA on test users.
- **Sign-in tokens don't complete session tasks.** If your instance requires a [session task](https://clerk.com/docs/guides/configure/session-tasks.md), such as setting up MFA or choosing an Organization, the test user's session stays pending after sign-in, and by default your app treats a pending session as signed out.

For more information, feedback, or issues, visit the [`@clerk/testing`](https://github.com/clerk/javascript/tree/main/packages/testing) package.

---

## Sitemap

[Overview of all docs pages](https://clerk.com/docs/llms.txt)
