The Power of Headless Chrome and PWA Automation Using Puppeteer

Testing code is critical for the maintainability of a complex code base, but it is just as important that tests are easy to write, maintain, and understand. Frontend code is no exception to this rule, and behaviors that live in your UI should be tested as well. Historically, testing UI has been hard to accomplish for a variety of reasons, but using React removes a lot of these hurdles. We hope that puppeteer does a good job of removing the remaining ones!

The love Story of Puppeteer

Before we start, let’s define Puppeteer. Puppeteer is a node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol, It goes on to say Puppeteer runs headless Chrome or Chromium instances by default, which is why they’re always mentioned in tandem. Puppeteer was made for Chrome by Google Chrome DevTools team to help further automated/headless browser testing and make it less painful.
Low-level emulation is usually done with DevTools protocol. Just like the Chrome DevTools UI, Puppeteer uses DevTools protocol under the hood to drive all its operations, and it does help a lot with input emulation.

What can we do with Puppeteer?

Most things that we can do manually in the browser can be done using Puppeteer! Here are a few examples to get you started:

  • Generate screenshots and PDFs of pages.
  • Crawl a SPA (Single-Page Application) and generate pre-rendered content (i.e. “SSR” (Server-Side Rendering)).
  • Automate form submission, UI testing, keyboard input, etc.
  • Create an up-to-date, automated testing environment.
  • Run your tests directly in the latest version of Chrome using the latest JavaScript and browser features.
  • Capture a timeline trace of your site to help diagnose performance issues. Test Chrome Extensions.

Declarative UIs are Testable UIs

All new UI features for OpenSooq.com are now implemented using React , which structures an application’s UI into a set of reusable “Components”. Components are a way to declare the way a UI should be rendered through an idempotent render function that is a pure function of application state.

Pure functions (and thus React components) are much easier to test because they simply return a description for what UI of the component should look like, given some application state, rather than actually mutating the UI and having side-effects. This “description” is known as a “Virtual DOM” and is a tree-like data structure.

Making assertions on the state of a React render tree can include a lot of boilerplate code and is hard to read, which detracts from the value of the test. Moreover, directly asserting on the resulting tree can strongly couple your tests to implementation details that end up making your tests extremely fragile.

Puppeteer makes asking questions about the rendered output of your React components easy and intuitive by providing a fluent interface around rendered React components.

Project Components

Our automation project consists of three folders :

  1. node-modules : are Node.js modules which represent a simple or complex functionality organized in single or multiple JavaScript files which can be reused throughout the Node.js application.
  2. src: which is for another necessary library for example: babel-preset-react-app and loadable.
  3. test cases: which is the most important one which contains the test cases source code.

The test case folder contains two folders first of them is

1- compositions: which consist of:

  • helpers: are sub-codes that I write it to simplicity the test case code, and make it readable .

like:

  • clickDOMBySelector:
export const clickDOMBySelector = async selector => {
    await page.waitForSelector(selector);
    await page.click(selector);
};

here in this method, we get HTML elements by their selectors, and then click on it. (we use it for necessary cases because selector are changed periodically )

  • clickDOMByText:
export const clickDOMByText = elemName => async text => {
    const dom = await getDOMByText(elemName)(text);
    await awaitFor(.1);
    await dom.click();
};

As we see here we have two arguments, HTML element and text, we use it to click on a specific element by the text on it, this method more powerful than the first one but not all HTML elements contain text so we need the first one for selected cases .

  • DOMElemByTextExists:
export const DOMElemByTextExists = elemName => async text => {
    const dom = await getDOMByText(elemName)(text);
    expect(!!dom).toBeTruthy();
};

On this method, we use the same arguments as the clickDOMByText, but here I use it to be sure that selected text is on the page (for example use it to be sure that I’m on home page or not ).

  • navigation: are sub-codes that I write it to simplicity moving from one page to another:

and this is examples of them:

  • closeSplashScreen:
export const closeSplashScreen = async () => {
    
    if (!await isSplashScreenExists()) {
        return;
    }
    await clickDOMBySelector('#continueToSiteLink')
    
};
  • openHomePage:
export const openHomePage = async () => {
    await openPage(`/ar`);   
     await page.evaluate(async () => {
        window.forceLoadAssets = true;
    });
  
    await closeSplashScreen();
    await page.waitForSelector('.homePage');
    await DOMElemByTextExists('h3')('بيع . إشتري . دردش');
};
  • openSideMenu:
export const openSideMenu = async () => {
    
    if (await isSideMenuOpen()) {
        return;
    }
    await clickDOMBySelector('#menu-handle')
};

Note: Those is not all of helpers and navigators, there is a lot of them .

**2- component :**The second folder is each component related to specific element or functionality on our app .

Running Test Cases

To run the test cases we use yarn commands (dependencies management) ;

 yarn test --runInBand  // to run all test cases to gather on sequence
yarn test test_case_name // to run all test cases one by on

The diagram below shows the execution of test case of log in using phone number.

When you execute the command, automatically the browser will be opened and you see the execution of test cases like below.

Moving Forward

The problems that Puppeteer addresses are by no means specific to OpenSooq. The main challenge of testing with Puppeteer is tied to chrome only. We have a number of features planned for future development and part of it is covering other browsers.

#pwa #Chrome #Puppeteer

The Power of Headless Chrome and PWA Automation Using Puppeteer
12.25 GEEK