Files
vitest/test/browser/specs/to-match-screenshot.test.ts

308 lines
9.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Stats } from 'node:fs'
import type { ViteUserConfig } from 'vitest/config'
import type { TestFsStructure } from '../../test-utils'
import { platform } from 'node:os'
import { describe, expect, test } from 'vitest'
import { runInlineTests } from '../../test-utils'
import { extractToMatchScreenshotPaths } from '../fixtures/expect-dom/utils'
import utilsContent from '../fixtures/expect-dom/utils?raw'
import { instances, provider } from '../settings'
const testFilename = 'basic.test.ts'
const testName = 'screenshot-snapshot'
const bgColor = '#fff'
const testContent = /* ts */`
import { page, server } from 'vitest/browser'
import { describe, test } from 'vitest'
import { render } from './utils'
const dataTestId = 'inline-test'
test('${testName}', async ({ expect }) => {
render('<div data-testid="' + dataTestId + '" style="background-color: ${bgColor}; font-size: 20px;">Inline Test</div>')
await expect(page.getByTestId(dataTestId)).toMatchScreenshot()
})
`
async function runBrowserTests(
structure: TestFsStructure,
config: ViteUserConfig['test'] = {},
) {
return runInlineTests({
...structure,
'vitest.config.js': `
export default {
test: {
browser: {
ui: false,
},
},
}`,
}, {
$cliOptions: {
watch: true,
},
browser: {
enabled: true,
screenshotFailures: false,
provider,
headless: true,
instances,
viewport: {
width: 400,
height: 200,
},
},
reporters: ['verbose'],
...config,
})
}
describe('--watch', () => {
test(
'fails when creating a snapshot for the first time and does NOT update it afterwards',
async () => {
const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
{
update: 'new',
},
)
const references = extractToMatchScreenshotPaths(stderr, testName)
const referenceStats: Record<typeof references[number], Omit<Stats, 'atime' | 'atimeMs'>> = {}
for (const referencePath of references) {
expect(stderr).toContain(`No existing reference screenshot found; a new one was created. Review it before running tests again.\n\nReference screenshot:\n ${referencePath}`)
const { atime: _1, atimeMs: _2, ...referenceStat } = fs.statFile(referencePath)
referenceStats[referencePath] = referenceStat
}
fs.editFile(testFilename, content => `${content}\n`)
vitest.resetOutput()
await vitest.waitForStdout(`Test Files ${instances.length} passed`)
for (const instance of instances) {
expect(vitest.stdout).toContain(`✓ |${instance.browser}| basic.test.ts > screenshot-snapshot`)
}
for (const referencePath of references) {
const { atime: _3, atimeMs: _4, ...newReferenceStat } = fs.statFile(referencePath)
expect(referenceStats[referencePath]).toEqual(newReferenceStat)
}
},
)
test(
'creates a reference and fails when changing the DOM content',
async () => {
const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
{
update: 'new',
},
)
expect(stderr).toContain(`No existing reference screenshot found; a new one was created. Review it before running tests again.\n\nReference screenshot:`)
fs.editFile(testFilename, content => content.replace(bgColor, '#0ff'))
vitest.resetOutput()
await vitest.waitForStdout(`Test Files ${instances.length} failed`)
for (const instance of instances) {
expect(vitest.stdout).toContain(`× |${instance.browser}| basic.test.ts > screenshot-snapshot`)
}
expect(vitest.stdout).toContain('Screenshot does not match the stored reference.')
expect(vitest.stdout).toMatch(/\d+ pixels \(ratio 0.\d{2}\) differ\./)
},
)
test(
'uses the `screenshotDirectory` option from config',
async () => {
const customDir = 'my-screenshots'
const { stderr } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
{
browser: {
enabled: true,
screenshotFailures: false,
provider,
headless: true,
instances,
viewport: {
width: 400,
height: 200,
},
expect: {
toMatchScreenshot: {
screenshotDirectory: customDir,
},
},
},
update: 'new',
},
)
const references = extractToMatchScreenshotPaths(stderr, testName)
expect(references.length).toBeGreaterThan(0)
for (const referencePath of references) {
expect(referencePath).toContain(`/${customDir}/`)
}
},
)
describe('--update', () => {
test(
'creates snapshot and does NOT update it if reference matches',
async () => {
const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
{
update: true,
},
)
expect(stderr).toMatchInlineSnapshot(`""`)
const osPlatform = platform()
const references = extractToMatchScreenshotPaths(stderr, testName)
.map(referencePath => [referencePath, fs.statFile(referencePath)] as const)
fs.editFile(testFilename, content => `${content}\n`)
vitest.resetOutput()
await vitest.waitForStdout(`Test Files ${instances.length} passed`)
for (const instance of instances) {
expect(vitest.stdout).toContain(`✓ |${instance.browser}| basic.test.ts > screenshot-snapshot`)
}
// only atime should change since reference should NOT be updated
for (const [referencePath, referenceStat] of references) {
const {
atime,
atimeMs,
...diffs
} = fs.statFile(referencePath)
expect(referenceStat).toEqual(expect.objectContaining(diffs))
// win32 does not update `atime` by default
if (osPlatform === 'win32') {
expect(atime.getTime()).toEqual(referenceStat.atime.getTime())
expect(atimeMs).toEqual(referenceStat.atimeMs)
}
else {
expect(atime.getTime()).toBeGreaterThan(referenceStat.atime.getTime())
expect(atimeMs).toBeGreaterThan(referenceStat.atimeMs)
}
}
},
)
test(
'creates snapshot and updates it if reference mismatches',
async () => {
const { fs, stderr, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
{
update: true,
},
)
expect(stderr).toMatchInlineSnapshot(`""`)
const references = extractToMatchScreenshotPaths(stderr, testName)
.map(referencePath => [referencePath, fs.statFile(referencePath)] as const)
fs.editFile(testFilename, content => content.replace(bgColor, '#000'))
vitest.resetOutput()
await vitest.waitForStdout(`Test Files ${instances.length} passed`)
for (const instance of instances) {
expect(vitest.stdout).toContain(`✓ |${instance.browser}| basic.test.ts > screenshot-snapshot`)
}
// atime, ctime, mtime, and size should change since reference should be updated
for (const [referencePath, referenceStat] of references) {
const {
atime,
atimeMs,
ctime,
ctimeMs,
mtime,
mtimeMs,
size,
...diffs
} = fs.statFile(referencePath)
expect(referenceStat).toEqual(expect.objectContaining(diffs))
expect(atime.getTime()).toBeGreaterThan(referenceStat.atime.getTime())
expect(ctime.getTime()).toBeGreaterThan(referenceStat.ctime.getTime())
expect(mtime.getTime()).toBeGreaterThan(referenceStat.mtime.getTime())
expect(atimeMs).toBeGreaterThan(referenceStat.atimeMs)
expect(ctimeMs).toBeGreaterThan(referenceStat.ctimeMs)
expect(mtimeMs).toBeGreaterThan(referenceStat.mtimeMs)
}
},
)
})
// tests whether the screenshots are stable in non-UI and UI mode
test(
'screenshots match across non-UI and UI mode',
async () => {
const { fs, vitest } = await runBrowserTests(
{
[testFilename]: testContent,
'utils.ts': utilsContent,
},
{
update: 'new',
},
)
await vitest.waitForStderr(`Failed Tests ${instances.length}`, 20_000)
vitest.resetOutput()
// switch to UI mode
fs.editFile('vitest.config.js', content => content.replace('ui: false,', 'ui: true,'))
await vitest.waitForStdout(`Test Files ${instances.length} passed`, 20_000)
},
)
})