Testing With Cypress
Use Cypress to write end-to-end tests with Clerk
Introduction
Cypress is a Javascript End to End Testing framework this 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
Sample repo: https://github.com/clerkinc/example-cypress-nextjs
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.js1Cypress.Commands.add(`signOut`, () => {2cy.log(`sign out by clearing all cookies.`);3cy.clearCookies({ domain: null });4});5
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.
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.js1Cypress.Commands.add(`signIn`, () => {2cy.log(`Signing in.`);3cy.visit(`/`);45cy.window()6.should((window) => {7expect(window).to.not.have.property(`Clerk`, undefined);8expect(window.Clerk.isReady()).to.eq(true);9})10.then(async (window) => {11await cy.clearCookies({ domain: window.location.domain });12const res = await window.Clerk.client.signIn.create({13identifier: "example@clerk.dev",14password: "clerkpassword1234",15});1617await window.Clerk.setActive({18session: res.createdSessionId,19});2021cy.log(`Finished Signing in.`);22});23});24
Simple tests
Now that we have some commands. let's see how to write some basic tests. We'll use the very simple HTML below.
1import { SignedIn, SignedOut } from "@clerk/nextjs";23export default function Dashboard() {4return (5<>6<SignedIn>7<h1>Signed in</h1>8</SignedIn>9<SignedOut>10<h1>Signed out</h1>11</SignedOut>12</>13);14}
Test signed out
app.cy.js1describe("Signed out", () => {2it("should navigate to the dashboard in a signed out state", () => {3// open dashboard page4cy.visit("http://localhost:3000/dashboard");56// check h1 says signed out7cy.get("h1").contains("Signed out");8});9});10
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.js1describe("Signed in", () => {2beforeEach(() => {3cy.session('signed-in', () => {4cy.signIn();5});6});78it("navigate to the dashboard", () => {9// open dashboard page10cy.visit("http://localhost:3000/dashboard");1112// check h1 says signed in13cy.get("h1").contains("Signed in");14});15});
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 we need to tell cypress to ignore the 401 and continue. Passing `failOnStatusCode: false` into `cy.visit` accomplishes just that.
Here's a simple SSR page, where we do the auth check on the server side, and return it to the frontend.
1import { withServerSideAuth } from "@clerk/nextjs/ssr";23export const getServerSideProps = withServerSideAuth(async ({ req }) => {4const { sessionId, getToken } = req.auth;5const sessionToken = await getToken();67return {8props: {9signedIn: sessionId != null,10sessionToken: sessionToken,11sessionId: sessionId,12},13};14});1516export default function Page({ signedIn, sessionToken, sessionId }) {17return (18<>19<h1>{signedIn ? "Signed in" : "Signed out"}</h1>20<div>sessionId: {sessionId}</div>21<div>sessionToken: {sessionToken}</div>22</>23);24}
Test signed out
app.cy.js1describe("Signed out", () => {2it("should navigate to the ssr page in a signed out state", () => {3// open dashboard page4cy.visit("http://localhost:3000/dashboard-ssr", {5failOnStatusCode: false,6});78// check h1 says signed in9cy.get("h1").contains("Signed out");10});11})
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.js1describe("Signed in", () => {2beforeEach(() => {3cy.session('signed-in', () => {4cy.signIn();5});6});78it("SSR: navigate to the ssr dashboard", () => {9// open dashboard page10cy.visit("http://localhost:3000/dashboard-ssr", {11failOnStatusCode: false,12});1314// check h1 says signed in15cy.get("h1").contains("Signed in");16});17});18