mirror of
https://github.com/hanzoai/vitest.git
synced 2026-10-30 14:46:34 +00:00
308 lines
9.0 KiB
TypeScript
308 lines
9.0 KiB
TypeScript
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)
|
||
},
|
||
)
|
||
})
|