Visual regression testing with puppeteer and Jest

How to perform visual regression testing with puppeteer and Jest. Or How to take screenshots in puppeteer and verify the image with existing screenshots using puppeteer and jest.

What is Visual testing

Visual testing processes to verify the View/Frontend of the application. As you know these days we have lost devices in different sizes so it’s really important to test the visuals of application on all the sizes of devices. In other words, we can say it “Responsive testing”.

Manually we can perform the visual testing but the problem occurs in the large scale projects or in the regression testing. So Puppeteer and Jest come up with the automation solution of the problem. It can take screenshot and compare the visuals with saved images or snapshots.

Visual testing with puppeteer and Jest.

Let’s start another puppeteer tutorial for visual testing with puppeteer and jest. Below is the complete example to get a snapshot and verify it with the existing snapshot.

Install Puppeteer and Jest

npm install puppeteer jest jest-image-snapshot

Node script for visual testing

Write a node script to run the visual test.

"test-spsnahot": "jest --config=jest.config.js --detectOpenHandles --forceExit",
"test-snapshots-update": "jest --config=jest.config.js --updateSnapshot --detectOpenHandles --forceExit"

Jest config setup for visual testing

Carate a jest.config.js file in the root folder of the project. and provide the path of the root directory of visual tests and snapshots.

module.exports = {
    rootDir:"./tests-snapshots",
    testTimeout: 30000,
    bail:0
}

Write a script for visual testing

Create a folder (__test__) under the root folder of visual testing In my case its (tests-snapshots). Now create a script (visual.test.js) under the __test__ folder.

const puppeteer = require('puppeteer')
const {toMatchImageSnapshot} = require('jest-image-snapshot')

expect.extend({toMatchImageSnapshot})

describe("visual regression testing",()=>{

    let browser
    let page
  
    beforeAll(async function(){
      browser = await puppeteer.launch({
        headless:false,
        slowMo:100
      })
      page = await browser.newPage()
      await page.goto("https://devexpress.github.io/testcafe/example/")
    })

    test('full page', async function(){
      await page.waitForSelector('h1')
        const image = await page.screenshot();
        expect(image).toMatchImageSnapshot({
            failureThresholdType:'pixel',
            failureThreshold:500
        })
    })
   
  

    afterAll(async function(){
       await browser.close()
    })
  
      });

Command to run the script

npm run test-snapshot

Output:

Responsive testing using puppeteer

Yes, we can perform responsive testing using puppeteer and Jest. Puppeteer has a viewport that helps to set and dynamic size of the view page also we can use device emulation to target a specific device.  But when It comes to verifying the snapshots we can perform many operations.

  • Visual regression testing with single element.
  • Visual regression testing with mobile view.
  • Visual regression testing with the tablet.
  • Remove elements before the snapshot.

Visual testing with Single element

test('Single Element Snapshot', async function() {
     await page.goto('https://www.example.com')
     const h1 = await page.waitForSelector('h1')
     const image = await h1.screenshot()
     expect(image).toMatchImageSnapshot({
         failureTresholdType: 'percent',
         failureTreshold: 0.01,
     })
 })

Visual testing with Mobile

Using device emulation we can set a viewport of page. In the below example we are using i Phone X as view size. check the complete tutorial on device emulation.

test('Mobile Snapshot', async function() {
     await page.goto('https://www.example.com')
     await page.waitForSelector('h1')
     await page.emulate(puppeteer.devices['iPhone X'])
     const image = await page.screenshot()
     expect(image).toMatchImageSnapshot({
         failureTresholdType: 'percent',
         failureTreshold: 0.01,
     })
 })

Visual testing with Tablet

test('Tablet Snapshot', async function() {
    await page.goto('https://www.example.com')
    await page.waitForSelector('h1')
    await page.emulate(puppeteer.devices['iPad landscape'])
    const image = await page.screenshot()
    expect(image).toMatchImageSnapshot({
        failureTresholdType: 'percent',
        failureTreshold: 0.01,
    })
})

Remove Element Before Snapshot

Yes, We can remove any text or element before getting the screenshot. In the below example it will remove the heading of the page then it will take a screenshot and compare it with existing screenshot and generate the result.

test('Remove Element Before Snapshot', async function() {
     await page.goto('https://www.example.com')
     await page.evaluate(() => {
         ;(document.querySelectorAll('h1') || []).forEach(el => el.remove())
     })
     await page.waitFor(5000)
 })

Visual regression test script using puppeteer and jest.

In the below puppeteer script. We are using all the above examples.

const puppeteer = require('puppeteer')
const { toMatchImageSnapshot } = require('jest-image-snapshot')
 
expect.extend({ toMatchImageSnapshot })
 
describe('Visual Regression Testing', () => {
    let browser
    let page
 
    beforeAll(async function() {
        browser = await puppeteer.launch({ headless: true })
        page = await browser.newPage()
    })
 
    afterAll(async function() {
        await browser.close()
    })
 
    test('Full Page Snapshot', async function() {
        await page.goto('https://www.example.com')
        await page.waitForSelector('h1')
        const image = await page.screenshot()
        expect(image).toMatchImageSnapshot({
            failureTresholdType: 'pixel',
            failureTreshold: 500,
        })
    })
 
    test('Single Element Snapshot', async function() {
        await page.goto('https://www.example.com')
        const h1 = await page.waitForSelector('h1')
        const image = await h1.screenshot()
        expect(image).toMatchImageSnapshot({
            failureTresholdType: 'percent',
            failureTreshold: 0.01,
        })
    })
 
    test('Mobile Snapshot', async function() {
        await page.goto('https://www.example.com')
        await page.waitForSelector('h1')
        await page.emulate(puppeteer.devices['iPhone X'])
        const image = await page.screenshot()
        expect(image).toMatchImageSnapshot({
            failureTresholdType: 'percent',
            failureTreshold: 0.01,
        })
    })
 
    test('Tablet Snapshot', async function() {
        await page.goto('https://www.example.com')
        await page.waitForSelector('h1')
        await page.emulate(puppeteer.devices['iPad landscape'])
        const image = await page.screenshot()
        expect(image).toMatchImageSnapshot({
            failureTresholdType: 'percent',
            failureTreshold: 0.01,
        })
    })
 
    test('Remove Element Before Snapshot', async function() {
        await page.goto('https://www.example.com')
        await page.evaluate(() => {
            ;(document.querySelectorAll('h1') || []).forEach(el => el.remove())
        })
        await page.waitFor(5000)
    })
})