feat: show expectation name as part of toHaveScreenshot title (#12612)

This patch adds snapshot file name as part of `toHaveScreenshot`
and `toMatchSnapshot` step title.
This commit is contained in:
Andrey Lushnikov 2022-03-09 21:09:45 -07:00 committed by GitHub
parent 5f1188d195
commit 75eef09c0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 4 deletions

View File

@ -43,7 +43,7 @@ import {
toHaveURL,
toHaveValue
} from './matchers/matchers';
import { toMatchSnapshot, toHaveScreenshot } from './matchers/toMatchSnapshot';
import { toMatchSnapshot, toHaveScreenshot, getSnapshotName } from './matchers/toMatchSnapshot';
import type { Expect, TestError } from './types';
import matchers from 'expect/build/matchers';
import { currentTestInfo } from './globals';
@ -189,6 +189,12 @@ function wrap(matcherName: string, matcher: any) {
if (!testInfo)
return matcher.call(this, ...args);
let titleSuffix = '';
if (matcherName === 'toHaveScreenshot' || matcherName === 'toMatchSnapshot') {
const [received, nameOrOptions, optOptions] = args;
titleSuffix = `(${getSnapshotName(testInfo, received, nameOrOptions, optOptions)})`;
}
const INTERNAL_STACK_LENGTH = 4;
// at Object.__PWTRAP__[expect.toHaveText] (...)
// at __EXTERNAL_MATCHER_TRAP__ (...)
@ -202,7 +208,7 @@ function wrap(matcherName: string, matcher: any) {
const step = testInfo._addStep({
location: frame && frame.file ? { file: path.resolve(process.cwd(), frame.file), line: frame.line || 0, column: frame.column || 0 } : undefined,
category: 'expect',
title: customMessage || `expect${isSoft ? '.soft' : ''}${this.isNot ? '.not' : ''}.${matcherName}`,
title: customMessage || `expect${isSoft ? '.soft' : ''}${this.isNot ? '.not' : ''}.${matcherName}${titleSuffix}`,
canHaveChildren: true,
forceNoParent: false
});

View File

@ -38,6 +38,19 @@ type SyncExpectationResult = {
type NameOrSegments = string | string[];
const SNAPSHOT_COUNTER = Symbol('noname-snapshot-counter');
export function getSnapshotName(
testInfo: TestInfoImpl,
received: any,
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & MatchSnapshotOptions = {},
optOptions: MatchSnapshotOptions = {}
) {
const anonymousSnapshotExtension = typeof received === 'string' || Buffer.isBuffer(received) ? determineFileExtension(received) : 'png';
const helper = new SnapshotHelper(
testInfo, anonymousSnapshotExtension, {},
nameOrOptions, optOptions, true /* dryRun */);
return path.basename(helper.snapshotPath);
}
class SnapshotHelper<T extends ImageComparatorOptions> {
readonly testInfo: TestInfoImpl;
readonly expectedPath: string;
@ -56,6 +69,7 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
configOptions: ImageComparatorOptions,
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & T,
optOptions: T,
dryRun: boolean = false,
) {
let options: T;
let name: NameOrSegments | undefined;
@ -68,11 +82,13 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
delete (options as any).name;
}
if (!name) {
(testInfo as any)[SNAPSHOT_COUNTER] = ((testInfo as any)[SNAPSHOT_COUNTER] || 0) + 1;
(testInfo as any)[SNAPSHOT_COUNTER] = ((testInfo as any)[SNAPSHOT_COUNTER] || 0);
const fullTitleWithoutSpec = [
...testInfo.titlePath.slice(1),
(testInfo as any)[SNAPSHOT_COUNTER],
(testInfo as any)[SNAPSHOT_COUNTER] + 1,
].join(' ');
if (!dryRun)
++(testInfo as any)[SNAPSHOT_COUNTER];
name = sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.' + anonymousSnapshotExtension;
}

View File

@ -63,6 +63,44 @@ test('should fail to screenshot a page with infinite animation', async ({ runInl
expect(fs.existsSync(testInfo.outputPath('a.spec.js-snapshots', 'is-a-test-1.png'))).toBe(false);
});
test('should report toHaveScreenshot step with expectation name in title', async ({ runInlineTest }) => {
const result = await runInlineTest({
'reporter.ts': `
class Reporter {
onStepBegin(test, result, step) {
console.log('%% begin ' + step.title);
}
}
module.exports = Reporter;
`,
'playwright.config.ts': `
module.exports = {
reporter: './reporter',
};
`,
...files,
'a.spec.js': `
const { test } = require('./helper');
test('is a test', async ({ page }) => {
// Named expectation.
await expect(page).toHaveScreenshot('foo.png', { timeout: 2000 });
// Anonymous expectation.
await expect(page).toHaveScreenshot({ timeout: 2000 });
});
`
}, { 'reporter': '', 'workers': 1, 'update-snapshots': true });
expect(result.exitCode).toBe(0);
expect(result.output.split('\n').filter(line => line.startsWith('%%'))).toEqual([
`%% begin Before Hooks`,
`%% begin browserContext.newPage`,
`%% begin expect.toHaveScreenshot(foo.png)`,
`%% begin expect.toHaveScreenshot(is-a-test-1.png)`,
`%% begin After Hooks`,
`%% begin browserContext.close`,
]);
});
test('should not fail when racing with navigation', async ({ runInlineTest }, testInfo) => {
const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html'));
const result = await runInlineTest({