···678678 })
679679 ```
680680681681+## toMatchFileSnapshot
682682+683683+- **Type:** `<T>(filepath: string, message?: string) => Promise<void>`
684684+685685+ Compare or update the snapshot with the content of a file explicitly specified (instead of the `.snap` file).
686686+687687+ ```ts
688688+ import { expect, it } from 'vitest'
689689+690690+ it('render basic', async () => {
691691+ const result = renderHTML(h('div', { class: 'foo' }))
692692+ await expect(result).toMatchFileSnapshot('./test/basic.output.html')
693693+ })
694694+ ```
695695+696696+ Note that since file system operation is async, you need to use `await` with `toMatchFileSnapshot()`.
681697682698## toThrowErrorMatchingSnapshot
683699
+17
docs/guide/snapshot.md
···7979vitest -u
8080```
81818282+## File Snapshots
8383+8484+When calling `toMatchSnapshot()`, we store all snapshots in a formatted snap file. That means we need to escaping some characters (namely the double-quote `"` and backtick `\``) in the snapshot string. Meanwhile, you might lose the syntax highlighting for the snapshot content (if they are in some language).
8585+8686+To improve this case, we introduce [`toMatchFileSnapshot()`](/api/expect#tomatchfilesnapshot) to explicitly snapshot in a file. This allows you to assign any file extension to the snapshot file, and making them more readable.
8787+8888+```ts
8989+import { expect, it } from 'vitest'
9090+9191+it('render basic', async () => {
9292+ const result = renderHTML(h('div', { class: 'foo' }))
9393+ await expect(result).toMatchFileSnapshot('./test/basic.output.html')
9494+})
9595+```
9696+9797+It will compare with the content of `./test/basic.output.html`. And can be written back with the `--update` flag.
9898+8299## Image Snapshots
8310084101It's also possible to snapshot images using [`jest-image-snapshot`](https://github.com/americanexpress/jest-image-snapshot).
···11+export function recordAsyncExpect(test: any, promise: Promise<any>) {
22+ // record promise for test, that resolves before test ends
33+ if (test) {
44+ // if promise is explicitly awaited, remove it from the list
55+ promise = promise.finally(() => {
66+ const index = test.promises.indexOf(promise)
77+ if (index !== -1)
88+ test.promises.splice(index, 1)
99+ })
1010+1111+ // record promise
1212+ if (!test.promises)
1313+ test.promises = []
1414+ test.promises.push(promise)
1515+ }
1616+1717+ return promise
1818+}
+1
packages/runner/src/index.ts
···22export { test, it, describe, suite, getCurrentSuite } from './suite'
33export { beforeAll, beforeEach, afterAll, afterEach, onTestFailed } from './hooks'
44export { setFn, getFn } from './map'
55+export { getCurrentTest } from './test-state'
56export * from './types'
+17-4
packages/runner/src/run.ts
···145145 await fn()
146146 }
147147148148+ // some async expect will be added to this array, in case user forget to await theme
149149+ if (test.promises) {
150150+ const result = await Promise.allSettled(test.promises)
151151+ const errors = result.map(r => r.status === 'rejected' ? r.reason : undefined).filter(Boolean)
152152+ if (errors.length)
153153+ throw errors
154154+ }
155155+148156 await runner.onAfterTryTest?.(test, retryCount)
149157150158 test.result.state = 'pass'
···197205198206function failTask(result: TaskResult, err: unknown, runner: VitestRunner) {
199207 result.state = 'fail'
200200- const error = processError(err, runner.config)
201201- result.error = error
202202- result.errors ??= []
203203- result.errors.push(error)
208208+ const errors = Array.isArray(err)
209209+ ? err
210210+ : [err]
211211+ for (const e of errors) {
212212+ const error = processError(e, runner.config)
213213+ result.error ??= error
214214+ result.errors ??= []
215215+ result.errors.push(error)
216216+ }
204217}
205218206219function markTasksAsSkipped(suite: Suite, runner: VitestRunner) {
+30-1
packages/snapshot/src/client.ts
···11import { deepMergeSnapshot } from './port/utils'
22import SnapshotState from './port/state'
33import type { SnapshotStateOptions } from './types'
44+import type { RawSnapshotInfo } from './port/rawSnapshot'
4556const createMismatchError = (message: string, actual: unknown, expected: unknown) => {
67 const error = new Error(message)
···3536 inlineSnapshot?: string
3637 error?: Error
3738 errorMessage?: string
3939+ rawSnapshot?: RawSnapshotInfo
3840}
39414042export class SnapshotClient {
···7981 }
80828183 /**
8282- * Should be overriden by the consumer.
8484+ * Should be overridden by the consumer.
8385 *
8486 * Vitest checks equality with @vitest/expect.
8587 */
···9799 inlineSnapshot,
98100 error,
99101 errorMessage,
102102+ rawSnapshot,
100103 } = options
101104 let { received } = options
102105···134137 isInline,
135138 error,
136139 inlineSnapshot,
140140+ rawSnapshot,
137141 })
138142139143 if (!pass)
140144 throw createMismatchError(`Snapshot \`${key || 'unknown'}\` mismatched`, actual?.trim(), expected?.trim())
145145+ }
146146+147147+ async assertRaw(options: AssertOptions): Promise<void> {
148148+ if (!options.rawSnapshot)
149149+ throw new Error('Raw snapshot is required')
150150+151151+ const {
152152+ filepath = this.filepath,
153153+ rawSnapshot,
154154+ } = options
155155+156156+ if (rawSnapshot.content == null) {
157157+ if (!filepath)
158158+ throw new Error('Snapshot cannot be used outside of test')
159159+160160+ const snapshotState = this.getSnapshotState(filepath)
161161+162162+ // save the filepath, so it don't lose even if the await make it out-of-context
163163+ options.filepath ||= filepath
164164+ // resolve and read the raw snapshot file
165165+ rawSnapshot.file = await snapshotState.environment.resolveRawPath(filepath, rawSnapshot.file)
166166+ rawSnapshot.content = await snapshotState.environment.readSnapshotFile(rawSnapshot.file) || undefined
167167+ }
168168+169169+ return this.assert(options)
141170 }
142171143172 async resetCurrent() {
···5959 fails?: boolean
6060 context: TestContext & ExtraContext
6161 onFailed?: OnTestFailedHandler[]
6262+ /**
6363+ * Store promises (from async expects) to wait for them before finishing the test
6464+ */
6565+ promises?: Promise<any>[]
6266}
63676468export type Task = Test | Suite | TaskCustom | File