| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -120,5 +120,8 @@ export default async function toMatchScreenshot( | |||
| 120 | 120 | ] | |
| 121 | 121 | .filter(element => element !== null) | |
| 122 | 122 | .join('\n'), | |
| 123 | + meta: { | ||
| 124 | + outcome: result.outcome, | ||
| 125 | + }, | ||
| 123 | 126 | } | |
| 124 | 127 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -156,14 +156,16 @@ export function createBrowserRunner( | |||
| 156 | 156 | } | |
| 157 | 157 | ||
| 158 | 158 | onTaskFinished = async (task: Task) => { | |
| 159 | + const lastErrorContext = task.result?.errors?.at(-1)?.context | ||
| 159 | 160 | if ( | |
| 160 | 161 | this.config.browser.screenshotFailures | |
| 161 | 162 | && document.body.clientHeight > 0 | |
| 162 | 163 | && task.result?.state === 'fail' | |
| 163 | 164 | && task.type === 'test' | |
| 164 | - && task.artifacts.every( | ||
| 165 | - artifact => artifact.type !== 'internal:toMatchScreenshot', | ||
| 166 | - ) | ||
| 165 | + && !( | ||
| 166 | + lastErrorContext | ||
| 167 | + && Reflect.get(lastErrorContext, 'assertionName') === 'toMatchScreenshot' | ||
| 168 | + && Reflect.get(lastErrorContext, 'meta')?.outcome !== 'unstable-screenshot') | ||
| 167 | 169 | ) { | |
| 168 | 170 | const screenshot = await page.screenshot({ | |
| 169 | 171 | timeout: this.config.browser.providerOptions?.actionTimeout ?? 5_000, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -273,6 +273,7 @@ function buildOutput( | |||
| 273 | 273 | case 'unstable-screenshot': | |
| 274 | 274 | return { | |
| 275 | 275 | pass: false, | |
| 276 | + outcome: outcome.type, | ||
| 276 | 277 | reference: outcome.reference && { | |
| 277 | 278 | path: outcome.reference.path, | |
| 278 | 279 | width: outcome.reference.image.metadata.width, | |
@@ -286,6 +287,7 @@ function buildOutput( | |||
| 286 | 287 | case 'missing-reference': { | |
| 287 | 288 | return { | |
| 288 | 289 | pass: false, | |
| 290 | + outcome: outcome.type, | ||
| 289 | 291 | reference: { | |
| 290 | 292 | path: outcome.reference.path, | |
| 291 | 293 | width: outcome.reference.image.metadata.width, | |
@@ -302,11 +304,12 @@ function buildOutput( | |||
| 302 | 304 | case 'update-reference': | |
| 303 | 305 | case 'matched-immediately': | |
| 304 | 306 | case 'matched-after-comparison': | |
| 305 | - return { pass: true } | ||
| 307 | + return { pass: true, outcome: outcome.type } | ||
| 306 | 308 | ||
| 307 | 309 | case 'mismatch': | |
| 308 | 310 | return { | |
| 309 | 311 | pass: false, | |
| 312 | + outcome: outcome.type, | ||
| 310 | 313 | reference: { | |
| 311 | 314 | path: outcome.reference.path, | |
| 312 | 315 | width: outcome.reference.image.metadata.width, | |
@@ -333,6 +336,7 @@ function buildOutput( | |||
| 333 | 336 | ||
| 334 | 337 | return { | |
| 335 | 338 | pass: false, | |
| 339 | + outcome: null as never, | ||
| 336 | 340 | actual: null, | |
| 337 | 341 | reference: null, | |
| 338 | 342 | diff: null, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,12 +17,20 @@ interface ScreenshotData { path: string; width: number; height: number } | |||
| 17 | 17 | export type ScreenshotMatcherOutput = Promise< | |
| 18 | 18 | { | |
| 19 | 19 | pass: false | |
| 20 | + outcome: | ||
| 21 | + | 'unstable-screenshot' | ||
| 22 | + | 'missing-reference' | ||
| 23 | + | 'mismatch' | ||
| 20 | 24 | reference: ScreenshotData | null | |
| 21 | 25 | actual: ScreenshotData | null | |
| 22 | 26 | diff: ScreenshotData | null | |
| 23 | 27 | message: string | |
| 24 | 28 | } | |
| 25 | 29 | | { | |
| 26 | 30 | pass: true | |
| 31 | + outcome: | ||
| 32 | + | 'update-reference' | ||
| 33 | + | 'matched-immediately' | ||
| 34 | + | 'matched-after-comparison' | ||
| 27 | 35 | } | |
| 28 | 36 | > | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -65,7 +65,12 @@ function getMatcherState( | |||
| 65 | 65 | } | |
| 66 | 66 | ||
| 67 | 67 | class JestExtendError extends Error { | |
| 68 | - constructor(message: string, public actual?: any, public expected?: any) { | ||
| 68 | + constructor( | ||
| 69 | + message: string, | ||
| 70 | + public actual?: any, | ||
| 71 | + public expected?: any, | ||
| 72 | + public context?: { assertionName: string; meta?: object }, | ||
| 73 | + ) { | ||
| 69 | 74 | super(message) | |
| 70 | 75 | } | |
| 71 | 76 | } | |
@@ -92,23 +97,33 @@ function JestExtendPlugin( | |||
| 92 | 97 | && typeof (result as any).then === 'function' | |
| 93 | 98 | ) { | |
| 94 | 99 | const thenable = result as PromiseLike<SyncExpectationResult> | |
| 95 | - return thenable.then(({ pass, message, actual, expected }) => { | ||
| 100 | + return thenable.then(({ pass, message, actual, expected, meta }) => { | ||
| 96 | 101 | if ((pass && isNot) || (!pass && !isNot)) { | |
| 97 | 102 | const errorMessage = customMessage != null | |
| 98 | 103 | ? customMessage | |
| 99 | 104 | : message() | |
| 100 | - throw new JestExtendError(errorMessage, actual, expected) | ||
| 105 | + throw new JestExtendError( | ||
| 106 | + errorMessage, | ||
| 107 | + actual, | ||
| 108 | + expected, | ||
| 109 | + { assertionName: expectAssertionName, meta }, | ||
| 110 | + ) | ||
| 101 | 111 | } | |
| 102 | 112 | }) | |
| 103 | 113 | } | |
| 104 | 114 | ||
| 105 | - const { pass, message, actual, expected } = result as SyncExpectationResult | ||
| 115 | + const { pass, message, actual, expected, meta } = result as SyncExpectationResult | ||
| 106 | 116 | ||
| 107 | 117 | if ((pass && isNot) || (!pass && !isNot)) { | |
| 108 | 118 | const errorMessage = customMessage != null | |
| 109 | 119 | ? customMessage | |
| 110 | 120 | : message() | |
| 111 | - throw new JestExtendError(errorMessage, actual, expected) | ||
| 121 | + throw new JestExtendError( | ||
| 122 | + errorMessage, | ||
| 123 | + actual, | ||
| 124 | + expected, | ||
| 125 | + { assertionName: expectAssertionName, meta }, | ||
| 126 | + ) | ||
| 112 | 127 | } | |
| 113 | 128 | } | |
| 114 | 129 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -91,6 +91,7 @@ export interface SyncExpectationResult { | |||
| 91 | 91 | message: () => string | |
| 92 | 92 | actual?: any | |
| 93 | 93 | expected?: any | |
| 94 | + meta?: object | ||
| 94 | 95 | } | |
| 95 | 96 | ||
| 96 | 97 | export type AsyncExpectationResult = Promise<SyncExpectationResult> | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,100 @@ | |||
| 1 | + import type { TestFsStructure } from '../../test-utils' | ||
| 2 | + import { describe, expect, test } from 'vitest' | ||
| 3 | + import { runInlineTests } from '../../test-utils' | ||
| 4 | + import utilsContent from '../fixtures/expect-dom/utils?raw' | ||
| 5 | + import { instances, provider } from '../settings' | ||
| 6 | + | ||
| 7 | + const testFilename = 'basic.test.ts' | ||
| 8 | + | ||
| 9 | + async function runBrowserTests( | ||
| 10 | + structure: TestFsStructure, | ||
| 11 | + ) { | ||
| 12 | + return runInlineTests({ | ||
| 13 | + ...structure, | ||
| 14 | + 'vitest.config.js': ` | ||
| 15 | + import { ${provider.name} } from '@vitest/browser-${provider.name}' | ||
| 16 | + export default { | ||
| 17 | + test: { | ||
| 18 | + browser: { | ||
| 19 | + enabled: true, | ||
| 20 | + screenshotFailures: true, | ||
| 21 | + provider: ${provider.name}(), | ||
| 22 | + ui: false, | ||
| 23 | + headless: true, | ||
| 24 | + instances: ${JSON.stringify(instances.slice(0, 1) /* logic not bound to browser instance */)}, | ||
| 25 | + }, | ||
| 26 | + reporters: ['verbose'], | ||
| 27 | + update: 'new', | ||
| 28 | + }, | ||
| 29 | + }`, | ||
| 30 | + }) | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + describe('failure screenshots', () => { | ||
| 34 | + describe('`toMatchScreenshot`', () => { | ||
| 35 | + test('usually does NOT produce a failure screenshot', async () => { | ||
| 36 | + const { stderr } = await runBrowserTests( | ||
| 37 | + { | ||
| 38 | + [testFilename]: /* ts */` | ||
| 39 | + import { page } from 'vitest/browser' | ||
| 40 | + import { test } from 'vitest' | ||
| 41 | + import { render } from './utils' | ||
| 42 | + | ||
| 43 | + test('screenshot-initial', async ({ expect }) => { | ||
| 44 | + render('<div data-testid="el">Test</div>') | ||
| 45 | + await expect(page.getByTestId('el')).toMatchScreenshot() | ||
| 46 | + }) | ||
| 47 | + `, | ||
| 48 | + 'utils.ts': utilsContent, | ||
| 49 | + }, | ||
| 50 | + ) | ||
| 51 | + | ||
| 52 | + expect(stderr).toContain('No existing reference screenshot found; a new one was created.') | ||
| 53 | + expect(stderr).not.toContain('Failure screenshot:') | ||
| 54 | + }) | ||
| 55 | + | ||
| 56 | + test('unstable screenshot fails produces a failure screenshot', async () => { | ||
| 57 | + const { stderr } = await runBrowserTests( | ||
| 58 | + { | ||
| 59 | + [testFilename]: /* ts */` | ||
| 60 | + import { page } from 'vitest/browser' | ||
| 61 | + import { test } from 'vitest' | ||
| 62 | + import { render } from './utils' | ||
| 63 | + | ||
| 64 | + test('screenshot-unstable', async ({ expect }) => { | ||
| 65 | + render('<div data-testid="el">Test</div>') | ||
| 66 | + await expect(page.getByTestId('el')).toMatchScreenshot({ timeout: 1 }) | ||
| 67 | + }) | ||
| 68 | + `, | ||
| 69 | + 'utils.ts': utilsContent, | ||
| 70 | + }, | ||
| 71 | + ) | ||
| 72 | + | ||
| 73 | + expect(stderr).toContain('Could not capture a stable screenshot within 1ms.') | ||
| 74 | + expect(stderr).toContain('Failure screenshot:') | ||
| 75 | + }) | ||
| 76 | + | ||
| 77 | + test('`expect.soft` produces a failure screenshot', async () => { | ||
| 78 | + const { stderr } = await runBrowserTests( | ||
| 79 | + { | ||
| 80 | + [testFilename]: /* ts */` | ||
| 81 | + import { page } from 'vitest/browser' | ||
| 82 | + import { test } from 'vitest' | ||
| 83 | + import { render } from './utils' | ||
| 84 | + | ||
| 85 | + test('screenshot-soft-then-fail', async ({ expect }) => { | ||
| 86 | + render('<div data-testid="el">Test</div>') | ||
| 87 | + await expect.soft(page.getByTestId('el')).toMatchScreenshot() | ||
| 88 | + expect(1).toBe(2) | ||
| 89 | + }) | ||
| 90 | + `, | ||
| 91 | + 'utils.ts': utilsContent, | ||
| 92 | + }, | ||
| 93 | + ) | ||
| 94 | + | ||
| 95 | + expect(stderr).toContain('No existing reference screenshot found; a new one was created.') | ||
| 96 | + expect(stderr).toContain('expected 1 to be 2') | ||
| 97 | + expect(stderr).toContain('Failure screenshot:') | ||
| 98 | + }) | ||
| 99 | + }) | ||
| 100 | + }) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments