diff --git a/README.md b/README.md index 10ed024..f150ddc 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ See [the examples](./examples/README.md) for more detailed usage or read about a * `comparisonMethod`: (default: `pixelmatch`) (options `pixelmatch` or `ssim`) The method by which images are compared. `pixelmatch` does a pixel by pixel comparison, whereas `ssim` does a structural similarity comparison. `ssim` is a new experimental feature for jest-image-snapshot, but may become the default comparison method in the future. For a better understanding of how to use SSIM, see [Recommendations when using SSIM Comparison](#recommendations-when-using-ssim-comparison). * `customSnapshotsDir`: A custom absolute path of a directory to keep this snapshot in * `customDiffDir`: A custom absolute path of a directory to keep this diff in +* `storeReceivedOnFailure`: (default: `false`) Store the received images seperately from the composed diff images on failure. This can be useful when updating baseline images from CI. +* `customReceivedDir`: A custom absolute path of a directory to keep this received image in * `customSnapshotIdentifier`: A custom name to give this snapshot. If not provided one is computed automatically. When a function is provided it is called with an object containing `testPath`, `currentTestName`, `counter` and `defaultIdentifier` as its first argument. The function must return an identifier to use for the snapshot. If a path is given, the path will be created inside the snapshot/diff directories. * `diffDirection`: (default: `horizontal`) (options `horizontal` or `vertical`) Changes diff image layout direction * `noColors`: Removes coloring from console output, useful if storing the results in a file diff --git a/__tests__/__snapshots__/index.spec.js.snap b/__tests__/__snapshots__/index.spec.js.snap index d168f24..455028d 100644 --- a/__tests__/__snapshots__/index.spec.js.snap +++ b/__tests__/__snapshots__/index.spec.js.snap @@ -47,9 +47,11 @@ Object { "diffDirection": "horizontal", "failureThreshold": 0, "failureThresholdType": "pixel", + "receivedDir": "path/to/__image_snapshots__/__received_output__", "receivedImageBuffer": "pretendthisisanimagebuffer", "snapshotIdentifier": "test-spec-js-test-1", "snapshotsDir": "path/to/__image_snapshots__", + "storeReceivedOnFailure": false, "updatePassedSnapshot": false, "updateSnapshot": false, } diff --git a/__tests__/diff-snapshot.spec.js b/__tests__/diff-snapshot.spec.js index 640876e..e1aafa9 100644 --- a/__tests__/diff-snapshot.spec.js +++ b/__tests__/diff-snapshot.spec.js @@ -79,6 +79,7 @@ describe('diff-snapshot', () => { describe('diffImageToSnapshot', () => { const mockSnapshotsDir = path.normalize('/path/to/snapshots'); + const mockReceivedDir = path.normalize('/path/to/snapshots/__received_output__'); const mockDiffDir = path.normalize('/path/to/snapshots/__diff_output__'); const mockSnapshotIdentifier = 'id1'; const mockImagePath = './__tests__/stubs/TestImage.png'; @@ -147,6 +148,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -167,6 +169,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -190,6 +193,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -219,12 +223,62 @@ describe('diff-snapshot', () => { expect(mockWriteFileSync).toHaveBeenCalledTimes(1); }); + it('should write a received image if the test fails and storeReceivedOnFailure = true', () => { + const diffImageToSnapshot = setupTest({ snapshotExists: true, pixelmatchResult: 5000 }); + const result = diffImageToSnapshot({ + receivedImageBuffer: mockFailImageBuffer, + snapshotIdentifier: mockSnapshotIdentifier, + snapshotsDir: mockSnapshotsDir, + storeReceivedOnFailure: true, + receivedDir: mockReceivedDir, + diffDir: mockDiffDir, + updateSnapshot: false, + failureThreshold: 0, + failureThresholdType: 'pixel', + }); + + expect(result).toMatchObject({ + diffOutputPath: path.join(mockSnapshotsDir, '__diff_output__', 'id1-diff.png'), + receivedSnapshotPath: path.join(mockSnapshotsDir, '__received_output__', 'id1-received.png'), + diffRatio: 0.5, + diffPixelCount: 5000, + pass: false, + }); + + expect(mockWriteFileSync).toHaveBeenCalledTimes(2); + }); + + it('should not write a received image if the test fails and storeReceivedOnFailure = false', () => { + const diffImageToSnapshot = setupTest({ snapshotExists: true, pixelmatchResult: 5000 }); + const result = diffImageToSnapshot({ + receivedImageBuffer: mockFailImageBuffer, + snapshotIdentifier: mockSnapshotIdentifier, + snapshotsDir: mockSnapshotsDir, + storeReceivedOnFailure: false, + receivedDir: mockReceivedDir, + diffDir: mockDiffDir, + updateSnapshot: false, + failureThreshold: 0, + failureThresholdType: 'pixel', + }); + + expect(result).toMatchObject({ + diffOutputPath: path.join(mockSnapshotsDir, '__diff_output__', 'id1-diff.png'), + diffRatio: 0.5, + diffPixelCount: 5000, + pass: false, + }); + + expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + }); + it('should fail if image passed is a different size', () => { const diffImageToSnapshot = setupTest({ snapshotExists: true, pixelmatchResult: 5000 }); const result = diffImageToSnapshot({ receivedImageBuffer: mockBigImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -254,6 +308,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 250, @@ -271,6 +326,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockBigImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 250, @@ -290,6 +346,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockBigImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -309,6 +366,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -326,6 +384,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 250, @@ -344,6 +403,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 250, @@ -361,6 +421,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0.025, @@ -378,6 +439,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0.025, @@ -396,6 +458,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -420,6 +483,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, customDiffConfig: { @@ -450,6 +514,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, failureThreshold: 0, failureThresholdType: 'pixel', @@ -469,6 +534,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: path.join('parent', mockSnapshotIdentifier), snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, failureThreshold: 0, failureThresholdType: 'pixel', @@ -483,6 +549,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -498,6 +565,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -513,6 +581,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: true, updatePassedSnapshot: true, @@ -529,6 +598,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: path.join('parent', mockSnapshotIdentifier), snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: true, updatePassedSnapshot: true, @@ -545,6 +615,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: true, failureThreshold: 0, @@ -560,6 +631,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -576,6 +648,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: true, updatePassedSnapshot: true, @@ -592,6 +665,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, diffDirection: 'vertical', updateSnapshot: false, @@ -612,6 +686,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -629,6 +704,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -644,6 +720,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: true, updatePassedSnapshot: false, @@ -662,6 +739,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: true, updatePassedSnapshot: true, @@ -680,6 +758,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: true, updatePassedSnapshot: false, @@ -697,6 +776,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -713,6 +793,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, updateSnapshot: false, failureThreshold: 0, @@ -740,6 +821,7 @@ describe('diff-snapshot', () => { receivedImageBuffer: mockFailImageBuffer, snapshotIdentifier: mockSnapshotIdentifier, snapshotsDir: mockSnapshotsDir, + receivedDir: mockReceivedDir, diffDir: mockDiffDir, failureThreshold: 0, failureThresholdType: 'pixel', diff --git a/__tests__/index.spec.js b/__tests__/index.spec.js index 8464ba2..f1a72f5 100644 --- a/__tests__/index.spec.js +++ b/__tests__/index.spec.js @@ -415,6 +415,54 @@ describe('toMatchImageSnapshot', () => { expect(() => matcherAtTest('pretendthisisanimagebuffer')).not.toThrow(); }); + it('should pass with defaults', () => { + const mockTestContext = { + testPath: path.join('path', 'to', 'test.spec.js'), + currentTestName: 'test1', + isNot: false, + snapshotState: { + _counters: new Map(), + update: true, + updated: undefined, + added: undefined, + }, + }; + setupMock({ updated: true }); + + const runDiffImageToSnapshot = jest.fn(() => ({})); + jest.doMock('../src/diff-snapshot', () => ({ + runDiffImageToSnapshot, + })); + + const Chalk = jest.fn(); + jest.doMock('chalk', () => ({ + constructor: Chalk, + })); + const { toMatchImageSnapshot } = require('../src/index'); + const matcherAtTest = toMatchImageSnapshot.bind(mockTestContext); + + matcherAtTest(); + + expect(runDiffImageToSnapshot).toHaveBeenCalledWith({ + allowSizeMismatch: false, + blur: 0, + comparisonMethod: 'pixelmatch', + customDiffConfig: {}, + diffDir: 'path/to/__image_snapshots__/__diff_output__', + diffDirection: 'horizontal', + failureThreshold: 0, + failureThresholdType: 'pixel', + receivedDir: 'path/to/__image_snapshots__/__received_output__', + receivedImageBuffer: undefined, + snapshotIdentifier: 'test-spec-js-test-1-1', + snapshotsDir: 'path/to/__image_snapshots__', + storeReceivedOnFailure: false, + updatePassedSnapshot: false, + updateSnapshot: false, + }); + expect(Chalk).toHaveBeenCalledWith({}); + }); + it('can provide custom defaults', () => { const mockTestContext = { testPath: path.join('path', 'to', 'test.spec.js'), @@ -447,6 +495,8 @@ describe('toMatchImageSnapshot', () => { customDiffConfig, customSnapshotIdentifier, customSnapshotsDir: path.join('path', 'to', 'my-custom-snapshots-dir'), + customReceivedDir: path.join('path', 'to', 'my-custom-received-dir'), + storeReceivedOnFailure: true, customDiffDir: path.join('path', 'to', 'my-custom-diff-dir'), diffDirection: 'vertical', noColors: true, @@ -469,6 +519,8 @@ describe('toMatchImageSnapshot', () => { }, snapshotIdentifier: 'custom-test-spec-js-test-1-1', snapshotsDir: path.join('path', 'to', 'my-custom-snapshots-dir'), + receivedDir: path.join('path', 'to', 'my-custom-received-dir'), + storeReceivedOnFailure: true, diffDir: path.join('path', 'to', 'my-custom-diff-dir'), diffDirection: 'vertical', updateSnapshot: false, @@ -510,7 +562,9 @@ describe('toMatchImageSnapshot', () => { const toMatchImageSnapshot = configureToMatchImageSnapshot({ customDiffConfig: customConfig, customSnapshotsDir: path.join('path', 'to', 'my-custom-snapshots-dir'), + customReceivedDir: path.join('path', 'to', 'my-custom-received-dir'), customDiffDir: path.join('path', 'to', 'my-custom-diff-dir'), + storeReceivedOnFailure: true, noColors: true, runInProcess: true, }); @@ -527,8 +581,10 @@ describe('toMatchImageSnapshot', () => { }, snapshotIdentifier: 'test-spec-js-test-1-1', snapshotsDir: path.join('path', 'to', 'my-custom-snapshots-dir'), + receivedDir: path.join('path', 'to', 'my-custom-received-dir'), diffDir: path.join('path', 'to', 'my-custom-diff-dir'), diffDirection: 'horizontal', + storeReceivedOnFailure: true, updateSnapshot: false, updatePassedSnapshot: false, failureThreshold: 0, diff --git a/src/diff-snapshot.js b/src/diff-snapshot.js index a3f212f..50f3b32 100644 --- a/src/diff-snapshot.js +++ b/src/diff-snapshot.js @@ -189,6 +189,8 @@ function diffImageToSnapshot(options) { receivedImageBuffer, snapshotIdentifier, snapshotsDir, + storeReceivedOnFailure, + receivedDir, diffDir, diffDirection, updateSnapshot = false, @@ -209,6 +211,9 @@ function diffImageToSnapshot(options) { fs.writeFileSync(baselineSnapshotPath, receivedImageBuffer); result = { added: true }; } else { + const receivedSnapshotPath = path.join(receivedDir, `${snapshotIdentifier}-received.png`); + rimraf.sync(receivedSnapshotPath); + const diffOutputPath = path.join(diffDir, `${snapshotIdentifier}-diff.png`); rimraf.sync(diffOutputPath); @@ -269,6 +274,12 @@ function diffImageToSnapshot(options) { }); if (isFailure({ pass, updateSnapshot })) { + if (storeReceivedOnFailure) { + mkdirp.sync(path.dirname(receivedSnapshotPath)); + fs.writeFileSync(receivedSnapshotPath, receivedImageBuffer); + result = { receivedSnapshotPath }; + } + mkdirp.sync(path.dirname(diffOutputPath)); const composer = new ImageComposer({ direction: diffDirection, @@ -298,6 +309,7 @@ function diffImageToSnapshot(options) { fs.writeFileSync(diffOutputPath, pngBuffer); result = { + ...result, pass: false, diffSize, imageDimensions, diff --git a/src/index.js b/src/index.js index 5c06475..a849f6e 100644 --- a/src/index.js +++ b/src/index.js @@ -135,6 +135,8 @@ function configureToMatchImageSnapshot({ customDiffConfig: commonCustomDiffConfig = {}, customSnapshotIdentifier: commonCustomSnapshotIdentifier, customSnapshotsDir: commonCustomSnapshotsDir, + storeReceivedOnFailure: commonStoreReceivedOnFailure = false, + customReceivedDir: commonCustomReceivedDir, customDiffDir: commonCustomDiffDir, diffDirection: commonDiffDirection = 'horizontal', noColors: commonNoColors, @@ -151,6 +153,8 @@ function configureToMatchImageSnapshot({ return function toMatchImageSnapshot(received, { customSnapshotIdentifier = commonCustomSnapshotIdentifier, customSnapshotsDir = commonCustomSnapshotsDir, + storeReceivedOnFailure = commonStoreReceivedOnFailure, + customReceivedDir = commonCustomReceivedDir, customDiffDir = commonCustomDiffDir, diffDirection = commonDiffDirection, customDiffConfig = {}, @@ -189,6 +193,7 @@ function configureToMatchImageSnapshot({ }); const snapshotsDir = customSnapshotsDir || path.join(path.dirname(testPath), SNAPSHOTS_DIR); + const receivedDir = customReceivedDir || path.join(snapshotsDir, '__received_output__'); const diffDir = customDiffDir || path.join(snapshotsDir, '__diff_output__'); const baselineSnapshotPath = path.join(snapshotsDir, `${snapshotIdentifier}-snap.png`); OutdatedSnapshotReporter.markTouchedFile(baselineSnapshotPath); @@ -208,6 +213,8 @@ function configureToMatchImageSnapshot({ imageToSnapshot({ receivedImageBuffer: received, snapshotsDir, + storeReceivedOnFailure, + receivedDir, diffDir, diffDirection, snapshotIdentifier,