diff --git a/packages/browser/src/client/tester/runner.ts b/packages/browser/src/client/tester/runner.ts index 4cd4a7e9e..b8b3d1cfe 100644 --- a/packages/browser/src/client/tester/runner.ts +++ b/packages/browser/src/client/tester/runner.ts @@ -56,7 +56,7 @@ export function createBrowserRunner( onTaskFinished = async (task: Task) => { if (this.config.browser.screenshotFailures && task.result?.state === 'fail') { - await page.screenshot() + task.meta.failScreenshotPath = await page.screenshot() } } diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 91f694924..75a8775e3 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -27,6 +27,7 @@ interface PrintErrorOptions { fullStack?: boolean showCodeFrame?: boolean printProperties?: boolean + screenshotPaths?: string[] } interface PrintErrorResult { @@ -110,14 +111,19 @@ export function printError( } }) - const errorProperties = printProperties - ? getErrorProperties(e) - : {} - if (type) { printErrorType(type, project.ctx) } printErrorMessage(e, logger) + if (options.screenshotPaths?.length) { + const length = options.screenshotPaths.length + logger.error(`\nFailure screenshot${length > 1 ? 's' : ''}:`) + logger.error(options.screenshotPaths.map(p => ` - ${c.dim(relative(process.cwd(), p))}`).join('\n')) + if (!e.diff) { + logger.error() + } + } + if (e.codeFrame) { logger.error(`${e.codeFrame}\n`) } @@ -132,6 +138,10 @@ export function printError( logger.error(c.yellow(e.frame)) } else { + const errorProperties = printProperties + ? getErrorProperties(e) + : {} + printStack(logger, project, stacks, nearest, errorProperties, (s) => { if (showCodeFrame && s === nearest && nearest) { const sourceCode = readFileSync(nearest.file, 'utf-8') diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 5c01e76d7..e44e5de88 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -17,6 +17,7 @@ interface ErrorOptions { fullStack?: boolean project?: WorkspaceProject verbose?: boolean + screenshotPaths?: string[] } const ESC = '\x1B[' @@ -99,6 +100,7 @@ export class Logger { showCodeFrame: true, logger: this, printProperties: options.verbose, + screenshotPaths: options.screenshotPaths, }) } diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index b93c2867f..809698395 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -603,8 +603,9 @@ export abstract class BaseReporter implements Reporter { )}${name}`, ) } + const screenshots = tasks.filter(t => t.meta?.failScreenshotPath).map(t => t.meta?.failScreenshotPath as string) const project = this.ctx.getProjectByTaskId(tasks[0].id) - this.ctx.logger.printError(error, { project, verbose: this.verbose }) + this.ctx.logger.printError(error, { project, verbose: this.verbose, screenshotPaths: screenshots }) errorDivider() } } diff --git a/packages/vitest/src/types/global.ts b/packages/vitest/src/types/global.ts index 14d27e781..b6c526361 100644 --- a/packages/vitest/src/types/global.ts +++ b/packages/vitest/src/types/global.ts @@ -81,6 +81,7 @@ declare module '@vitest/runner' { interface TaskMeta { typecheck?: boolean benchmark?: boolean + failScreenshotPath?: string } interface File { diff --git a/test/browser/specs/runner.test.ts b/test/browser/specs/runner.test.ts index 539511dec..eb6c62686 100644 --- a/test/browser/specs/runner.test.ts +++ b/test/browser/specs/runner.test.ts @@ -36,6 +36,8 @@ describe('running browser tests', async () => { expect(stderr).toMatch(/- 2\s+\+ 1/) expect(stderr).toContain('Expected to be') expect(stderr).toContain('But got') + expect(stderr).toContain('Failure screenshot') + expect(stderr).toContain('__screenshots__/failing') }) test('logs are redirected to stdout', () => {