Check out my newly released course 99 Cypress.io Tips
When you're writing end-to-end tests, authentication can often become the first gatekeeper. You can't test the actual functionality of your app without first getting past the login screen. But modern authentication methods can make automation difficult, utilizing multiple factors that are difficult to automate (which is actually the point of 2FA).
This is usually handled either by skipping these methods on test environments or some other workarounds. Some might argue this is not true e2e testing. To be honest, it’s probably a debate for another day, but there is definitely a merit to critiquing the approach of bypassing login.
So how to handle authentication properly?
For years now, my go-to solution has been Mailosaur. It is a testing service that gives you virtual email addresses and phone numbers for automation. Think of it as a testing inbox that your tests can programmatically access. You can:
Let me show you how to set it up.
First, you'll need a Mailosaur account. This is needed to create virtual email addresses and phone numbers for your tests. Once you have one, install the Mailosaur client:
In your Mailosaur account, you'll need two things:
It’s good to keep these private (especially the API key), so I recommend storing these in environment variables, either in .env
file or in your CI/CD pipeline.
Now let's tackle each authentication method.
Magic links are becoming increasingly popular. They can be used instead of a password, but in essence they are the same thing you use when you reset your password. A link that authorizes certain usage (logging in or changing your password) is generated on server and sent to the account owner’s email. Here's how the flow looks like:
sequenceDiagram participant User participant Browser participant Server User->>Browser: Enters email address Browser->>Server: POST /api/auth/magic-link/send (email) Server->>Server: Generates a temporary, signed token Server-->>User: Sends email with login link (containing token) Server-->>Browser: Responds with "Magic link sent" Note over User, Browser: User opens email and clicks the link Browser->>Server: GET /api/auth/magic-link/verify?token=... Server->>Server: Verifies token and creates session Server-->>Browser: Redirects to /success page with session cookie
As you can see in the diagram above, the main challenge is that at a certain point the flow disconnects from the browser. When doing browser test automation, this is a problem. How do you enter your inbox?
This is where Mailosaur comes in. It allows you to programmatically access the inbox that Mailosaur created for you. You’ll retrieve the vital information from the inbox (email containing the link) and then continue on with the test.
Here's how a test like this would look like in Playwright:
Note: In order to use
.env
variables in Playwright, you need to import thedotenv
package to theplaywright.config.ts
file.
The key part here is the mailosaur.messages.get()
method. It automatically waits for the email to arrive, and parses the email content. You can access them via message.html.links
. Each link object contains:
If your email contains multiple links, you can filter them:
In essence, SMS-based authentication is the same as email magic links, but instead of an email, it sends a numeric code to your phone. You enter this code to prove you own that phone number. Here's the flow:
sequenceDiagram participant User participant Browser participant Server User->>Browser: Enters phone number Browser->>Server: POST /api/auth/sms/send (phone) Server->>Server: Generates & stores OTP code Server-->>User: Sends SMS with OTP code Server-->>Browser: Responds with "SMS sent" User->>Browser: Enters the received OTP code Browser->>Server: POST /api/auth/sms/verify (phone, code) Server->>Server: Creates session Server-->>Browser: Responds with success and sets session cookie Browser->>Browser: Redirects to /success page
With Mailosaur, you can create a virtual phone number that will receive your SMS messages to.
Once you have one, you can start using it in your tests. Here's how to automate this flow:
Just like with email links, Mailosaur automatically extracts verification codes from SMS messages. You can access them via message.text.codes
. If your SMS contains multiple codes (though this is rare), they'll all be available in the codes
array.
Authenticator apps like Google Authenticator or Authy generate time-based one-time passwords (TOTP). These codes change every 30 seconds and are generated using a shared secret. There are actually two steps in this flow.
In the first step, you setup the authenticator app with the shared secret.
sequenceDiagram participant User participant AuthenticatorApp as Authenticator App participant Browser participant Server User->>Browser: Enters identifier (email/phone) Browser->>Server: POST /api/auth/totp/setup (identifier) Server->>Server: Generates & stores a unique secret Server-->>Browser: Responds with QR code (containing secret) Browser-->>User: Displays QR Code for scanning User->>AuthenticatorApp: Scans QR Code AuthenticatorApp->>User: Now generates 6-digit codes
In the second step, you use the authenticator app to generate a code and enter it into the login form. This code will then be validated on the server.
sequenceDiagram participant User participant AuthenticatorApp as Authenticator App participant Browser participant Server User->>Browser: Opens login page User->>AuthenticatorApp: Gets current 6-digit code User->>Browser: Enters 6-digit code Browser->>Server: POST /api/auth/totp/verify (identifier, code) Server->>Server: Validates code against stored secret Server->>Server: Creates session Server-->>Browser: Responds with success and sets session cookie Browser->>Browser: Redirects to /success page
The secret is usually presented as a QR code or a string of characters during setup. When integrating with Mailosaur, you can manually set up the first step inside Mailosaur’s service:
This allows you to interact with the authenticator during a manual test. But you can also set up things automatically in your tests. Here's how the test for a TOTP authentication flow would look like:
As you can notice, we are extracting the shared secret from an element on the page. You‘ll usually find this option on TOTP setup pages. But if you don't, you can always use QR code decoding to get the key.
TOTP codes expire every 30 seconds. If your test is slow or runs near a boundary, the code might expire between generation and use. To handle this:
While Mailosaur's messages.get()
method waits automatically, you can customize the timeout:
If your inbox gets clogged with test data, you can clean it up:
Instead of relying on unique email addresses, you can search for emails based on their content or subject. This is especially useful when multiple tests might send emails to the same address:
For SMS, you typically have a limited number of virtual phone numbers, so use search criteria to find the right message:
Hope this helps! If you found this useful, feel free to share it with others who might be struggling with authentication testing. You can also find me on Twitter or LinkedIn where I share more testing tips and tricks.
From time to time I send some useful tips to your inbox and let you know about upcoming events. Sign up if you want to stay in loop.