diff --git a/package.json b/package.json index 45b13a18..95b7a78c 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,13 @@ "dependencies": { "ajv": "^7.0.3", "execa": "^5.1.1", - "native-image-diff": "^0.1.14", + "pixelmatch": "^5.2.1", "pngjs": "^6.0.0", "yargs": "^17.2.1" }, "devDependencies": { "@types/jest": "^26.0.19", + "@types/pixelmatch": "^5.2.4", "@types/pngjs": "^6.0.1", "@types/yargs": "^17.0.3", "jest": "^26.6.3", diff --git a/src/matchers.test.ts b/src/matchers.test.ts index e7704f3d..d187de98 100644 --- a/src/matchers.test.ts +++ b/src/matchers.test.ts @@ -1,23 +1,23 @@ import fs from 'fs'; -import * as nativeImageDiff from 'native-image-diff'; +import pixelmatch from 'pixelmatch'; +import { mocked } from 'ts-jest/utils'; import { toMatchBaseline } from './matchers'; +jest.mock('pixelmatch'); + describe('matchers.ts', () => { + const mockedPixelmatch = mocked(pixelmatch, true); + const imageHello1Data = `iVBORw0KGgoAAAANSUhEUgAAACUAAAALCAYAAAD4OERFAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAJaADAAQAAAABAAAACwAAAADN8bJQAAABcElEQVQ4Ec2UvytGURjHr1/lHd5JlF9leMNAiQzKbqCMBt5ikUEWMhkMDGZ/gEHZDMqCsr6JQel9yW8Dg4lBJvL5ck7dTudw3UG+9el5nvM859znnnPujaIoKoMq+Ffqo5uTFB1dMacLuuEixXxNaYRNd26lO/BHcZbnjEEecu4zy82AjnARbuEIOsBqEqdkmLGDAav1luAB7mEBtLYrNdUKd27Cxjq+d1iFTtiALZCG4QYGQLlTGAXJd3zTjB9CM7SB6schpF4Sj76kmnoCu2v9+OemcBc7Z3yZKbAN+5o6Jj+hQiM1uWMDj/U2Ze+Utlu7Jb1A5tP7Om9NnDexvtIz4/tMC4MHscQ1fm0sTuTa3XkLVD8zrretM9RjByGkIommWFJHWIjFiVzbVKh4n8QIVBvWsLMQ0jaJPKheF3wI9uBX+qmpZVarAV12fSnyVyCkdRI9cAm65K/w3Z0inU56Y/1LRBJVUNQODUmKfTUfKJc7FJ+heOgAAAAASUVORK5CYII=`; const imageHello1Buffer = Buffer.from(imageHello1Data, 'base64'); const imageHello2Data = `iVBORw0KGgoAAAANSUhEUgAAACUAAAALCAYAAAD4OERFAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAJaADAAQAAAABAAAACwAAAADN8bJQAAABsklEQVQ4Ec2UOyhFcRzHj1d5lEEhogyKQqEMyioDxYJBCoMMsngslDvYDQyUYlJsHimURZJQSi6RV15ZxGD0+Hxv///pdDq3bnfhW5/7e/3/5/4fv3Mcx3ESIAX+lWpZzWkcK7pmTiVUw1Uc83uYswZL0ALuwSQT/IXa+dNxgzY3DbkwC06ifpCuMAR3cATlYNWLEzYM2GQUq+dNwDM8wRjo2X7Vk5iEBdiFOWgDV7q+b5iCCliEFZCa4RYaQLVz6AAp6Pr6yR9CIZSAxneBX/kkMjzJZfyQJ3a0qHewp1aHf2kGbGGHjC/TB3bBQYs6od6tgUZa5KYNAmw6uRm4B20kIttTOm6dlvQJaRHPcYqxNTBiYjXjhfGDTBHJA0/hBj/bE3vdMoJ1ULtUwRtEZE/nyyZ89oNYu80x5GEbIZrOKBR4irrCfU9sXW12FYahFdwF4btXJj9IOyT1pqQa5rGDEE3aeSdovBq8CbbBr1ES+hRsgK5QaI4r9ZT3O6WjfDDVLOwevMArqMcyQQrqKZ1SGB5B16xF2lbAdaXaj49jtxqDox3ruEUsSmJQKegNi0u/XtRShUjycDoAAAAASUVORK5CYII=`; const imageHello2Buffer = Buffer.from(imageHello2Data, 'base64'); - const imageHelloDiffData = `iVBORw0KGgoAAAANSUhEUgAAACUAAAALCAYAAAD4OERFAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAJaADAAQAAAABAAAACwAAAADN8bJQAAAA0ElEQVQ4Ee2SsQsBYRiHH0qJGJSSMiib2Xir2WSWxb9hMhrMym5ktJrEIIuwSEmEuoUynN8N19Ht9xlc39P3696v3qf3++D/+RNwoLmBiQUH5ZqI+VUDaQeNLlxzsJXJXUJL0fJUol4Ic39BdQyrE8yVteiLepgOgV6aSl4kVaiIs/JQtAMHTfwogpWCh4T2omDC4aunJMpTOKbhqZz5LBp5U5IoSWI0g54NdgRun1JGsqQGouNen4QuygkRNyLjNZXAWjialJMVbhYLr/6T+xvERzxV3g04QAAAAABJRU5ErkJggg==`; - const imageHelloDiffBuffer = Buffer.from(imageHelloDiffData, 'base64'); - const readFileMock = jest.spyOn(fs, 'readFileSync'); const writeFileMock = jest.spyOn(fs, 'writeFileSync'); - const diffImagesMock = jest.spyOn(nativeImageDiff, 'diffImages'); - beforeAll(() => { process.env.OWL_PLATFORM = 'ios'; }); @@ -37,10 +37,7 @@ describe('matchers.ts', () => { .mockReturnValueOnce(imageHello1Buffer) .mockReturnValueOnce(imageHello1Buffer); - diffImagesMock.mockReturnValueOnce({ - image: { data: Buffer.alloc(100), width: 37, height: 11 }, - pixels: 0, - } as nativeImageDiff.DiffResult); + mockedPixelmatch.mockReturnValueOnce(0); const latestPath = 'latest/ios/screen.png'; @@ -58,10 +55,7 @@ describe('matchers.ts', () => { .mockReturnValueOnce(imageHello1Buffer) .mockReturnValueOnce(imageHello2Buffer); - diffImagesMock.mockReturnValueOnce({ - image: { data: imageHelloDiffBuffer, width: 37, height: 11 }, - pixels: 55, - } as nativeImageDiff.DiffResult); + mockedPixelmatch.mockReturnValueOnce(55); const latestPath = 'latest/ios/screen.png'; diff --git a/src/matchers.ts b/src/matchers.ts index 7ee5087f..6d809ab9 100644 --- a/src/matchers.ts +++ b/src/matchers.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import path from 'path'; +import pixelmatch from 'pixelmatch'; import { PNG } from 'pngjs'; -import { diffImages } from 'native-image-diff'; import { Platform } from './cli/types'; @@ -44,13 +44,21 @@ export const toMatchBaseline = (latestPath: string) => { const latestData = fs.readFileSync(latestPath); const latestImage = PNG.sync.read(latestData); - const { image: diff, pixels } = diffImages({ - image1: baselineImage, - image2: latestImage, - colorThreshold: 0.1, + const diffImage = new PNG({ + width: baselineImage.width, + height: baselineImage.height, }); - if (pixels === 0) { + const diffPixelsCount = pixelmatch( + baselineImage.data, + latestImage.data, + diffImage.data, + baselineImage.width, + baselineImage.height, + { threshold: 0 } + ); + + if (diffPixelsCount === 0) { return { message: () => `Compared screenshot to match baseline. No differences were found.`, @@ -59,15 +67,12 @@ export const toMatchBaseline = (latestPath: string) => { } fs.mkdirSync(path.dirname(diffPath), { recursive: true }); - - const diffPng = { ...diff! } as PNG; - const diffImage = PNG.sync.write(diffPng); - fs.writeFileSync(diffPath, diffImage); + fs.writeFileSync(diffPath, PNG.sync.write(diffImage)); return { message: () => - `Compared screenshot to match baseline. ${pixels} were different.`, - pass: pixels === 0, + `Compared screenshot to match baseline. ${diffPixelsCount} were different.`, + pass: diffPixelsCount === 0, }; }; diff --git a/yarn.lock b/yarn.lock index d2f302fe..d0c2f95e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -556,6 +556,13 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/pixelmatch@^5.2.4": + version "5.2.4" + resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6" + integrity sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ== + dependencies: + "@types/node" "*" + "@types/pngjs@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-6.0.1.tgz#c711ec3fbbf077fed274ecccaf85dd4673130072" @@ -2582,13 +2589,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -native-image-diff@^0.1.14: - version "0.1.14" - resolved "https://registry.yarnpkg.com/native-image-diff/-/native-image-diff-0.1.14.tgz#f5dc337b2ffbca3da4a6923524254d41a98acabf" - integrity sha512-E9kEAKZn7DvJznFyq/QxtE1b5HDyBcZN7fcYXwKvRHYZ7CY0/rynLoPKHDf/8n3kj6IwjACXC/EG3sor5tfXoQ== - dependencies: - request "^2.88.2" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2807,6 +2807,13 @@ pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" +pixelmatch@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65" + integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ== + dependencies: + pngjs "^4.0.1" + pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -2814,6 +2821,11 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pngjs@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" + integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== + pngjs@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821"