Menu

Developer documentation

End-to-end testing with real Signet sessions

/docs/testing

Status: shipped 2026-08-04.

@signet/testing is a dependency-free, repository-owned helper package for Playwright, Cypress, and API tests. It signs in through the ordinary POST /sign-in/email wire, reads the signed session credential exposed by Signet's certified bearer plugin, and installs the ordinary better-auth.session_token cookie. The resulting browser state is the same state an application gets after a real sign-in.

This is deliberately not a privileged "testing token." Clerk's testing token bypasses its edge bot detection; authentication is a separate operation. Signet has no in-binary bot scorer or challenge to bypass, so inventing a bypass token would add a production backdoor without solving a Signet problem. The helper cannot create users, ignore a ban or lockout, skip email admission, disable rate limits, bypass MFA, extend a session, or mint a session from an admin secret.

The package is marked private and is not published to npm under the proprietary product name. Install it from this repository or a vendored workspace path, for example:

{
  "devDependencies": {
    "@signet/testing": "file:../vendor/signet-testing"
  }
}

baseURL always means the full Signet auth API URL, including the configured base path: https://auth.example.com/api/auth.

Playwright

Use a dedicated test account created through your normal fixture/seed process. The password still passes every configured Signet policy.

import { test } from "@playwright/test"
import { signInAndInstallSession } from "@signet/testing/playwright"

test.beforeEach(async ({ context }) => {
  await signInAndInstallSession({
    context,
    baseURL: process.env.SIGNET_AUTH_URL!,
    email: process.env.E2E_USER_EMAIL!,
    password: process.env.E2E_USER_PASSWORD!,
  })
})

The request returns the set-auth-token value that Signet exposes from the real signed session cookie. The adapter installs it as an HttpOnly, SameSite=Lax cookie on the authentication origin; Secure follows the URL scheme. It does not write the raw database session token.

Cypress

import { signInAndInstallSession } from "@signet/testing/cypress"

beforeEach(() => {
  signInAndInstallSession({
    cy,
    baseURL: Cypress.env("SIGNET_AUTH_URL"),
    email: Cypress.env("E2E_USER_EMAIL"),
    password: Cypress.env("E2E_USER_PASSWORD"),
  })
})

Signet's authentication origin must be in the application's trusted-origin/CORS configuration. If the application and auth service use different origins, follow Cypress's current cross-origin test rules (cy.origin) for any subsequent browser commands which visit the auth origin. The cookie itself is installed for the host named by SIGNET_AUTH_URL, not for the application host.

API tests and cleanup

The core export is runner-independent:

import {
  authorizationHeader,
  signInWithPassword,
  signOut,
} from "@signet/testing"

const session = await signInWithPassword({
  baseURL: process.env.SIGNET_AUTH_URL!,
  email: process.env.E2E_USER_EMAIL!,
  password: process.env.E2E_USER_PASSWORD!,
})

const response = await fetch(`${process.env.SIGNET_AUTH_URL}/get-session`, {
  headers: authorizationHeader(session.token),
})

await signOut({ baseURL: process.env.SIGNET_AUTH_URL!, token: session.token })

signOut revokes the server-side session, not merely the local cookie. Use it in teardown when a test does not discard its entire database. Never print the returned token or put credentials in a committed fixture.

MFA is an explicit test boundary

Password sign-in for an MFA-enabled account creates a second-factor challenge rather than a session. The helper stops with TWO_FACTOR_REQUIRED and tells the caller to use either a dedicated non-MFA E2E account or the real TOTP/OTP/backup-code UI flow. It never completes or disables the factor behind the application's back. Tests for step-up or MFA behavior should drive those routes directly instead of using this setup shortcut.

The OpenAPI document and the reference page

Enter to open · Esc to close