Playwright Login Test With Two Factor Authentication (2FA) Enabled (TOTP)
This week I'll walk through a Playwright Test with 2FA enabled. 2FA or Two Factor Authentication is a common recommended security measure to protect users accounts across the internet. Having a way to test this functionality in your CI/CD pipelines within your Playwright tests can ensure that this functionality doesn't get broken as new features and bug fixes are made to the codebase.
Example of 2FA with TOTP
A common way to implement 2FA is by using TOTP, this stands for Time-based One-time Password. Typically the TOTP service provides you with a QR code or a Key that you input into an Authenticator App, after you initially create your user account. There is an example of a file below.
The key above A7VDILVI5ZTJPPGR
, is used to generate the time based password. If you want to view the current 6 digit code that is generated at this current moment you can use a tool like - https://it-tools.tech/otp-generator, paste in the OTP key and see the code that is generated every 30 seconds. You can think of this step the same as scanning the barcode into your Authenticator App, the key is what gets saved to your device and is used to generate the random rolling 6 digit password.
Install otpauth dependency
In the same way the above web app can take the key and generate the 6 digit TOTP code, we will be using the Node package otpauth, within our Playwright tests. Let's first install this into our project.
npm install otpauth
Build logic to generate the one time password
Once installed we'll go ahead and create a new helper file in the lib folder named otp.ts
.
// lib/helpers/otp.ts
import * as OTPAuth from "otpauth";
export function generateOTP(secret: string) {
const totp = new OTPAuth.TOTP({
secret: secret,
digits: 6,
algorithm: "sha1",
period: 30,
});
return totp.generate();
}
This code will generate the One Time Password that will be used to validate and login with the key that was provided above. All we have to do from a test now is call generateOTP(secretKey)
while passing in the secretKey we created from the last step.
One thing to note on this code, if the digits, algorithm, or period are different for your system under test you will need to adjust these.
Implement within a Playwright test
For our example we will be using https://practicesoftwaretesting.com website. This is a tool built by Roy de Kleijn, which can be used as a test site for exploring or writing test automation.
All the code I'm showing off can be found in this pull request from the repo linked below.
If you reviewed the pull request closely you'll notice I added a few helpers and data factories in order to setup test data, as the practicesoftwaretesting.com site database resets multiple times a day. For our purposes I'm not going to focus on test data setup/creation/management but rather how to implement 2fa into your tests.
Creating the data
First off you should register a new account. Once registered and logged back in, you can visit the profile page and you will see the section above with a QR code and a key. You can scan this into an authenticator app or use this tool I linked earlier to generate the 6 digit 1 time password from the key. Once you have a 6 digit code, enter and verify the TOTP code.
Update .env file
Next we'll update the .env file with the username, password, and one time password key.
//.env
PLAYWRIGHT_USERNAME=playwrightsolutions@practicesoftwaretesting.com
PLAYWRIGHT_PASSWORD=testyMctesterface!@5
OTP_KEY=LMWOOBI4DSVGGR4W
2FA login spec
At this point the spec is pretty straight forward. notice we set the otpKey
from the environment variable along with the email and passwords. We use these to login on the main page. The next section includes the generateOTP()
method where we pass in the otpKey
, to generate the 6 digit code we will use to login.
// tests/auth/login.2fa.spec.ts
import { generateOTP } from "@helpers/otp";
import { LoginPage } from "@pages";
import { test, expect } from "@playwright/test";
test("Login with 2FA enabled", async ({ page }) => {
const playwrightEmail = process.env.PLAYWRIGHT_USERNAME;
const playwrightPassword = process.env.PLAYWRIGHT_PASSWORD;
const otpKey = process.env.OTP_KEY;
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(playwrightEmail, playwrightPassword);
const otpCode = generateOTP(otpKey);
await loginPage.totp.fill(otpCode);
await loginPage.verifyTotp.click();
expect(await loginPage.navMenu.innerText()).toContain("Testy McTester");
});
The full end to end flow looks like this.
Final Thoughts
The example we had was quite straight forward, you may run into a scenario where you may not have the key but you do have a QR code to use. This guide will walk you through how to extract the key from a QR code utilizing a mobile device.
Thanks for reading! If you found this helpful, reach out and let me know on LinkedIn or consider buying me a cup of coffee. If you want more content delivered to you in your inbox subscribe below, and be sure to leave a ❤️ to show some love.