In ReactJs project I’m working on we are using Puppeteer as UI testing framework, the framework is part of node dev dependencies and living with app source code. Since development team constantly working on new features in the project we are adding data-test-id’s as UI locators strategy.

Puppeteer using query selectors to select elements and standard click function looks like this:

page.click(selector);

It is working fine, but there is always ways to improve by using async await and waitForSelector functions:

async click(selector) {
    try {
      await page.waitForSelector(selector);
      await page.click(selector);
    } catch (error) {
      throw new Error(`Could not click on: ${selector}`);
    }
  }

We also wrap the click function in try catch block and throwing an error message if there is a problem to click on the selector. So now if want to make a click we are using custom click:

await this.click('[data-test-id="signin"]');

As I mentioned early in the blog post we are using data-test-ids as UI elements locator strategy and this leads to better abstraction and faster coding experience. Similar to Selenium WebDriver we can made custom function which locates element by data-test-id, here is how to do it:

async click(selector) {
    try {
      await page.waitForSelector(selector);
      await page.click(selector);
    } catch (error) {
      throw new Error(`Could not click on: ${selector}`);
    }
  }

async clickTestId(id){this.click(`[data-test-id="${id}"]`)};

On line ten we added clickTestId function which is calling our custom click and all we need to do is pass data-test-id, here is how our final function look like:

await this.clickTestId('signin');

So just a little bit of abstraction make tests cleaner and easier to maintain in case our locator strategy change at some point there is only one place to adjust it.