July 2, 2024
Email verification with Playwright
When you sign up for a website, you often need to verify your email. This step is there to make sure you’re using a real email address that you actually own. Without it, anyone could use any email address they wanted, which could cause all sorts of problems.
That's why it’s so important to test this feature. If email verification doesn’t work properly, people could sign up with someone else’s email address, or legitimate users might not be able to register at all. This can lead to a lot of headaches for both the site and its users. Automating testing for this ensures it works correctly every time, preventing these issues from happening.
For this post we'll be using the code available in this repository.
To start off, let's quickly recap how email verification works:
- The user visits the sign-up page, where they create an account. After singing up they are taken to a verify email page.
- Depending on the flow, the user might have to log in again first.
- Usually, right after signing up, the user will receive an email containing the verification code they need to enter.
- The user enters the code they received. If successful, the user is redirected to the dashboard, and their account is now verified.
We'll need to automate this process. Luckily with Playwright we have access to all the necessary tools.
Playwright
We're using Playwright for this. If this is your first time using Playwright take a look at the guide. The provided repository in this post already has Playwright setup.
Mailisk
For extracting code from an email, we'll be using Mailisk which is an email testing service that has API endpoints for reading emails.
We'll be able to read emails sent to a subdomain like this <anything>@mynamespace.mailisk.net
. Where mynamespace
is the name of the namespace.
Sending emails to these addresses allows us to read them using the API, enabling us to automate the email verification process. We'll look at this part again later.
In order to use Mailisk, you'll need to sign up, create a namespace, and get the API key. The getting started guide has all of the necessary information.
For Node we'll use the mailisk
library:
npm install mailisk
Getting started
Let's write a simple email-verification.spec.ts
file.
The goal will be to test the following:
- Sign up
- Login
- Verify email
- Check if the user is redirected to the dashboard
We'll be using the app available in the example repository.
First, let's create a .env
file in the root of the project and add the following
MAILISK_API_KEY=<your-api-key>
MAILISK_NAMESPACE=<your-namespace>
We'll setup playwright.config.ts
to use the .env
file:
require("dotenv").config({ path: path.join(__dirname, "../.env") });
Writing the test
Now let's write the test:
import { test, expect } from "@playwright/test";
import { MailiskClient } from "mailisk";
const namespace = process.env.MAILISK_NAMESPACE;
const mailisk = new MailiskClient({ apiKey: process.env.MAILISK_API_KEY });
With this we've initilized the Mailisk client. Which we can then use in our test
test.describe("Test email verification", () => {
test("Should sign up, verify email, and login a new user", async ({ page }) => {
const testEmailAddress = `test.${Date.now()}@${namespace}.mailisk.net`;
});
});
First, we'll generate an email address, which we'll use throughout the entire test.
We're adding the current timestamp so that if we rerun the test, it will use a new email address. This prevents later tests from failing due to an older email being returned.
await page.goto("http://localhost:3000/register");
await page.fill("#email", testEmailAddress);
await page.fill("#password", "password");
await page.click('form button[type="submit"]');
// if the register was successful we should be redirected to the login screen
await expect(page).toHaveURL("http://localhost:3000/");
// Login as user
await page.fill("#email", testEmailAddress);
await page.fill("#password", "password");
await page.click('form button[type="submit"]');
// if the login was successful we should be redirected to the verify email screen, as we haven't verified our email yet
await expect(page).toHaveURL("http://localhost:3000/verify-email");
// at this point an email with the verification code will be sent by the backend
Then we sign up and log in. We are redirected to the verify email screen, where we have to enter the verification code sent to our email address.
Extracting the code from the email
let code;
// we wait for the email to arrive, we filter by it's prefix
const { data: emails } = await mailisk.searchInbox(namespace, {
to_addr_prefix: testEmailAddress,
});
const email = emails[0];
// we know that the code is the only number in the email, so we easily filter it out
code = email.text.match(/\d+/)[0];
expect(code).toBeDefined();
// now we enter the code and confirm our email
await page.fill("#email", testEmailAddress);
await page.fill("#code", code);
await page.click('form button[type="submit"]');
// we should be redirected to the dashboard as proof of a successful verification
await expect(page).toHaveURL("http://localhost:3000/dashboard");
We wait for the email to arrive by looking for an email with the prefix of our email address. We then filter out the code from the email and enter it into the form.
const { data: emails } = await mailisk.searchInbox(namespace, {
to_addr_prefix: testEmailAddress,
});
This is a simple filter using the "TO" address of the destination email. Check out the README for more advanced filtering.
We should now be redirected to the dashboard as proof of a successful verification.
And that's it. With this, we have automated the email verification process.
For reference. Here's how the final email-verification.spec.ts
file looks like:
import { test, expect } from "@playwright/test";
import axios from "axios";
import { MailiskClient } from "mailisk";
const namespace = process.env.MAILISK_NAMESPACE;
const mailisk = new MailiskClient({ apiKey: process.env.MAILISK_API_KEY });
test.describe("Test email verification", () => {
test("Should sign up, verify email, and login a new user", async ({ page }) => {
const testEmailAddress = `test.${Date.now()}@${namespace}.mailisk.net`;
await page.goto("http://localhost:3000/register");
await page.fill("#email", testEmailAddress);
await page.fill("#password", "password");
await page.click('form button[type="submit"]');
// if the register was successful we should be redirected to the login screen
await expect(page).toHaveURL("http://localhost:3000/");
// Login as user
await page.fill("#email", testEmailAddress);
await page.fill("#password", "password");
await page.click('form button[type="submit"]');
// if the login was successful we should be redirected to the verify email screen, as we haven't verified our email yet
await expect(page).toHaveURL("http://localhost:3000/verify-email");
// at this point an email with the verification code will be sent by the backend
// Verify email
await page.goto("http://localhost:3000/verify-email");
let code;
// we wait for the email to arrive, we filter by it's prefix
const { data: emails } = await mailisk.searchInbox(namespace, {
to_addr_prefix: testEmailAddress,
});
const email = emails[0];
// we know that the code is the only number in the email, so we easily filter it out
code = email.text.match(/\d+/)[0];
expect(code).toBeDefined();
// now we enter the code and confirm our email
await page.fill("#email", testEmailAddress);
await page.fill("#code", code);
await page.click('form button[type="submit"]');
// we should be redirected to the dashboard as proof of a successful verification
await expect(page).toHaveURL("http://localhost:3000/dashboard");
// Login as user again
// as a sanity check we want to ensure our email is verified, by logging in again
await page.goto("http://localhost:3000/");
await page.fill("#email", testEmailAddress);
await page.fill("#password", "password");
await page.click('form button[type="submit"]');
// this time we should be redirected to the dashboard since we've verified the email
await expect(page).toHaveURL("http://localhost:3000/dashboard");
});
});