fix(test runner): allow multiple missing snapshots per test (#10621)

Instead of failing right away, continue test execution but mark
the test as failed.
This commit is contained in:
Dmitry Gozman 2021-11-30 17:50:19 -08:00 committed by GitHub
parent 056d0cb5c1
commit 729da65eba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 17 deletions

View file

@ -26,7 +26,7 @@ import { TestInfoImpl, UpdateSnapshots } from '../types';
import { addSuffixToFilePath } from '../util'; import { addSuffixToFilePath } from '../util';
// Note: we require the pngjs version of pixelmatch to avoid version mismatches. // Note: we require the pngjs version of pixelmatch to avoid version mismatches.
const { PNG } = require(require.resolve('pngjs', { paths: [require.resolve('pixelmatch')] })); const { PNG } = require(require.resolve('pngjs', { paths: [require.resolve('pixelmatch')] })) as typeof import('pngjs');
const extensionToMimeType: { [key: string]: string } = { const extensionToMimeType: { [key: string]: string } = {
'dat': 'application/octet-string', 'dat': 'application/octet-string',
@ -36,14 +36,15 @@ const extensionToMimeType: { [key: string]: string } = {
'txt': 'text/plain', 'txt': 'text/plain',
}; };
const GoldenComparators: { [key: string]: any } = { type Comparator = (actualBuffer: Buffer | string, expectedBuffer: Buffer, mimeType: string, options?: any) => { diff?: Buffer; errorMessage?: string; } | null;
const GoldenComparators: { [key: string]: Comparator } = {
'application/octet-string': compareBuffersOrStrings, 'application/octet-string': compareBuffersOrStrings,
'image/png': compareImages, 'image/png': compareImages,
'image/jpeg': compareImages, 'image/jpeg': compareImages,
'text/plain': compareText, 'text/plain': compareText,
}; };
function compareBuffersOrStrings(actualBuffer: Buffer | string, expectedBuffer: Buffer, mimeType: string): { diff?: object; errorMessage?: string; } | null { function compareBuffersOrStrings(actualBuffer: Buffer | string, expectedBuffer: Buffer, mimeType: string): { diff?: Buffer; errorMessage?: string; } | null {
if (typeof actualBuffer === 'string') if (typeof actualBuffer === 'string')
return compareText(actualBuffer, expectedBuffer); return compareText(actualBuffer, expectedBuffer);
if (!actualBuffer || !(actualBuffer instanceof Buffer)) if (!actualBuffer || !(actualBuffer instanceof Buffer))
@ -53,7 +54,7 @@ function compareBuffersOrStrings(actualBuffer: Buffer | string, expectedBuffer:
return null; return null;
} }
function compareImages(actualBuffer: Buffer | string, expectedBuffer: Buffer, mimeType: string, options = {}): { diff?: object; errorMessage?: string; } | null { function compareImages(actualBuffer: Buffer | string, expectedBuffer: Buffer, mimeType: string, options = {}): { diff?: Buffer; errorMessage?: string; } | null {
if (!actualBuffer || !(actualBuffer instanceof Buffer)) if (!actualBuffer || !(actualBuffer instanceof Buffer))
return { errorMessage: 'Actual result should be a Buffer.' }; return { errorMessage: 'Actual result should be a Buffer.' };
@ -69,7 +70,7 @@ function compareImages(actualBuffer: Buffer | string, expectedBuffer: Buffer, mi
return count > 0 ? { diff: PNG.sync.write(diff) } : null; return count > 0 ? { diff: PNG.sync.write(diff) } : null;
} }
function compareText(actual: Buffer | string, expectedBuffer: Buffer): { diff?: object; errorMessage?: string; diffExtension?: string; } | null { function compareText(actual: Buffer | string, expectedBuffer: Buffer): { diff?: Buffer; errorMessage?: string; diffExtension?: string; } | null {
if (typeof actual !== 'string') if (typeof actual !== 'string')
return { errorMessage: 'Actual result should be a string' }; return { errorMessage: 'Actual result should be a string' };
const expected = expectedBuffer.toString('utf-8'); const expected = expectedBuffer.toString('utf-8');
@ -86,14 +87,13 @@ function compareText(actual: Buffer | string, expectedBuffer: Buffer): { diff?:
export function compare( export function compare(
actual: Buffer | string, actual: Buffer | string,
pathSegments: string[], pathSegments: string[],
snapshotPath: TestInfoImpl['snapshotPath'], testInfo: TestInfoImpl,
outputPath: TestInfoImpl['outputPath'],
updateSnapshots: UpdateSnapshots, updateSnapshots: UpdateSnapshots,
withNegateComparison: boolean, withNegateComparison: boolean,
options?: { threshold?: number } options?: { threshold?: number }
): { pass: boolean; message?: string; expectedPath?: string, actualPath?: string, diffPath?: string, mimeType?: string } { ): { pass: boolean; message?: string; expectedPath?: string, actualPath?: string, diffPath?: string, mimeType?: string } {
const snapshotFile = snapshotPath(...pathSegments); const snapshotFile = testInfo.snapshotPath(...pathSegments);
const outputFile = outputPath(...pathSegments); const outputFile = testInfo.outputPath(...pathSegments);
const expectedPath = addSuffixToFilePath(outputFile, '-expected'); const expectedPath = addSuffixToFilePath(outputFile, '-expected');
const actualPath = addSuffixToFilePath(outputFile, '-actual'); const actualPath = addSuffixToFilePath(outputFile, '-actual');
const diffPath = addSuffixToFilePath(outputFile, '-diff'); const diffPath = addSuffixToFilePath(outputFile, '-diff');
@ -116,6 +116,15 @@ export function compare(
console.log(message); console.log(message);
return { pass: true, message }; return { pass: true, message };
} }
if (updateSnapshots === 'missing') {
if (testInfo.status === 'passed')
testInfo.status = 'failed';
if (!('error' in testInfo))
testInfo.error = { value: 'Error: ' + message };
else if (testInfo.error?.value)
testInfo.error.value += '\nError: ' + message;
return { pass: true, message };
}
return { pass: false, message }; return { pass: false, message };
} }

View file

@ -51,8 +51,7 @@ export function toMatchSnapshot(this: ReturnType<Expect['getState']>, received:
const { pass, message, expectedPath, actualPath, diffPath, mimeType } = compare( const { pass, message, expectedPath, actualPath, diffPath, mimeType } = compare(
received, received,
pathSegments, pathSegments,
testInfo.snapshotPath, testInfo,
testInfo.outputPath,
updateSnapshots, updateSnapshots,
withNegateComparison, withNegateComparison,
options options

View file

@ -155,22 +155,31 @@ test('should fail on same snapshots with negate matcher', async ({ runInlineTest
expect(result.output).toContain('Expected result should be different from the actual one.'); expect(result.output).toContain('Expected result should be different from the actual one.');
}); });
test('should write missing expectations locally', async ({ runInlineTest }, testInfo) => { test('should write missing expectations locally twice and continue', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({ const result = await runInlineTest({
...files, ...files,
'a.spec.js': ` 'a.spec.js': `
const { test } = require('./helper'); const { test } = require('./helper');
test('is a test', ({}) => { test('is a test', ({}) => {
expect('Hello world').toMatchSnapshot('snapshot.txt'); expect('Hello world').toMatchSnapshot('snapshot.txt');
expect('Hello world2').toMatchSnapshot('snapshot2.txt');
console.log('Here we are!');
}); });
` `
}, {}, { CI: '' }); });
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
const snapshotOutputPath = testInfo.outputPath('a.spec.js-snapshots/snapshot.txt'); expect(result.failed).toBe(1);
expect(result.output).toContain(`${snapshotOutputPath} is missing in snapshots, writing actual`);
const data = fs.readFileSync(snapshotOutputPath); const snapshot1OutputPath = testInfo.outputPath('a.spec.js-snapshots/snapshot.txt');
expect(data.toString()).toBe('Hello world'); expect(result.output).toContain(`${snapshot1OutputPath} is missing in snapshots, writing actual`);
expect(fs.readFileSync(snapshot1OutputPath, 'utf-8')).toBe('Hello world');
const snapshot2OutputPath = testInfo.outputPath('a.spec.js-snapshots/snapshot2.txt');
expect(result.output).toContain(`${snapshot2OutputPath} is missing in snapshots, writing actual`);
expect(fs.readFileSync(snapshot2OutputPath, 'utf-8')).toBe('Hello world2');
expect(result.output).toContain('Here we are!');
}); });
test('shouldn\'t write missing expectations locally for negated matcher', async ({ runInlineTest }, testInfo) => { test('shouldn\'t write missing expectations locally for negated matcher', async ({ runInlineTest }, testInfo) => {