PercyScript uses Google Puppeteer—a Node library that provides a high-level API to control headless Chrome—as the driver for the browser.

More complex interactions

You can use any of the Puppeteer Page API with the PercyScript page object for more complex interactions.

Show the browser as it's running

You can pass the headless: false option to PercyScript, which will turn off Chrome headless mode and show you the Chrome instance as it runs the script.

const options = {headless: false};
PercyScript.run(async (page, percySnapshot) => {
  // ...
}, options);

Tip: you can pass any of the Puppeteer launch options to the options argument.

Mocking network requests

Testing pages with dynamic data can cause diffs when the data changes between snapshots. One way to stop that from happening is by intercepting the network request and sending a mocked response instead. To do this, we'll make use of Puppeteers request interception API. At the very top of your PercyScript run function, you'll want to set page.setRequestInterception to true:

PercyScript.run(async (page, percySnapshot) => {
  await page.setRequestInterception(true);
  // ...
});

It's important to do this before visiting URL you want to snapshot.

Once that's done you're ready to start mocking any of the URLs the visited page requests. This is done by adding an page.on('request') event listener:

PercyScript.run(async (page, percySnapshot) => {
  await page.setRequestInterception(true);
  // Listen to each page request
  page.on("request", request => {
    // Find the request you want to mock out
    if (request.url().includes("/v1/your-api-endpoint")) {
      // TODO
    } else {
      // We must tell puppeteer to let normal requests happen:
      request.continue();
    }
  });

  await page.goto("https://example.com");
});

Now that we're listening to each request the page makes and picking the request that we want to mock, it's time to actually respond with our fake data. This is done by using Puppeteers respond API:

PercyScript.run(async (page, percySnapshot) => {
  await page.setRequestInterception(true);
  // Listen to each page request
  page.on("request", request => {
    // Find the request you want to mock out
    if (request.url().includes("/v1/your-api-endpoint")) {
      request.respond({
        status: 200,
        contentType: "application/json; charset=utf-8",
        // The mocked data you want to return
        body: JSON.stringify({ foo: "bar" })
      });
    } else {
      // We must tell puppeteer to let normal requests happen:
      request.continue();
    }
  });

  await page.goto("https://example.com");
  await percySnapshot('home page')
});

Now you can run your snapshots with mocked data that you control for the tests! No more diffs related to changing data. 🎉 This can get pretty lengthy with a few mocks, so it's common to pull this setup out into a helper function like so:


async function setupMocks(page) {
  await page.setRequestInterception(true);
  // Listen to each page request
  page.on("request", request => {
    // Find the request you want to mock out
    if (request.url().includes("/v1/your-api-endpoint")) {
      request.respond({
        status: 200,
        contentType: "application/json; charset=utf-8",
        // The mocked data you want to return
        body: JSON.stringify({ foo: "bar" })
      });
    } else {
      // We must tell puppeteer to let normal requests happen:
      request.continue();
    }
  });
}

PercyScript.run(async (page, percySnapshot) => {
  await setupMocks(page);
  await page.goto("https://example.com");
});

What's next