Luka Mis
Luka Mis

Reputation: 573

how to implement comparing two screenshots in one test with playwright

I am very new to playwright and i have a problem. I am trying to implement comparing two screenshots (before and after) in one test. this is what i want to achieve:

  1. navigate to webpage
  2. take screenshot (before.png)
  3. do some stuff,state changes, etc
  4. take screenshot (after.png)
  5. compare before.png to after.png (if they are the same test should pass, otherwise test fails)

something like this:

test('compare screenshots', async ({ page }) => {
  await page.goto('my website here');
  const beforeImage = await page.screenshot({
    path: `./screenshots/before.png`
  })
  //
  // some state changes implemented here
  //
  const afterImage = await page.screenshot({
    path: `./screenshots/after.png`
  })
  expect(beforeImage).toMatchSnapshot(afterImage)
});

but it does not work like this. Any ideas/suggestions how can i achieve this? Help would be greatly appreciated

Upvotes: 5

Views: 8757

Answers (3)

Nico
Nico

Reputation: 890

Another potential solution is to create a fixture that manages the file comparison, as inspired by @d2vid's answer.

The idea is to expose (via a Playwright fixture) a function to create a screenshot, and a function to compare them.

If the test succeeds, the images are deleted.

It would look something like this:


export const compareSnapshotsFixture: TFixture<'compareSnapshots'> = async (
  { page },
  use,
  testConfig,
) => {
  const testName = testConfig.title
  // Unique -- in case we rerun the test
  const now = ts()
  // Different folders for each test
  const imageFolder = CONFIG.paths.inTestSnapshot(testName)
  // Generate image names on the fly
  const imageName = (prefix: string = '') =>
    `${imageFolder}/${now}---${prefix}.png` as const

  // import from `playwright-core/lib/utils`
  const compare = getComparator('img/png')

  // The test that uses this fixture
  await use({
    compare,
    page,
    snapImg: async (opts = {}) => {
      await page.mouse.move(0, 0)
      await page.waitForTimeout(300) // wait for any async/animations
      return page.screenshot({ ...opts, path: imageName(opts.prefix) })
    },
    expectDifferent: (buffer1, buffer2, minDiffPixels, message) =>
      expect(
        compare(buffer1, buffer2, { maxDiffPixels: minDiffPixels }),
        message,
      ).not.toBeNull(),
    expectSimilar: (buffer1, buffer2, maxDiffPixels, message) =>
      expect(compare(buffer1, buffer2, { maxDiffPixels }), message).toBeNull(),
  })
  // //////////////

  if (testConfig.status === 'passed') {
    await test.step('[fixture] Remove screenshots (test succeeded)', async () => {
      await fs.rm(imageFolder, { recursive: true, force: true })
    })
  } else {
    await test.step('[fixture] Not removing screenshots (test did not succeed)', () => {
      expect(1).toEqual(1)
    })
  }
}

This exposes snapImg, expectDifferent(img1, img2, 100) and expectSimilar.

In my test I can use it like this:

test('Test before and after', async ({ compareSnapshots }) => {
  const { expectDifferent, expectSimilar, snapImg, page } = compareSnapshots
  const one = await snapImg({ prefix: 'before' })

  // DO STUFF
  // page.locator(...).click()

  const two = await snapImg({ prefix: 'after' })

  expectDifferent(one, two, 400)
})

Upvotes: 0

d2vid
d2vid

Reputation: 2322

The problem with Playwright's toHaveScreenshot and toMatchSnapshot is that they're a bit over-engineered and will only compare a current screenshot to a screenshot from a previous test run. If you want to compare two screenshots that you have as Buffers in memory, you can use the getComparator method that Playwright uses behind the scenes:

import { getComparator } from 'playwright-core/lib/utils';

await page.goto('my website here');
const beforeImage = await page.screenshot({
    path: `./screenshots/before.png`
});
//
// some state changes implemented here
//
const afterImage = await page.screenshot({
  path: `./screenshots/after.png`
});

const comparator = getComparator('image/png');
expect(comparator(beforeImage, afterImage)).toBeNull();

The advantage of using getComparator is that it fuzzy matches, and you can set the threshold of how many pixels are allowed to be different. If you just want to check that the PNGs are exactly identical, a dead simple method to check for equality between the two screenshots is:

expect(Buffer.compare(beforeImage, afterImage)).toEqual(0)

Beware though - this simpler method is flakey and sensitive to a single pixel difference in rendering (such as if any animations/transitions are not completed or if there are differences in anti-aliasing).

Upvotes: 4

Basti
Basti

Reputation: 917

You can do something like this:

test('compare screenshots', async ({ page }, testInfo)=>{
  await page.goto(pageUrl);
  const screenshotTarget =  page.locator(scTarget);
  await expect(screenshotTarget).toHaveScreenshot( `${testInfo.title}.png`);
  //
  // some state changes implemented here
  //
  await expect(screenshotTarget).toHaveScreenshot( `${testInfo.title}.png`);
});

I prefer to use the test titel for naming my screenshots but it should also work if you just enter the same name twice. Then if you run your tests without --update-snapshots they should fail if some visual changes happened.

Upvotes: 3

Related Questions