DEV Community

Cover image for How did we test hundreds of landing pages
Elias Lima for Woovi

Posted on • Updated on

How did we test hundreds of landing pages

Automated tests are a great way to ensure the quality of your software and provide a good user experience. At Woovi, we have thousands of landing pages, and occasionally, the first contact a user will have with us will be through these pages that showcase the products we offer. Therefore, we need to ensure that each one of them is functioning correctly. Each user who visits our pages represents a new opportunity to gain a customer.

The Challenge of Testing Landing Pages

Woovi's landing pages only present static content, such as text, images, and buttons that redirect to other pages. Due to this simplicity, there is no need to create a specific test for each new page, as they do not have advanced business rules that require more complex tests. With each new page, different content is displayed, so we need to simplify the testing process. Manually testing each one is unfeasible. Don’t do it!

The Solution: Render Testing

We use render testing (render test from the Testing Library) in conjunction with the glob library to ensure that all pages are tested efficiently.

  • Render Testing: This is used to check if a component or a page renders correctly without errors. It also ensures that the expected elements are present in the user interface after rendering.
  • GlobSync: This is used to search for files and directories that match a specific pattern, in our case .tsx, synchronously. This means that the function executes the search and returns the result immediately, blocking execution until the operation is complete.

Organize your project as follows:


src/

├── __tests__/
   └── renderScreens.spec.tsx  # Render test file for pages

├── pages/                      # Directory containing the pages to be tested
   ├── Home.tsx                # Example page: Home
   ├── About.tsx               # Example page: About
   ├── Contact.tsx             # Example page: Contact
   └── ...                     # Other project pages

Enter fullscreen mode Exit fullscreen mode

Source:

import { globSync } from 'glob';
import { render } from '@testing-library/react';
import path from 'path';


const screensPath = path.resolve(__dirname, '../pages');
const screensPattern = `${screensPath}/**/*.tsx`;
const screensIgnorePattern = '**/_*.tsx';

const getScreens = () => {
  try {
    const screenFiles = globSync(screensPattern, {
      ignore: [screensIgnorePattern],
    });

    return screenFiles.map((filePath) => {
      const relativePath = path.relative(screensPath, filePath);
      const component = require(filePath).default;

      return {
        name: relativePath,
        component,
      };
    });
  } catch (e) {
    console.log('Error fetching files:', e);
    return [];
  }
};


const screens = getScreens();

test.each(screens.map(({ name, component: Screen }) => [name, Screen]))(
  'Render screen: %s',
  (_, Screen) => {
    render(<Screen />);
  },
);

Enter fullscreen mode Exit fullscreen mode

Output:

terminal output

Code Explanation

  1. getScreens(): This function uses globSync to synchronously search for all .tsx files in the /pages directory and its subdirectories. It returns a list of objects, each containing the file name and the corresponding component.
  2. screens: A constant that stores the result of the getScreens function, containing all the pages that will be tested.
  3. test.each(): A method that runs the render test for each page. It passes the page name and component to the test function, ensuring that all pages render correctly.

Final Considerations

This automated testing method ensures that all your landing pages are rendered correctly, without the need to create individual tests for each page. This saves time and resources, while also ensuring that users have a consistent experience when visiting your pages.

Use this article as a base to adapt the tests to your project's needs, and always carefully test the implementation.

Top comments (11)

Collapse
 
horaceshmorace profile image
Horace Nelson

I'm not sure how much I like this. I think a lot but maybe a little. Perhaps my one issue is that a render test is the least amount of testing. Snapshot tests for each screen would add an acceptable level of coverage.

Collapse
 
eliaslma profile image
Elias Lima

And that’s exactly the problem we want to avoid—creating a test for each page when the content is static and completely different from each other is impractical. How would you test over 1000 pages like this?

Collapse
 
horaceshmorace profile image
Horace Nelson • Edited

Assuming you're using Jest, adding "snapshot testing" is trivial, but will provide much better coverage. Something like:

test.each(screens.map(({ name, component: Screen }) => [name, Screen]))(
  'Render and snapshot screen: %s',
  (_, Screen) => {
    const component = render(<Screen />);
    expect(component).toMatchSnapshot();  // <---- Add snapshot testing
  },
);
Enter fullscreen mode Exit fullscreen mode

This will take a "snapshot" of the the render output, and ensure it doesn't unexpectedly change.

Thread Thread
 
alexanderop profile image
Alexander Opalic

But wouldn't this mean you'd have a huge snapshot, namely the whole DOM for a page? Every time you change something, you'd need to look into the test and then update it.

Thread Thread
 
horaceshmorace profile image
Horace Nelson • Edited

You're right about that. You can configure Jest to generate separate snapshot files by creating what Jest calls a "snapshot resolver."

const path = require('path');

module.exports = {
  /**
   * Saves snapshots in a __snapshots__ folder near each component file
   * @param testPath 
   * @param snapshotExtension 
   * @returns 
   */
  resolveSnapshotPath: (testPath, snapshotExtension) => {
    const dir = path.dirname(testPath);
    return path.join(dir, '__snapshots__', `${path.basename(testPath)}${snapshotExtension}`);
  },
  resolveTestPath: (snapshotPath, snapshotExtension) => {
    return snapshotPath.slice(0, -snapshotExtension.length);
  },
  /**
   * This is a little confusing, but this is the path to the test file that
   * Jest will use to check for consistency between `resolveSnapshotPath` and
   * `resolveTestPath`. It just needs to point to a real test file.
   * @type {string}
   */
  testPathForConsistencyCheck: 'some/example.test.js',
};
Enter fullscreen mode Exit fullscreen mode

Then you tell Jest to use your snapshot resolver in your Jest config file (jest.config.js):

module.exports = {
  snapshotResolver: './snapshotResolver.js',
};
Enter fullscreen mode Exit fullscreen mode

And updating all of those snapshots is as easy as running jest -u.

Collapse
 
chrischism8063 profile image
chrischism8063

This looks useful!!!

Collapse
 
code_passion profile image
CodePassion

Great post!

Collapse
 
manvendrask profile image
Manvendra Singh

Snapshots shouldn't be used to test. These only tell you that something rendered without a probable problem, but it doesn't convey any information about something. For example, it wouldn't tell you if a hero image is rendered on the top or in the middle, which I think you would like to test.

Collapse
 
horaceshmorace profile image
Horace Nelson

You probably read that somewhere or heard it from a more senior dev, but it's wrong. Snapshots should absolutely be used to test. They just shouldn't be your only test, as they test at one of the highest possible scopes for a component, but it is the best way to test render, which is a unit that needs to be tested. Regardless, snapshot testing will at least provide better coverage than him simply checking if the component renders without error since he can't feasibly do specific unit tests.

Collapse
 
horaceshmorace profile image
Horace Nelson

How did you get so many likes and saves on your first post? 😂

Collapse
 
ricardogesteves profile image
Ricardo Esteves

Ok interesting, have to check it out!