Clerk logo

Clerk Docs

Ctrl + K
Go to clerkstage.dev
Check out a preview of our new docs.

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.js
1
Cypress.Commands.add(`signOut`, () => {
2
cy.log(`sign out by clearing all cookies.`);
3
cy.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.js
1
Cypress.Commands.add(`signIn`, () => {
2
cy.log(`Signing in.`);
3
cy.visit(`/`);
4
5
cy.window()
6
.should((window) => {
7
expect(window).to.not.have.property(`Clerk`, undefined);
8
expect(window.Clerk.isReady()).to.eq(true);
9
})
10
.then(async (window) => {
11
await cy.clearCookies({ domain: window.location.domain });
12
const res = await window.Clerk.client.signIn.create({
13
identifier: "example@clerk.dev",
14
password: "clerkpassword1234",
15
});
16
17
await window.Clerk.setActive({
18
session: res.createdSessionId,
19
});
20
21
cy.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.

1
import { SignedIn, SignedOut } from "@clerk/nextjs";
2
3
export default function Dashboard() {
4
return (
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.js
1
describe("Signed out", () => {
2
it("should navigate to the dashboard in a signed out state", () => {
3
// open dashboard page
4
cy.visit("http://localhost:3000/dashboard");
5
6
// check h1 says signed out
7
cy.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.js
1
describe("Signed in", () => {
2
beforeEach(() => {
3
cy.session('signed-in', () => {
4
cy.signIn();
5
});
6
});
7
8
it("navigate to the dashboard", () => {
9
// open dashboard page
10
cy.visit("http://localhost:3000/dashboard");
11
12
// check h1 says signed in
13
cy.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.

1
import { withServerSideAuth } from "@clerk/nextjs/ssr";
2
3
export const getServerSideProps = withServerSideAuth(async ({ req }) => {
4
const { sessionId, getToken } = req.auth;
5
const sessionToken = await getToken();
6
7
return {
8
props: {
9
signedIn: sessionId != null,
10
sessionToken: sessionToken,
11
sessionId: sessionId,
12
},
13
};
14
});
15
16
export default function Page({ signedIn, sessionToken, sessionId }) {
17
return (
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.js
1
describe("Signed out", () => {
2
it("should navigate to the ssr page in a signed out state", () => {
3
// open dashboard page
4
cy.visit("http://localhost:3000/dashboard-ssr", {
5
failOnStatusCode: false,
6
});
7
8
// check h1 says signed in
9
cy.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.js
1
describe("Signed in", () => {
2
beforeEach(() => {
3
cy.session('signed-in', () => {
4
cy.signIn();
5
});
6
});
7
8
it("SSR: navigate to the ssr dashboard", () => {
9
// open dashboard page
10
cy.visit("http://localhost:3000/dashboard-ssr", {
11
failOnStatusCode: false,
12
});
13
14
// check h1 says signed in
15
cy.get("h1").contains("Signed in");
16
});
17
});
18

Was this helpful?

Clerk © 2023