From 75eef09c0def39f7f1dc5d32908b213096a84617 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Wed, 9 Mar 2022 21:09:45 -0700 Subject: [PATCH] feat: show expectation name as part of toHaveScreenshot title (#12612) This patch adds snapshot file name as part of `toHaveScreenshot` and `toMatchSnapshot` step title. --- packages/playwright-test/src/expect.ts | 10 ++++- .../src/matchers/toMatchSnapshot.ts | 20 +++++++++- .../to-have-screenshot.spec.ts | 38 +++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/packages/playwright-test/src/expect.ts b/packages/playwright-test/src/expect.ts index 16c8f45d7a..34d615867b 100644 --- a/packages/playwright-test/src/expect.ts +++ b/packages/playwright-test/src/expect.ts @@ -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 }); diff --git a/packages/playwright-test/src/matchers/toMatchSnapshot.ts b/packages/playwright-test/src/matchers/toMatchSnapshot.ts index a5b9c6bc95..f21179a729 100644 --- a/packages/playwright-test/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright-test/src/matchers/toMatchSnapshot.ts @@ -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 { readonly testInfo: TestInfoImpl; readonly expectedPath: string; @@ -56,6 +69,7 @@ class SnapshotHelper { 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 { 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; } diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index 02c559194e..da10b1e2d7 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -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({