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:
parent
5f1188d195
commit
75eef09c0d
|
|
@ -43,7 +43,7 @@ import {
|
||||||
toHaveURL,
|
toHaveURL,
|
||||||
toHaveValue
|
toHaveValue
|
||||||
} from './matchers/matchers';
|
} from './matchers/matchers';
|
||||||
import { toMatchSnapshot, toHaveScreenshot } from './matchers/toMatchSnapshot';
|
import { toMatchSnapshot, toHaveScreenshot, getSnapshotName } from './matchers/toMatchSnapshot';
|
||||||
import type { Expect, TestError } from './types';
|
import type { Expect, TestError } from './types';
|
||||||
import matchers from 'expect/build/matchers';
|
import matchers from 'expect/build/matchers';
|
||||||
import { currentTestInfo } from './globals';
|
import { currentTestInfo } from './globals';
|
||||||
|
|
@ -189,6 +189,12 @@ function wrap(matcherName: string, matcher: any) {
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
return matcher.call(this, ...args);
|
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;
|
const INTERNAL_STACK_LENGTH = 4;
|
||||||
// at Object.__PWTRAP__[expect.toHaveText] (...)
|
// at Object.__PWTRAP__[expect.toHaveText] (...)
|
||||||
// at __EXTERNAL_MATCHER_TRAP__ (...)
|
// at __EXTERNAL_MATCHER_TRAP__ (...)
|
||||||
|
|
@ -202,7 +208,7 @@ function wrap(matcherName: string, matcher: any) {
|
||||||
const step = testInfo._addStep({
|
const step = testInfo._addStep({
|
||||||
location: frame && frame.file ? { file: path.resolve(process.cwd(), frame.file), line: frame.line || 0, column: frame.column || 0 } : undefined,
|
location: frame && frame.file ? { file: path.resolve(process.cwd(), frame.file), line: frame.line || 0, column: frame.column || 0 } : undefined,
|
||||||
category: 'expect',
|
category: 'expect',
|
||||||
title: customMessage || `expect${isSoft ? '.soft' : ''}${this.isNot ? '.not' : ''}.${matcherName}`,
|
title: customMessage || `expect${isSoft ? '.soft' : ''}${this.isNot ? '.not' : ''}.${matcherName}${titleSuffix}`,
|
||||||
canHaveChildren: true,
|
canHaveChildren: true,
|
||||||
forceNoParent: false
|
forceNoParent: false
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,19 @@ type SyncExpectationResult = {
|
||||||
type NameOrSegments = string | string[];
|
type NameOrSegments = string | string[];
|
||||||
const SNAPSHOT_COUNTER = Symbol('noname-snapshot-counter');
|
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> {
|
class SnapshotHelper<T extends ImageComparatorOptions> {
|
||||||
readonly testInfo: TestInfoImpl;
|
readonly testInfo: TestInfoImpl;
|
||||||
readonly expectedPath: string;
|
readonly expectedPath: string;
|
||||||
|
|
@ -56,6 +69,7 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
|
||||||
configOptions: ImageComparatorOptions,
|
configOptions: ImageComparatorOptions,
|
||||||
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & T,
|
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & T,
|
||||||
optOptions: T,
|
optOptions: T,
|
||||||
|
dryRun: boolean = false,
|
||||||
) {
|
) {
|
||||||
let options: T;
|
let options: T;
|
||||||
let name: NameOrSegments | undefined;
|
let name: NameOrSegments | undefined;
|
||||||
|
|
@ -68,11 +82,13 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
|
||||||
delete (options as any).name;
|
delete (options as any).name;
|
||||||
}
|
}
|
||||||
if (!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 = [
|
const fullTitleWithoutSpec = [
|
||||||
...testInfo.titlePath.slice(1),
|
...testInfo.titlePath.slice(1),
|
||||||
(testInfo as any)[SNAPSHOT_COUNTER],
|
(testInfo as any)[SNAPSHOT_COUNTER] + 1,
|
||||||
].join(' ');
|
].join(' ');
|
||||||
|
if (!dryRun)
|
||||||
|
++(testInfo as any)[SNAPSHOT_COUNTER];
|
||||||
name = sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.' + anonymousSnapshotExtension;
|
name = sanitizeForFilePath(trimLongString(fullTitleWithoutSpec)) + '.' + anonymousSnapshotExtension;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
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) => {
|
test('should not fail when racing with navigation', async ({ runInlineTest }, testInfo) => {
|
||||||
const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html'));
|
const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html'));
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue