What is TestCafe
TestCafe is a Node.js-based end-to-end testing framework that runs directly in real browsers without needing WebDriver or plugins. It takes care of everything - from launching browsers and running tests to gathering data and generating reports. With its built-in proxy, you can run tests consistently across Chrome, Firefox, Edge, Safari, and headless environments.
You can create tests effortlessly using the test recorder, either through codeless automation or by writing scripts. TestCafe automatically handles waits for asynchronous events, provides a full set of assertions to validate UI elements, and generates selectors to identify elements reliably. It also supports flexible run configurations and detailed reporting in multiple formats, making it a powerful and efficient choice for web application testing.
Why test OTPs
One-Time Passwords (OTPs) add an extra layer of security to online apps by ensuring that only the right person - someone with access to the registered email or phone - can complete certain actions. Unlike regular passwords, they expire quickly, making them harder to intercept or reuse.
However, for OTP-based authentication to be effective, it must function reliably across different devices, networks, and email providers. Companies rigorously test OTP flows to identify issues like delayed delivery, incorrect validation, and user experience problems. A failed OTP can prevent users from accessing their accounts, leading to frustration and potential loss of business. Testing makes sure OTPs arrive on time, work correctly, and are easy to use. This helps prevent login issues while keeping accounts secure from unauthorized access.
Why use testmail.app
Efficiently testing email-based OTPs requires a quick and reliable way to generate and access test email addresses. Manually creating inboxes and checking emails is slow, error-prone, and difficult to scale. With a tool like testmail.app, you can generate disposable email addresses on demand and fetch OTP messages via API, enabling instant verification of delivery and validation. When combined with TestCafe, this process becomes fully automated.
In this test, we automate the signup process using TestCafe and testmail.app. First, we generate a disposable email and use it to sign up for the app. Once the form is submitted, we call the Testmail.app API to retrieve the OTP and validate its format. Then, we enter the OTP in the verification field and submit it. Finally, we confirm successful signup by checking for a success message or a redirect, ensuring the entire flow works as expected.
Prerequisites
Before starting, ensure you have the following:
- Node.js and npm installed: Download and install Node.js.
- testmail.app account: Sign up to testmail.app. Get your API key and namespace from the dashboard.
- TestCafe installed: Install TestCafe globally or in your project directory:
npm install -g testcafe
Set up your project
testmail.app offers two APIs for fetching test emails: a simple JSON API and a more advanced GraphQL API. The JSON API is beginner-friendly and works across all environments, including browsers. It supports any HTTP client in any programming language - just send a GET request to retrieve emails. The GraphQL API, on the other hand, provides more flexibility for complex queries and filtering (Compare features).
Before making API requests, set up environment variables to securely store your testmail.app API key and namespace. Create a .env
file in your project’s root directory:
TESTMAIL_APIKEY=your_testmail_api_key
TESTMAIL_NAMESPACE=your_testmail_namespace
Testmail offers unlimited email addresses and a robust API for email testing, enabling you to automate end-to-end tests and integrate them smoothly into your CI/CD pipelines.
To get the Testmail credentials:

Testmail receives emails at the address format {namespace}.{tag}@inbox.testmail.app.
Namespace: A unique identifier assigned to you, acting as a collection of mailboxes. Each namespace supports unlimited email addresses.
Tag: A customizable label that can be anything you choose. Using different tags, you can dynamically create new email addresses and mailboxes as needed.
Install required dependencies:
npm init -y # Initialize a Node.js project
npm install dotenv axios chance
Set up Testmail API endpoint: We'll load our API credentials from environment variables, then use the Chance library to generate a random 12-character tag for tracking emails. We'll also capture the current timestamp to filter emails later.
require('dotenv').config(); // Load environment variables from .env file
const axios = require('axios'); // For API requests
const Chance = require('chance'); // For generating random strings
// Testmail API endpoint
const ENDPOINT = `https://api.testmail.app/api/json?apikey=${process.env.TESTMAIL_APIKEY}&namespace=${process.env.TESTMAIL_NAMESPACE}`;
// Create a Chance instance for random string generation
const chance = new Chance();
// Generate a random tag to use for tracking emails
const TAG = chance.string({ length: 12, pool: 'abcdefghijklmnopqrstuvwxyz0123456789' });
// Track the current time for timestamp-based email filtering
const startTimestamp = Date.now();
console.log(`Generated Tag: ${TAG}`);
console.log(`Testmail API Endpoint: ${ENDPOINT}`);
console.log(`Timestamp for Filtering Emails: ${startTimestamp}`);
You can do a lot more with tags. You can filter emails by using a tag prefix to organize and retrieve related messages easily.
For example, if you add &tag_prefix=john
to your query, it will match any email whose tag starts with "john"—such as [email protected]
and [email protected]
. This is especially useful for grouping emails by user, project, or test ID.
Signup to your app
Navigate to the signup page of your app and fill in the signup form with a testmail.app email address:
const { Selector } = require('testcafe');
require('dotenv').config();
const Chance = require('chance');
dotenv.config();
// Initialize Chance for generating random strings
const chance = new Chance();
// Generate a unique TAG for the email address
const TAG = chance.string({ length: 12, pool: 'abcdefghijklmnopqrstuvwxyz0123456789' });
// Your app's signup page URL
const SIGNUP_PAGE_URL = 'http://your-app.com/signup'; // Replace with your app's signup page URL
// Your Testmail namespace
const NAMESPACE = process.env.TESTMAIL_NAMESPACE; // Make sure TESTMAIL_NAMESPACE is set in your .env file
// Generated test email address using Testmail
const testEmail = `${NAMESPACE}.${TAG}@inbox.testmail.app`; // Test email address with unique tag
fixture`Signup Page Tests`
.page(SIGNUP_PAGE_URL);
test("Test OTP verification with testmail", async (t) => {
await fillSignupForm(t);
const email = await fetchEmailFromTestmailAPI(t);
await verifyEmail(t, email);
const otpCode = await extractOtp(t, email);
await submitOtp(t, otpCode);
await checkIfSuccessful(t);
});
const fillSignupForm = async (t) => {
const emailField = Selector('#email-input'); // Replace with the email input field selector
const signupButton = Selector('#signup-button'); // Replace with the signup button selector
// Fill out the email field with a generated email address
await t.typeText(emailField, testEmail);
// Click the signup button to submit the form
await t.click(signupButton);
};
Note that if your app has anti-bot measures (like CAPTCHA, rate limiting, or IP blocking), it might prevent the signup form from being submitted.
Verify email delivery
To check if your test email was delivered, you only need two parameters: your API key (&apikey=YOUR_APIKEY
) and your namespace (&namespace=YOUR_NAMESPACE
). If you're viewing the output in a browser, adding &pretty=true
makes it easier to read.
The API returns a result object. The result
field tells you if it was successful ("success"
) or if something went wrong ("fail"
). If there's an issue, the message
field will explain why. You'll also see a count
field showing how many emails matched your query and an emails
array containing the actual messages. If you want to dive deeper into what the API can do, check out the JSON API reference.
const fetchEmailFromTestmailAPI = async (t) => {
const response = await axios.get(
`${ENDPOINT}&tag=${TAG}×tamp_from=${startTimestamp}&livequery=true`
);
const inbox = response.data;
// Ensure the query was successful
await t.expect(inbox.result).eql("success", { timeout: 300000 });
// Get the first email from the result
const otpEmail = inbox.emails[0];
return otpEmail;
};
To make sure the email is correct, check that it has the expected subject and sender. You can add other tests here as well. You might want to check the email body for specific content, verify that links are present and correctly formatted, or ensure attachments (if any) are included.
const verifyEmail = async (t, email) => {
await t.expect(email.subject).eql("Your login code"); // Replace with expected subject
// Check if the sender's email contains the expected domain
await t.expect(email.from).contains("Sender's email id"); // Replace with your sender's email/domain
};
Extract the OTP code from the email body:
const extractOtp = async (t, email) => {
// Use a regular expression to search for the OTP in the email's HTML body
let otpCode = email.text
.match(/Your login code: [A-Za-z0-9]{3}-[A-Za-z0-9]{3}/)[0]
.slice(17);
console.log("OTP Code:", otpCode);
return otpCode;
};
Instead of directly searching for a 6-digit number, consider looking for keywords like "OTP" or "Verification code" first. This helps reduce the chances of mistakenly extracting an unrelated number from the body of the email.
Submit OTP Code
Submit the extracted OTP code in your app:
const submitOtp = async (t, otpCode) => {
// Type the OTP code into the OTP input field
await t.typeText('#otp-input', otpCode); // Replace '#otp-input' with the selector
// Click the verify button to submit the OTP code
await t.click('#verify-button'); // Replace '#verify-button' with the selector
};
Once you've submitted the extracted OTP code, it's important to validate whether the submission was successful. Typically, this is done by checking for a success message or a page redirect that confirms the verification.
const checkIfSuccessful = async(t) => {
const successText = Selector("*").withText("OTP verification successful."); // Checking if success message exists in the DOM
await t.expect(successText.exists).ok("Failed OTP verification."); //Message shown when assertion fails
}
If the OTP submission doesn't succeed, there are likely error messages indicating what went wrong, such as "Invalid OTP," "OTP expired," or "OTP not received." These messages should be tested as well to ensure that your application provides appropriate feedback to users in case of errors.
Now, to run your tests run the following command
testcafe chrome index.js
The following output will be shown in the terminal after running the tests:
