DEV Community

Uroš Štok for Mailisk

Posted on • Originally published at mailisk.com

Password reset with Selenium in Node

Testing password reset functionality is an important part of ensuring the security and reliability of any web application. In this tutorial, we will learn how to test password reset in Selenium with Node.js.

In order to test this functionality we'll also need some demo app which supports password reset. For ease of use the final code including the test app for this post is available on GitHub.

Selenium Setup

We'll be using the selenium-webdriver library in Node. You can add this into your project using the following command

npm install selenium-webdriver
Enter fullscreen mode Exit fullscreen mode

We're not setup to use selenium quite yet though, we'll also need to install the relevant driver. In this example we'll be using chrome and install the ChromeDriver. For an easier setup let's use the chromedriver npm package which uses one of the latest ChromeDriver versions. It's installed like so

npm install chromedriver
Enter fullscreen mode Exit fullscreen mode

This will make it easier for other developers to checkout our repository since they won't need to do a separate installation step for the driver, doing npm install will be enough.

Writing the test

We'll write a test which focuses on the key aspects of resetting passwords

  1. Create a new user, this will make it easier to run the test by itself with no other dependent logic.
  2. Login as this user, here we'll just confirm that the selected password works.
  3. Reset the password for this user, this will be a two step process. First we'll trigger the password reset email, after it arrives we'll enter our new password and reset it.
  4. Login with new password, finally we confirm that the new password is accepted and the password reset works as expected.

As mentioned before, we're using the app available in the repository which has both the frontend and backend already prepared.

Let's start by getting the parts not related to emails out of the way. Here are the contents of our password-reset.test.js

const { Builder, By } = require("selenium-webdriver");
const { MailiskClient } = require("mailisk");

(async function passwordResetTest() {
  let resetLink;
  const namespace = "mynamespace";
  const testEmailAddress = `test.${new Date().getTime()}@${namespace}.mailisk.net`;

  const mailisk = new MailiskClient({ apiKey: "YOUR_API_KEY" });

  // Create a new Chrome driver
  let driver = await new Builder().forBrowser("chrome").build();

  try {
    // Let's visit the signup page and create a new user
    await driver.get("http://localhost:3000/register");
    await new Promise((r) => setTimeout(r, 500));
    await driver.findElement(By.id("email")).sendKeys(testEmailAddress);
    await driver.findElement(By.id("password")).sendKeys("password");
    await driver.findElement(By.css("form")).submit();
    await new Promise((r) => setTimeout(r, 500));

    // We should have been redirected to the login page, so let's enter our credentials
    await driver.findElement(By.id("email")).sendKeys(testEmailAddress);
    await driver.findElement(By.id("password")).sendKeys("password");
    await driver.findElement(By.css("form")).submit();
    await new Promise((r) => setTimeout(r, 500));

    // Let's send a password reset email
    await driver.get("http://localhost:3000/forgot");
    await new Promise((r) => setTimeout(r, 500));
    await driver.findElement(By.id("email")).sendKeys(testEmailAddress);
    await driver.findElement(By.css("form")).submit();
    // the reset email is sent here!

    // Now we wait for the email to arrive extract the link
    // TODO: we'll need to implement this part

    // We visit the reset link and set the new password
    await driver.get(resetLink);
    await new Promise((r) => setTimeout(r, 500));
    await driver.findElement(By.id("new-password")).sendKeys("newpassword");
    await driver.findElement(By.css("form")).submit();
    await new Promise((r) => setTimeout(r, 500));

    // Let's try logging in again, but this time with the new password
    await driver.get("http://localhost:3000");
    await new Promise((r) => setTimeout(r, 500));
    await driver.findElement(By.id("email")).sendKeys(testEmailAddress);
    await driver.findElement(By.id("password")).sendKeys("newpassword");
    await driver.findElement(By.css("form")).submit();
    await new Promise((r) => setTimeout(r, 500));

    // If we successfully log in, the app will redirect us to the dashboard
    let currentUrl = await driver.getCurrentUrl();
    if (currentUrl !== "http://localhost:3000/dashboard") {
      throw new Error(`Expected url to be 'http://localhost:3000/dashboard' got '${currentUrl}'`);
    }
  } catch (error) {
    console.error(error);
  } finally {
    // Close the browser
    await driver.quit();
  }
})();
Enter fullscreen mode Exit fullscreen mode

The provided code above implements most of the key steps in resetting the password. We only need some way to receive the email programmatically and get the reset link.

Receiving Email

We'll use the mailisk library to fetch the password emails. First install it with

npm install mailisk
Enter fullscreen mode Exit fullscreen mode

The test sample above already contains some relevant code, such as importing the library and creating a client

const { MailiskClient } = require("mailisk");
...
const mailisk = new MailiskClient({ apiKey: "YOUR_API_KEY" });
Enter fullscreen mode Exit fullscreen mode

In order to use the client, you'll also need an api key, you can find yours in the dashboard.

Mailisk works based on namespaces you can think of these as a catch-all address which we can access programmatically. For example if we have the namespace mynamespace we can find all emails sent to john@mynamespace.mailisk.net (you can replace john with any valid email address).

...
const namespace = "mynamespace";
const testEmailAddress = `test.${new Date().getTime()}@${namespace}.mailisk.net`;
Enter fullscreen mode Exit fullscreen mode

We use this snippet of code to create a unique email address per test. Every time the test starts it'll create a new user using a random email address, which will look like this

test.123456789@mynamespace.mailisk.net
Enter fullscreen mode Exit fullscreen mode

This means we don't have to worry about clutter due to existing emails (e.g. it returning an old email with an invalid reset link).

searchInbox

In order to read the email we'll fill out the missing code using mailisk's searchInbox function

// Now we wait for the email to arrive extract the link
const { data: emails } = await mailisk.searchInbox(namespace, {
  to_addr_prefix: testEmailAddress,
});
const email = emails[0];
resetLink = email.text.match(/.*\[(http:\/\/localhost:3000\/.*)\].*/)[1];
Enter fullscreen mode Exit fullscreen mode

The searchInbox function takes a namespace and options. We use the to_addr_prefix option to filter out emails sent to other addresses (e.g. john@mynamespace.mailisk.net). In combination with the email being random this ensures that we're guaranteed to only find the password reset email.

By default the searchInbox function filters out older emails and waits for at least one email to be returned. Which is why we don't need to pool/wait manually for the results.

As a response we'll get an array of emails, but we only care about the one. We'll run some regex to filter out the link and assign it to resetLink so it can be used later in the test.

And that's it. We've got an automated test that will reset the password and try logging in again with the new password.

Top comments (0)