Skip to Content
Clerk logo

Clerk Docs

Ctrl + K
Go to clerk.com

Testing with Cypress

Cypress is a Javascript End to End Testing framework that is popular, easy to use, and very powerful. This document will go over the setup you need to write authenticated tests with Clerk. This guide will assume you're somewhat familiar with Clerk and Cypress.

If you would like to see a full example of a Cypress project with Clerk, check out this repo.

Since Cypress does not allow third-party cookies by default, you might be interested in the experimental Cookieless Dev mode.

Cypress commands

Easiest way to test with Clerk is to create sign in and sign out helper thats you can re-use in all of your tests.

Sign out helper

This helper is simple, and just clears all cookies, which effectively puts the browser back into a "signed out" state

commands.js
Cypress.Commands.add(`signOut`, () => { cy.log(`sign out by clearing all cookies.`); cy.clearCookies({ domain: null }); });

Sign in helper

This helper is dependent on your authentication strategy, and just signs in an existing user. The following helper uses an email/pw strategy to sign in the user. If you're using phone codes, you should use Test emails and phones.

The helper will read from the Cypress environment variables. To define the email and password, a cypress.env.json can be created.

Be careful not to call this method once per test. You will get rate-limited. Instead, sign in once, then re-use the same session for multiple tests. See below for an example.

commands.js
Cypress.Commands.add(`signIn`, () => { cy.log(`Signing in.`); cy.visit(`/`); cy.window() .should((window) => { expect(window).to.not.have.property(`Clerk`, undefined); expect(window.Clerk.isReady()).to.eq(true); }) .then(async (window) => { await cy.clearCookies({ domain: window.location.domain }); const res = await window.Clerk.client.signIn.create({ identifier: Cypress.env(`test_email`), password: Cypress.env(`test_password`), }); await window.Clerk.setActive({ session: res.createdSessionId, }); cy.log(`Finished Signing in.`); }); });
cypress.env.json
{ "test_email": "example@clerk.dev", "test_password": "clerkpassword1234" }

Simple tests

Now that some simple commands have been created, let's see how to write some basic tests.

import { SignedIn, SignedOut } from "@clerk/nextjs"; export default function Dashboard() { return ( <> <SignedIn> <h1>Signed in</h1> </SignedIn> <SignedOut> <h1>Signed out</h1> </SignedOut> </> ); }

Test signed out

app.cy.js
describe("Signed out", () => { it("should navigate to the dashboard in a signed out state", () => { // open dashboard page cy.visit("http://localhost:3000/dashboard"); // check h1 says signed out cy.get("h1").contains("Signed out"); }); });

Test signed in

Note the use of before and beforeEach. You want to be careful about signing in too many times quickly as you might get rate limited. So, you should only sign in once for all your tests inside of before. Then, since Cypress will clear cookies by default after tests, you need to use beforeEach to maintain these cookies and keep the browser in the signedIn state.

app.cy.js
describe("Signed in", () => { beforeEach(() => { cy.session('signed-in', () => { cy.signIn(); }); }); it("navigate to the dashboard", () => { // open dashboard page cy.visit("http://localhost:3000/dashboard"); // check h1 says signed in cy.get("h1").contains("Signed in"); }); });

SSR tests

You need to make some slight modifications to test in an SSR context. Because Clerk uses short-lived JWTs, it uses middleware that will re-load the JWT from a different page if necessary. This page might return a 401, so Cypress needs to be told to ignore the 401 and continue. Passing failOnStatusCode: false into cy.visit accomplishes just that.

Here's a simple SSR page, where the auth check is done on the server side, and returned to the frontend.

import { withServerSideAuth } from "@clerk/nextjs/ssr"; export const getServerSideProps = withServerSideAuth(async ({ req }) => { const { sessionId, getToken } = req.auth; const sessionToken = await getToken(); return { props: { signedIn: sessionId != null, sessionToken: sessionToken, sessionId: sessionId, }, }; }); export default function Page({ signedIn, sessionToken, sessionId }) { return ( <> <h1>{signedIn ? "Signed in" : "Signed out"}</h1> <div>sessionId: {sessionId}</div> <div>sessionToken: {sessionToken}</div> </> ); }

Test signed out

app.cy.js
describe("Signed out", () => { it("should navigate to the ssr page in a signed out state", () => { // open dashboard page cy.visit("http://localhost:3000/dashboard-ssr", { failOnStatusCode: false, }); // check h1 says signed in cy.get("h1").contains("Signed out"); }); })

Test signed in

Note the use of before and beforeEach. You want to be careful about signing in too many times quickly as you might get rate limited. So, you should only sign in once for all your tests inside of before. Then, since Cypress will clear cookies by default after tests, you need to use beforeEach to maintain these cookies and keep the browser in the signedIn state.

app.cy.js
describe("Signed in", () => { beforeEach(() => { cy.session('signed-in', () => { cy.signIn(); }); }); it("SSR: navigate to the ssr dashboard", () => { // open dashboard page cy.visit("http://localhost:3000/dashboard-ssr", { failOnStatusCode: false, }); // check h1 says signed in cy.get("h1").contains("Signed in"); }); });

What did you think of this content?

Clerk © 2023