diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 5af62f4e..8f3f5719 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -7,8 +7,13 @@ import { groupIconMdPlugin, groupIconVitePlugin, } from 'vitepress-plugin-group-icons' +<<<<<<< HEAD import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' import { version } from '../package.json' +======= +import llmstxt from 'vitepress-plugin-llms' +import { version } from '../../package.json' +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 import { teamMembers } from './contributors' import { bluesky, @@ -79,6 +84,7 @@ export default ({ mode }: { mode: string }) => { 'qwik': 'logos:qwik-icon', }, }), + llmstxt(), ], }, markdown: { @@ -131,7 +137,7 @@ export default ({ mode }: { mode: string }) => { footer: { message: 'Released under the MIT License.', - copyright: 'Copyright © 2021-PRESENT Anthony Fu, Matías Capeletto and Vitest contributors', + copyright: 'Copyright © 2021-PRESENT VoidZero Inc. and Vitest contributors', }, nav: [ @@ -283,6 +289,11 @@ export default ({ mode }: { mode: string }) => { link: '/guide/browser/multiple-setups', docFooterText: 'Multiple Setups | Browser Mode', }, + { + text: 'Visual Regression Testing', + link: '/guide/browser/visual-regression-testing', + docFooterText: 'Visual Regression Testing | Browser Mode', + }, ], }, { diff --git a/.vitepress/sponsors.ts b/.vitepress/sponsors.ts index aa00c314..ec4a3920 100644 --- a/.vitepress/sponsors.ts +++ b/.vitepress/sponsors.ts @@ -45,6 +45,11 @@ const vitestSponsors = { url: 'https://oomol.com/', img: '/oomol.svg', }, + { + name: 'Mailmeteor', + url: 'https://mailmeteor.com/', + img: '/mailmeteor.svg', + }, ], } satisfies Record diff --git a/advanced/runner.md b/advanced/runner.md index 7beaf8bc..95bf101c 100644 --- a/advanced/runner.md +++ b/advanced/runner.md @@ -130,14 +130,20 @@ export default CustomRunner ``` ::: warning +<<<<<<< HEAD Vitest 还会将 `ViteNodeRunner` 的实例作为 `__vitest_executor` 属性注入。你可以使用它来处理 `importFile` 方法中的文件(这是 `TestRunner` 和 `BenchmarkRunner` 的默认行为)。 `ViteNodeRunner` 暴露了 `executeId` 方法,该方法用于在友好的 Vite 环境中导入测试文件。这意味着它会在运行时解析导入并转换文件内容,以便 Node 能够理解。 +======= +Vitest also injects an instance of `ModuleRunner` from `vite/module-runner` as `moduleRunner` property. You can use it to process files in `importFile` method (this is default behavior of `TestRunner` and `BenchmarkRunner`). + +`ModuleRunner` exposes `import` method, which is used to import test files in a Vite-friendly environment. Meaning, it will resolve imports and transform file content at runtime so that Node can understand it: +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ```ts export default class Runner { async importFile(filepath: string) { - await this.__vitest_executor.executeId(filepath) + await this.moduleRunner.import(filepath) } } ``` diff --git a/api/expect.md b/api/expect.md index 1b746c0b..b1316a16 100644 --- a/api/expect.md +++ b/api/expect.md @@ -1501,7 +1501,11 @@ test.each(errorDirs)('build fails with "%s"', async (dir) => { - **类型:** `() => any` +<<<<<<< HEAD 该非对称匹配器与相等检查一起使用时,将始终返回 `true`。如果只想确定属性是否存在,那么它就很有用。 +======= +This asymmetric matcher matches anything except `null` or `undefined`. Useful if you just want to be sure that a property exists with any value that's not either `null` or `undefined`. +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ```ts import { expect, test } from 'vitest' diff --git a/config/index.md b/config/index.md index 2dd927e9..1b00deec 100644 --- a/config/index.md +++ b/config/index.md @@ -227,8 +227,13 @@ Vite 将处理内联模块。这可能有助于处理以 ESM 格式传送 `.js` #### deps.optimizer {#deps-optimizer} +<<<<<<< HEAD - **类型:** `{ ssr?, web? }` - **参考:** [依赖优化选项](https://cn.vitejs.dev/config/dep-optimization-options.html) +======= +- **Type:** `{ ssr?, client? }` +- **See also:** [Dep Optimization Options](https://vitejs.dev/config/dep-optimization-options.html) +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 启用依赖优化。如果你有很多测试,这可能会提高它们的性能。 @@ -239,7 +244,11 @@ Vite 将处理内联模块。这可能有助于处理以 ESM 格式传送 `.js` - 你的 `alias` 配置现在在捆绑包中得到处理 - 测试中的代码更接近于它在浏览器中的运行方式 +<<<<<<< HEAD 请注意,只有 `deps.experimentalOptimizer?.[mode].include` 选项中的包会被捆绑(一些插件会自动填充它,比如 Svelte)。 你可以在 [Vite](https://cn.vitejs.dev/config/dep-optimization-options.html) 文档中阅读有关可用选项的更多信息。默认情况,Vitest 的 `experimentalOptimizer.web` 用在 `jsdom` 和 `happy-dom`, 在 `node` 和 `edge` 环境下使用 `experimentalOptimizer.ssr`,但这可以在 [`transformMode`](#transformmode) 进行配置。 +======= +Be aware that only packages in `deps.optimizer?.[mode].include` option are bundled (some plugins populate this automatically, like Svelte). You can read more about available options in [Vite](https://vitejs.dev/config/dep-optimization-options.html) docs (Vitest doesn't support `disable` and `noDiscovery` options). By default, Vitest uses `optimizer.client` for `jsdom` and `happy-dom` environments, and `optimizer.ssr` for `node` and `edge` environments. +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 此选项还继承了你的 `optimizeDeps` 配置(对于 web 环境, Vitest 将会继承 `optimizeDeps`,对于 ssr 则是 `ssr.optimizeDeps`)。如果你在 `deps.experimentalOptimizer` 中重新定义 `include`/`exclude`/`entries` 选项,它将在运行测试时覆盖你的 `optimizeDeps`。如果它们在 `exclude` 中配置,Vitest 会自动从 `include` 中删除相同的选项。 @@ -254,13 +263,21 @@ Vite 将处理内联模块。这可能有助于处理以 ESM 格式传送 `.js` 启用依赖优化。 +<<<<<<< HEAD ::: warning 此选项仅适用于 Vite 4.3.2 及更高版本。 ::: +======= +#### deps.client {#deps-client} +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 #### deps.web 0.34.2+ +<<<<<<< HEAD #### deps.web +======= +Options that are applied to external files when the environment is set to `client`. By default, `jsdom` and `happy-dom` use `client` environment, while `node` and `edge` environments use `ssr`, so these options will have no affect on files inside those environments. +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 - **类型:** `{ transformAssets?, ... }` @@ -268,7 +285,7 @@ Vite 将处理内联模块。这可能有助于处理以 ESM 格式传送 `.js` 通常,`node_modules` 内的文件是外部化的,但这些选项也会影响 [`server.deps.external`](#server-deps-external) 中的文件。 -#### deps.web.transformAssets +#### deps.client.transformAssets - **类型:** `boolean` - **默认值:** `true` @@ -281,7 +298,7 @@ Vitest 是否应该像 Vite 在浏览器中一样处理静态资源(.png、.sv 目前,此选项适用于 [`vmThreads`](#vmthreads) 和 [`vmForks`](#vmForks) 池。 ::: -#### deps.web.transformCss +#### deps.client.transformCss - **类型:** `boolean` - **默认值:** `true` @@ -294,7 +311,7 @@ Vitest 是否应该像 Vite 在浏览器中一样处理静态资源(.css, .scs 目前,此选项仅适用于 [`vmThreads`](#vmthreads) 和 [`vmForks`](#vmForks) 池。 ::: -#### deps.web.transformGlobPattern +#### deps.client.transformGlobPattern - **类型:** `RegExp | RegExp[]` - **默认值:** `[]` @@ -556,7 +573,7 @@ import type { Environment } from 'vitest' export default { name: 'custom', - transformMode: 'ssr', + viteEnvironment: 'ssr', setup() { // custom setup return { @@ -616,7 +633,7 @@ In CI, or when run from a non-interactive shell, "watch" mode is not the default Vitest reruns tests based on the module graph which is populated by static and dynamic `import` statements. However, if you are reading from the file system or fetching from a proxy, then Vitest cannot detect those dependencies. -To correctly rerun those tests, you can define a regex pattern and a function that retuns a list of test files to run. +To correctly rerun those tests, you can define a regex pattern and a function that returns a list of test files to run. ```ts import { defineConfig } from 'vitest/config' @@ -1681,6 +1698,7 @@ test('doNotRun', () => { 将在每次测试前调用 [`vi.unstubAllGlobals`](/api/#vi-unstuballglobals)。 +<<<<<<< HEAD ### testTransformMode {#testtransformmode} - **类型:** `{ web?, ssr? }` @@ -1717,6 +1735,8 @@ export default defineConfig({ }) ``` +======= +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ### snapshotFormat - **类型:** `PrettyFormatOptions` diff --git a/guide/browser/assertion-api.md b/guide/browser/assertion-api.md index 371ed353..1d9cab96 100644 --- a/guide/browser/assertion-api.md +++ b/guide/browser/assertion-api.md @@ -951,7 +951,12 @@ function toHaveSelection(selection?: string): Promise 这在检查元素内是否选择了文本或部分文本时非常有用。该元素可以是文本类型的输入框、`textarea`,或者是任何包含文本的其他元素,例如段落、`span`、`div` 等。 ::: warning +<<<<<<< HEAD 预期的选择是一个字符串,它不允许检查选择范围的索引。 +======= +The expected selection is a string, it does not allow to check for +selection range indices. +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ::: ```html @@ -1000,3 +1005,216 @@ await expect.element(queryByTestId('parent')).toHaveSelection('ected text') await expect.element(queryByTestId('prev')).not.toHaveSelection() await expect.element(queryByTestId('next')).toHaveSelection('ne') ``` + +## toMatchScreenshot experimental + +```ts +function toMatchScreenshot( + options?: ScreenshotMatcherOptions, +): Promise +function toMatchScreenshot( + name?: string, + options?: ScreenshotMatcherOptions, +): Promise +``` + +::: tip +The `toMatchScreenshot` assertion can be configured globally in your +[Vitest config](/guide/browser/config#browser-expect-tomatchscreenshot). +::: + +This assertion allows you to perform visual regression testing by comparing +screenshots of elements or pages against stored reference images. + +When differences are detected beyond the configured threshold, the test fails. +To help identify the changes, the assertion generates: + +- The actual screenshot captured during the test +- The expected reference screenshot +- A diff image highlighting the differences (when possible) + +::: warning Screenshots Stability +The assertion automatically retries taking screenshots until two consecutive +captures yield the same result. This helps reduce flakiness caused by +animations, loading states, or other dynamic content. You can control this +behavior with the `timeout` option. + +However, browser rendering can vary across: + +- Different browsers and browser versions +- Operating systems (Windows, macOS, Linux) +- Screen resolutions and pixel densities +- GPU drivers and hardware acceleration +- Font rendering and system fonts + +It is recommended to read the +[Visual Regression Testing guide](/guide/browser/visual-regression-testing) to +implement this testing strategy efficiently. +::: + +::: tip +When a screenshot comparison fails due to **intentional changes**, you can +update the reference screenshot by pressing the `u` key in watch mode, or by +running tests with the `-u` or `--update` flags. +::: + +```html + +``` + +```ts +// basic usage, auto-generates screenshot name +await expect.element(getByTestId('button')).toMatchScreenshot() + +// with custom name +await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button') + +// with options +await expect.element(getByTestId('button')).toMatchScreenshot({ + comparatorName: 'pixelmatch', + comparatorOptions: { + allowedMismatchedPixelRatio: 0.01, + }, +}) + +// with both name and options +await expect.element(getByTestId('button')).toMatchScreenshot('fancy-button', { + comparatorName: 'pixelmatch', + comparatorOptions: { + allowedMismatchedPixelRatio: 0.01, + }, +}) +``` + +### Options + +- `comparatorName: "pixelmatch" = "pixelmatch"` + + The name of the algorithm/library used for comparing images. + + Currently, [`"pixelmatch"`](https://github.com/mapbox/pixelmatch) is the only + supported comparator. + +- `comparatorOptions: object` + + These options allow changing the behavior of the comparator. What properties + can be set depends on the chosen comparator algorithm. + + Vitest has set default values out of the box, but they can be overridden. + + - [`"pixelmatch"` options](#pixelmatch-comparator-options) + + ::: warning + **Always explicitly set `comparatorName` to get proper type inference for + `comparatorOptions`**. + + Without it, TypeScript won't know which options are valid: + + ```ts + // ❌ TypeScript can't infer the correct options + await expect.element(button).toMatchScreenshot({ + comparatorOptions: { + // might error when new comparators are added + allowedMismatchedPixelRatio: 0.01, + }, + }) + + // ✅ TypeScript knows these are pixelmatch options + await expect.element(button).toMatchScreenshot({ + comparatorName: 'pixelmatch', + comparatorOptions: { + allowedMismatchedPixelRatio: 0.01, + }, + }) + ``` + ::: + +- `screenshotOptions: object` + + The same options allowed by + [`locator.screenshot()`](/guide/browser/locators.html#screenshot), except for: + + - `'base64'` + - `'path'` + - `'save'` + - `'type'` + +- `timeout: number = 5_000` + + Time to wait until a stable screenshot is found. + + Setting this value to `0` disables the timeout, but if a stable screenshot + can't be determined the process will not end. + +#### `"pixelmatch"` comparator options + +The following options are available when using the `"pixelmatch"` comparator: + +- `allowedMismatchedPixelRatio: number | undefined = undefined` + + The maximum allowed ratio of differing pixels between the captured screenshot + and the reference image. + + Must be a value between `0` and `1`. + + For example, `allowedMismatchedPixelRatio: 0.02` means the test will pass + if up to 2% of pixels differ, but fail if more than 2% differ. + +- `allowedMismatchedPixels: number | undefined = undefined` + + The maximum number of pixels that are allowed to differ between the captured + screenshot and the stored reference image. + + If set to `undefined`, any non-zero difference will cause the test to fail. + + For example, `allowedMismatchedPixels: 10` means the test will pass if 10 or + fewer pixels differ, but fail if 11 or more differ. + +- `threshold: number = 0.1` + + Acceptable perceived color difference between the same pixel in two images. + + Value ranges from `0` (strict) to `1` (very lenient). Lower values mean small + differences will be detected. + + The comparison uses the [YIQ color space](https://en.wikipedia.org/wiki/YIQ). + +- `includeAA: boolean = false` + + If `true`, disables detection and ignoring of anti-aliased pixels. + +- `alpha: number = 0.1` + + Blending level of unchanged pixels in the diff image. + + Ranges from `0` (white) to `1` (original brightness). + +- `aaColor: [r: number, g: number, b: number] = [255, 255, 0]` + + Color used for anti-aliased pixels in the diff image. + +- `diffColor: [r: number, g: number, b: number] = [255, 0, 0]` + + Color used for differing pixels in the diff image. + +- `diffColorAlt: [r: number, g: number, b: number] | undefined = undefined` + + Optional alternative color for dark-on-light differences, to help show what's + added vs. removed. + + If not set, `diffColor` is used for all differences. + +- `diffMask: boolean = false` + + If `true`, shows only the diff as a mask on a transparent background, instead + of overlaying it on the original image. + + Anti-aliased pixels won't be shown (if detected). + +::: warning +When both `allowedMismatchedPixels` and `allowedMismatchedPixelRatio` are set, +the more restrictive value is used. + +For example, if you allow 100 pixels or 2% ratio, and your image has 10,000 +pixels, the effective limit would be 100 pixels instead of 200. +::: diff --git a/guide/browser/commands.md b/guide/browser/commands.md index c9df8eb0..e470a591 100644 --- a/guide/browser/commands.md +++ b/guide/browser/commands.md @@ -11,7 +11,11 @@ outline: deep ### 文件处理 +<<<<<<< HEAD 在浏览器端的测试中,你可以通过 `readFile` 、`writeFile` 和 `removeFile` 这些 API 来操作文件。从 Vitest 3.2 版本起,所有文件路径都会相对于 [项目](/guide/projects) 根目录解析(默认为 `process.cwd()`,除非你手动修改过)。在此之前,路径是以测试文件所在位置作为基准进行解析的。 +======= +You can use the `readFile`, `writeFile`, and `removeFile` APIs to handle files in your browser tests. Since Vitest 3.2, all paths are resolved relative to the [project](/guide/projects) root (which is `process.cwd()`, unless overridden manually). Previously, paths were resolved relative to the test file. +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 默认情况下,Vitest 使用 `utf-8` 编码,但你可以使用选项覆盖它。 @@ -162,7 +166,11 @@ Vitest 在上下文对象上公开了一些 `webdriverio` 特有属性。 - `browser` 是 `WebdriverIO.Browser` API. +<<<<<<< HEAD Vitest 通过在调用命令前调用 `browser.switchToFrame` 自动将 `webdriver` 上下文切换到测试 iframe,因此 `$` 和 `$` 方法将引用 iframe 内的元素,而不是 orchestrator 中的元素,但非 Webdriver API 仍将引用 parent frame 上下文。 +======= +Vitest automatically switches the `webdriver` context to the test iframe by calling `browser.switchFrame` before the command is called, so `$` and `$$` methods refer to the elements inside the iframe, not in the orchestrator, but non-webdriver APIs will still refer to the parent frame context. +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ::: tip 如果我们使用的是 TypeScript,请记得在您的 [setup 文件](/config/#setupfile)或 [config 文件](/config/)中引用 `@vitest/browser/providers/webdriverio` ,以便获得自动补全功能。 diff --git a/guide/browser/config.md b/guide/browser/config.md index 0d402377..a260161c 100644 --- a/guide/browser/config.md +++ b/guide/browser/config.md @@ -325,3 +325,152 @@ export interface BrowserScript { ::: info 这是浏览器与 Vitest 服务器建立 WebSocket 连接所需的时间。在正常情况下,此超时不应被触发。 ::: + +## browser.expect + +- **Type:** `ExpectOptions` + +### browser.expect.toMatchScreenshot + +Default options for the +[`toMatchScreenshot` assertion](/guide/browser/assertion-api.html#tomatchscreenshot). +These options will be applied to all screenshot assertions. + +::: tip +Setting global defaults for screenshot assertions helps maintain consistency +across your test suite and reduces repetition in individual tests. You can still +override these defaults at the assertion level when needed for specific test cases. +::: + +```ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + browser: { + enabled: true, + expect: { + toMatchScreenshot: { + comparatorName: 'pixelmatch', + comparatorOptions: { + threshold: 0.2, + allowedMismatchedPixels: 100, + }, + resolveScreenshotPath: ({ arg, browserName, ext, testFileName }) => + `custom-screenshots/${testFileName}/${arg}-${browserName}${ext}`, + }, + }, + }, + }, +}) +``` + +[All options available in the `toMatchScreenshot` assertion](/guide/browser/assertion-api#options) +can be configured here. Additionally, two path resolution functions are +available: `resolveScreenshotPath` and `resolveDiffPath`. + +#### browser.expect.toMatchScreenshot.resolveScreenshotPath + +- **Type:** `(data: PathResolveData) => string` +- **Default output:** `` `${root}/${testFileDirectory}/${screenshotDirectory}/${testFileName}/${arg}-${browserName}-${platform}${ext}` `` + +A function to customize where reference screenshots are stored. The function +receives an object with the following properties: + +- `arg: string` + + Path **without** extension, sanitized and relative to the test file. + + This comes from the arguments passed to `toMatchScreenshot`; if called + without arguments this will be the auto-generated name. + + ```ts + test('calls `onClick`', () => { + expect(locator).toMatchScreenshot() + // arg = "calls-onclick-1" + }) + + expect(locator).toMatchScreenshot('foo/bar/baz.png') + // arg = "foo/bar/baz" + + expect(locator).toMatchScreenshot('../foo/bar/baz.png') + // arg = "foo/bar/baz" + ``` + +- `ext: string` + + Screenshot extension, with leading dot. + + This can be set through the arguments passed to `toMatchScreenshot`, but + the value will fall back to `'.png'` if an unsupported extension is used. + +- `browserName: string` + + The instance's browser name. + +- `platform: NodeJS.Platform` + + The value of + [`process.platform`](https://nodejs.org/docs/v22.16.0/api/process.html#processplatform). + +- `screenshotDirectory: string` + + The value provided to + [`browser.screenshotDirectory`](/guide/browser/config#browser-screenshotdirectory), + if none is provided, its default value. + +- `root: string` + + Absolute path to the project's [`root`](/config/#root). + +- `testFileDirectory: string` + + Path to the test file, relative to the project's [`root`](/config/#root). + +- `testFileName: string` + + The test's filename. + +- `testName: string` + + The [`test`](/api/#test)'s name, including parent + [`describe`](/api/#describe), sanitized. + +- `attachmentsDir: string` + + The value provided to [`attachmentsDir`](/config/#attachmentsdir), if none is + provided, its default value. + +For example, to group screenshots by browser: + +```ts +resolveScreenshotPath: ({ arg, browserName, ext, root, testFileName }) => + `${root}/screenshots/${browserName}/${testFileName}/${arg}${ext}` +``` + +#### browser.expect.toMatchScreenshot.resolveDiffPath + +- **Type:** `(data: PathResolveData) => string` +- **Default output:** `` `${root}/${attachmentsDir}/${testFileDirectory}/${testFileName}/${arg}-${browserName}-${platform}${ext}` `` + +A function to customize where diff images are stored when screenshot comparisons +fail. Receives the same data object as +[`resolveScreenshotPath`](#browser-expect-tomatchscreenshot-resolvescreenshotpath). + +For example, to store diffs in a subdirectory of attachments: + +```ts +resolveDiffPath: ({ arg, attachmentsDir, browserName, ext, root, testFileName }) => + `${root}/${attachmentsDir}/screenshot-diffs/${testFileName}/${arg}-${browserName}${ext}` +``` + +::: tip +To have a better type safety when using built-in providers, you should reference +one of these types (for provider that you are using) in your +[config file](/config/): + +```ts +/// +/// +``` +::: diff --git a/guide/browser/locators.md b/guide/browser/locators.md index c3a1c30c..83677d21 100644 --- a/guide/browser/locators.md +++ b/guide/browser/locators.md @@ -234,7 +234,11 @@ function getByLabelText( 创建一个能够找到具有关联标签的元素的定位器。 +<<<<<<< HEAD `page.getByLabelText('Username')` 定位器将在以下示例中找到所有的输入元素: +======= +The `page.getByLabelText('Username')` locator will find every input in the example below: +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ```html // for/htmlFor relationship between label and form element id @@ -1004,7 +1008,7 @@ declare module '@vitest/browser/context' { } ``` -If the method is called on the global `page` object, then selector will be applied to the whole page. In the example bellow, `getByArticleTitle` will find all elements with an attribute `data-title` with the value of `title`. However, if the method is called on the locator, then it will be scoped to that locator. +If the method is called on the global `page` object, then selector will be applied to the whole page. In the example below, `getByArticleTitle` will find all elements with an attribute `data-title` with the value of `title`. However, if the method is called on the locator, then it will be scoped to that locator. ```html
diff --git a/guide/browser/visual-regression-testing.md b/guide/browser/visual-regression-testing.md new file mode 100644 index 00000000..544ea1de --- /dev/null +++ b/guide/browser/visual-regression-testing.md @@ -0,0 +1,715 @@ +--- +title: Visual Regression Testing +outline: [2, 3] +--- + +# Visual Regression Testing + +Vitest can run visual regression tests out of the box. It captures screenshots +of your UI components and pages, then compares them against reference images to +detect unintended visual changes. + +Unlike functional tests that verify behavior, visual tests catch styling issues, +layout shifts, and rendering problems that might otherwise go unnoticed without +thorough manual testing. + +## Why Visual Regression Testing? + +Visual bugs don’t throw errors, they just look wrong. That’s where visual +testing comes in. + +- That button still submits the form... but why is it hot pink now? +- The text fits perfectly... until someone views it on mobile +- Everything works great... except those two containers are out of viewport +- That careful CSS refactor works... but broke the layout on a page no one tests + +Visual regression testing acts as a safety net for your UI, automatically +catching these visual changes before they reach production. + +## Getting Started + +::: warning Browser Rendering Differences +Visual regression tests are **inherently unstable across different +environments**. Screenshots will look different on different machines because +of: + +- Font rendering (the big one. Windows, macOS, Linux, they all render text +differently) +- GPU drivers and hardware acceleration +- Whether you're running headless or not +- Browser settings and versions +- ...and honestly, sometimes just the phase of the moon + +That's why Vitest includes the browser and platform in screenshot names (like +`button-chromium-darwin.png`). + +For stable tests, use the same environment everywhere. We **strongly recommend** +cloud services like +[Microsoft Playwright Testing](https://azure.microsoft.com/en-us/products/playwright-testing) +or [Docker containers](https://playwright.dev/docs/docker). +::: + +Visual regression testing in Vitest can be done through the +[`toMatchScreenshot` assertion](/guide/browser/assertion-api.html#tomatchscreenshot): + +```ts +import { expect, test } from 'vitest' +import { page } from '@vitest/browser/context' + +test('hero section looks correct', async () => { + // ...the rest of the test + + // capture and compare screenshot + await expect(page.getByTestId('hero')).toMatchScreenshot('hero-section') +}) +``` + +### Creating References + +When you run a visual test for the first time, Vitest creates a reference (also +called baseline) screenshot and fails the test with the following error message: + +``` +expect(element).toMatchScreenshot() + +No existing reference screenshot found; a new one was created. Review it before running tests again. + +Reference screenshot: + tests/__screenshots__/hero.test.ts/hero-section-chromium-darwin.png +``` + +This is normal. Check that the screenshot looks right, then run the test again. +Vitest will now compare future runs against this baseline. + +::: tip +Reference screenshots live in `__screenshots__` folders next to your tests. +**Don't forget to commit them!** +::: + +### Screenshot Organization + +By default, screenshots are organized as: + +``` +. +├── __screenshots__ +│ └── test-file.test.ts +│ ├── test-name-chromium-darwin.png +│ ├── test-name-firefox-linux.png +│ └── test-name-webkit-win32.png +└── test-file.test.ts +``` + +The naming convention includes: +- **Test name**: either the first argument of the `toMatchScreenshot()` call, +or automatically generated from the test's name. +- **Browser name**: `chrome`, `chromium`, `firefox` or `webkit`. +- **Platform**: `aix`, `darwin`, `freebsd`, `linux`, `openbsd`, `sunos`, or +`win32`. + +This ensures screenshots from different environments don't overwrite each other. + +### Updating References + +When you intentionally change your UI, you'll need to update the reference +screenshots: + +```bash +$ vitest --update +``` + +Review updated screenshots before committing to make sure changes are +intentional. + +## Configuring Visual Tests + +### Global Configuration + +Configure visual regression testing defaults in your +[Vitest config](/guide/browser/config#browser-expect-tomatchscreenshot): + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + browser: { + expect: { + toMatchScreenshot: { + comparatorName: 'pixelmatch', + comparatorOptions: { + // 0-1, how different can colors be? + threshold: 0.2, + // 1% of pixels can differ + allowedMismatchedPixelRatio: 0.01, + }, + }, + }, + }, + }, +}) +``` + +### Per-Test Configuration + +Override global settings for specific tests: + +```ts +await expect(element).toMatchScreenshot('button-hover', { + comparatorName: 'pixelmatch', + comparatorOptions: { + // more lax comparison for text-heavy elements + allowedMismatchedPixelRatio: 0.1, + }, +}) +``` + +## Best Practices + +### Test Specific Elements + +Unless you explicitly want to test the whole page, prefer capturing specific +components to reduce false positives: + +```ts +// ❌ Captures entire page; prone to unrelated changes +await expect(page).toMatchScreenshot() + +// ✅ Captures only the component under test +await expect(page.getByTestId('product-card')).toMatchScreenshot() +``` + +### Handle Dynamic Content + +Dynamic content like timestamps, user data, or random values will cause tests +to fail. You can either mock the sources of dynamic content or mask them when +using the Playwright provider by using the +[`mask` option](https://playwright.dev/docs/api/class-page#page-screenshot-option-mask) +in `screenshotOptions`. + +```ts +await expect(page.getByTestId('profile')).toMatchScreenshot({ + screenshotOptions: { + mask: [page.getByTestId('last-seen')], + }, +}) +``` + +### Disable Animations + +Animations can cause flaky tests. Disable them during testing by injecting +a custom CSS snippet: + +```css +*, *::before, *::after { + animation-duration: 0s !important; + animation-delay: 0s !important; + transition-duration: 0s !important; + transition-delay: 0s !important; +} +``` + +::: tip +When using the Playwright provider, animations are automatically disabled +when using the assertion: the `animations` option's value in `screenshotOptions` +is set to `"disabled"` by default. +::: + +### Set Appropriate Thresholds + +Tuning thresholds is tricky. It depends on the content, test environment, +what's acceptable for your app, and might also change based on the test. + +Vitest does not set a default for the mismatching pixels, that's up for the +user to decide based on their needs. The recommendation is to use +`allowedMismatchedPixelRatio`, so that the threshold is computed on the size +of the screenshot and not a fixed number. + +When setting both `allowedMismatchedPixelRatio` and +`allowedMismatchedPixels`, Vitest uses whichever limit is stricter. + +### Set consistent viewport sizes + +As the browser instance might have a different default size, it's best to +set a specific viewport size, either on the test or the instance +configuration: + +```ts +await page.viewport(1280, 720) +``` + +```ts [vitest.config.ts] +export default defineConfig({ + test: { + browser: { + enabled: true, + provider: 'playwright', + instances: [ + { + browser: 'chromium', + viewport: { width: 1280, height: 720 }, + }, + ], + }, + }, +}) +``` + +### Use Git LFS + +Store reference screenshots in +[Git LFS](https://github.com/git-lfs/git-lfs?tab=readme-ov-file) if you plan to +have a large test suite. + +## Debugging Failed Tests + +When a visual test fails, Vitest provides three images to help debug: + +1. **Reference screenshot**: the expected baseline image +1. **Actual screenshot**: what was captured during the test +1. **Diff image**: highlights the differences, but this might not get generated + +You'll see something like: + +``` +expect(element).toMatchScreenshot() + +Screenshot does not match the stored reference. +245 pixels (ratio 0.03) differ. + +Reference screenshot: + tests/__screenshots__/button.test.ts/button-chromium-darwin.png + +Actual screenshot: + tests/.vitest-attachments/button.test.ts/button-chromium-darwin-actual.png + +Diff image: + tests/.vitest-attachments/button.test.ts/button-chromium-darwin-diff.png +``` + +### Understanding the diff image + +- **Red pixels** are areas that differ between reference and actual +- **Yellow pixels** are anti-aliasing differences (when anti-alias is not ignored) +- **Transparent/original** are unchanged areas + +:::tip +If the diff is mostly red, something's really wrong. If it's speckled with a +few red pixels around text, you probably just need to bump your threshold. +::: + +## Common Issues and Solutions + +### False Positives from Font Rendering + +Font availability and rendering varies significantly between systems. Some +possible solutions might be to: + +- Use web fonts and wait for them to load: + + ```ts + // wait for fonts to load + await document.fonts.ready + + // continue with your tests + ``` + +- Increase comparison threshold for text-heavy areas: + + ```ts + await expect(page.getByTestId('article-summary')).toMatchScreenshot({ + comparatorName: 'pixelmatch', + comparatorOptions: { + // 10% of the pixels are allowed to change + allowedMismatchedPixelRatio: 0.1, + }, + }) + ``` + +- Use a cloud service or containerized environment for consistent font rendering. + +### Flaky Tests or Different Screenshot Sizes + +If tests pass and fail randomly, or if screenshots have different dimensions +between runs: + +- Wait for everything to load, including loading indicators +- Set explicit viewport sizes: `await page.viewport(1920, 1080)` +- Check for responsive behavior at viewport boundaries +- Check for unintended animations or transitions +- Increase test timeout for large screenshots +- Use a cloud service or containerized environment + +## Visual Regression Testing for Teams + +Remember when we mentioned visual tests need a stable environment? Well, here's +the thing: your local machine isn't it. + +For teams, you've basically got three options: + +1. **Self-hosted runners**, complex to set up, painful to maintain +1. **GitHub Actions**, free (for open source), works with any provider +1. **Cloud services**, like +[Microsoft Playwright Testing](https://azure.microsoft.com/en-us/products/playwright-testing), +built for this exact problem + +We'll focus on options 2 and 3 since they're the quickest to get running. + +To be upfront, the main trade-offs for each are: + +- **GitHub Actions**: visual tests only run in CI (developers can't run them +locally) +- **Microsoft's service**: works everywhere but costs money and only works +with Playwright + +:::: tabs key:vrt-for-teams +=== GitHub Actions + +The trick here is keeping visual tests separate from your regular tests, +otherwise, you'll waste hours checking failing logs of screenshot mismatches. + +#### Organizing Your Tests + +First, isolate your visual tests. Stick them in a `visual` folder (or whatever +makes sense for your project): + +```json [package.json] +{ + "scripts": { + "test:unit": "vitest --exclude tests/visual/*.test.ts", + "test:visual": "vitest tests/visual/*.test.ts" + } +} +``` + +Now developers can run `npm run test:unit` locally without visual tests getting +in the way. Visual tests stay in CI where the environment is consistent. + +::: tip Alternative +Not a fan of glob patterns? You could also use separate +[Test Projects](/guide/projects) instead and run them using: + +- `vitest --project unit` +- `vitest --project visual` +::: + +#### CI Setup + +Your CI needs browsers installed. How you do this depends on your provider: + +::: tabs key:provider +== Playwright + +[Playwright](https://npmjs.com/package/playwright) makes this easy. Just pin +your version and add this before running tests: + +```yaml [.github/workflows/ci.yml] +# ...the rest of the workflow +- name: Install Playwright Browsers + run: npx --no playwright install --with-deps --only-shell +``` + +== WebdriverIO + +[WebdriverIO](https://www.npmjs.com/package/webdriverio) expects you to bring +your own browsers. The folks at +[@browser-actions](https://github.com/browser-actions) have your back: + +```yaml [.github/workflows/ci.yml] +# ...the rest of the workflow +- uses: browser-actions/setup-chrome@v1 + with: + chrome-version: 120 +``` + +::: + +Then run your visual tests: + +```yaml [.github/workflows/ci.yml] +# ...the rest of the workflow +# ...browser setup +- name: Visual Regression Testing + run: npm run test:visual +``` + +#### The Update Workflow + +Here's where it gets interesting. You don't want to update screenshots on every +PR automatically *(chaos!)*. Instead, create a +manually-triggered workflow that developers can run when they intentionally +change the UI. + +The workflow below: +- Only runs on feature branches (never on main) +- Credits the person who triggered it as co-author +- Prevents concurrent runs on the same branch +- Shows a nice summary: + - **When screenshots changed**, it lists what changed + + Action summary after updates + Action summary after updates + + - **When nothing changed**, well, it tells you that too + + Action summary after no updates + Action summary after no updates + +::: tip +This is just one approach. Some teams prefer PR comments (`/update-screenshots`), +others use labels. Adjust it to fit your workflow! + +The important part is having a controlled way to update baselines. +::: + +```yaml [.github/workflows/update-screenshots.yml] +name: Update Visual Regression Screenshots + +on: + workflow_dispatch: # manual trigger only + +env: + AUTHOR_NAME: 'github-actions[bot]' + AUTHOR_EMAIL: '41898282+github-actions[bot]@users.noreply.github.com' + COMMIT_MESSAGE: | + test: update visual regression screenshots + + Co-authored-by: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> + +jobs: + update-screenshots: + runs-on: ubuntu-24.04 + + # safety first: don't run on main + if: github.ref_name != github.event.repository.default_branch + + # one at a time per branch + concurrency: + group: visual-regression-screenshots@${{ github.ref_name }} + cancel-in-progress: true + + permissions: + contents: write # needs to push changes + + steps: + - name: Checkout selected branch + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + # use PAT if triggering other workflows + # token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config --global user.name "${{ env.AUTHOR_NAME }}" + git config --global user.email "${{ env.AUTHOR_EMAIL }}" + + # your setup steps here (node, pnpm, whatever) + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Install dependencies + run: npm ci + + - name: Install Playwright Browsers + run: npx --no playwright install --with-deps --only-shell + + # the magic happens below 🪄 + - name: Update Visual Regression Screenshots + run: npm run test:visual --update + + # check what changed + - name: Check for changes + id: check_changes + run: | + CHANGED_FILES=$(git status --porcelain | awk '{print $2}') + if [ "${CHANGED_FILES:+x}" ]; then + echo "changes=true" >> $GITHUB_OUTPUT + echo "Changes detected" + + # save the list for the summary + echo "changed_files<> $GITHUB_OUTPUT + echo "$CHANGED_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "changed_count=$(echo "$CHANGED_FILES" | wc -l)" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + echo "No changes detected" + fi + + # commit if there are changes + - name: Commit changes + if: steps.check_changes.outputs.changes == 'true' + run: | + git add -A + git commit -m "${{ env.COMMIT_MESSAGE }}" + + - name: Push changes + if: steps.check_changes.outputs.changes == 'true' + run: git push origin ${{ github.ref_name }} + + # pretty summary for humans + - name: Summary + run: | + if [[ "${{ steps.check_changes.outputs.changes }}" == "true" ]]; then + echo "### 📸 Visual Regression Screenshots Updated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Successfully updated **${{ steps.check_changes.outputs.changed_count }}** screenshot(s) on \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "#### Changed Files:" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "${{ steps.check_changes.outputs.changed_files }}" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ The updated screenshots have been committed and pushed. Your visual regression baseline is now up to date!" >> $GITHUB_STEP_SUMMARY + else + echo "### ℹ️ No Screenshot Updates Required" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "The visual regression test command ran successfully but no screenshots needed updating." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All screenshots are already up to date! 🎉" >> $GITHUB_STEP_SUMMARY + fi +``` + +=== Microsoft Playwright Testing + +Your tests stay local, only the browsers run in the cloud. It's Playwright's +remote browser feature, but Microsoft handles all the infrastructure. + +#### Organizing Your Tests + +Keep visual tests separate to control costs. Only tests that actually take +screenshots should use the service. + +The cleanest approach is using [Test Projects](/guide/projects): + +```ts [vitest.config.ts] +import { env } from 'node:process' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + // ...global Vite config + tests: { + // ...global Vitest config + projects: [ + { + extends: true, + test: { + name: 'unit', + include: ['tests/**/*.test.ts'], + // regular config, can use local browsers + }, + }, + { + extends: true, + test: { + name: 'visual', + // or you could use a different suffix, e.g.,: `tests/**/*.visual.ts?(x)` + include: ['visual-regression-tests/**/*.test.ts?(x)'], + browser: { + enabled: true, + provider: 'playwright', + headless: true, + instances: [ + { + browser: 'chromium', + viewport: { width: 2560, height: 1440 }, + connect: { + wsEndpoint: `${env.PLAYWRIGHT_SERVICE_URL}?cap=${JSON.stringify({ + os: 'linux', // always use Linux for consistency + // helps identifying runs in the service's dashboard + runId: `Vitest ${env.CI ? 'CI' : 'local'} run @${new Date().toISOString()}`, + })}`, + options: { + exposeNetwork: '', + headers: { + 'x-mpt-access-key': env.PLAYWRIGHT_SERVICE_ACCESS_TOKEN, + }, + timeout: 30_000, + }, + }, + }, + ], + }, + }, + }, + ], + }, +}) +``` + +The service gives you two environment variables: + +- `PLAYWRIGHT_SERVICE_URL` tells Playwright where to connect +- `PLAYWRIGHT_SERVICE_ACCESS_TOKEN` is your auth token + +::: danger Keep that Token Secret! +Never commit `PLAYWRIGHT_SERVICE_ACCESS_TOKEN` to your repository. Anyone with +the token can rack up your bill. Use environment variables locally and secrets +in CI. +::: + +Then split your `test` script like this: + +```json [package.json] +{ + "scripts": { + "test:visual": "vitest --project visual", + "test:unit": "vitest --project unit" + } +} +``` + +#### Running Tests + +```bash +# Local development +npm run test:unit # free, runs locally +npm run test:visual # uses cloud browsers + +# Update screenshots +npm run test:visual -- --update +``` + +The best part of this approach is that it just works: + +- **Consistent screenshots**, everyone uses the same cloud browsers +- **Works locally**, developers can run and update visual tests on their machines +- **Pay for what you use**, only visual tests consume service minutes +- **No Docker or workflow setups needed**, nothing to manage or maintain + +#### CI Setup + +In your CI, add the secrets: + +```yaml +env: + PLAYWRIGHT_SERVICE_URL: ${{ vars.PLAYWRIGHT_SERVICE_URL }} + PLAYWRIGHT_SERVICE_ACCESS_TOKEN: ${{ secrets.PLAYWRIGHT_SERVICE_ACCESS_TOKEN }} +``` + +Then run your tests like normal. The service handles the rest. + +:::: + +### So Which One? + +Both approaches work. The real question is what pain points matter most to your +team. + +If you're already deep in the GitHub ecosystem, GitHub Actions is hard to beat. +Free for open source, works with any browser provider, and you control +everything. + +The downside? That "works on my machine" conversation when someone generates +screenshots locally and they don't match CI expectations anymore. + +The cloud service makes sense if developers need to run visual tests locally. + +Some teams have designers checking their work or developers who prefer catching +issues before pushing. It allows skipping the push-wait-check-fix-push cycle. + +Still on the fence? Start with GitHub Actions. You can always add the cloud +service later if local testing becomes a pain point. diff --git a/guide/environment.md b/guide/environment.md index 3f2e0674..1258a870 100644 --- a/guide/environment.md +++ b/guide/environment.md @@ -48,7 +48,7 @@ import type { Environment } from 'vitest/environments' export default { name: 'custom', - transformMode: 'ssr', + viteEnvironment: 'ssr', // optional - only if you support "experimental-vm" pool async setupVM() { const vm = await import('node:vm') @@ -74,7 +74,11 @@ export default { ``` ::: warning +<<<<<<< HEAD Vitest 需要指定环境对象上的 `transformMode` 选项。它应该等于 `ssr` 或 `web`。该值决定插件如何转换源代码。如果设置为 `ssr`,则插件挂钩在转换或解析文件时将收到 `ssr: true`。 否则,`ssr` 被设置为 `false`。 +======= +Vitest requires `viteEnvironment` option on environment object (fallbacks to the Vitest environment name by default). It should be equal to `ssr`, `client` or any custom [Vite environment](https://vite.dev/guide/api-environment) name. This value determines which environment is used to process file. +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ::: 你还可以通过 `vitest/environments` 访问默认的 Vitest 环境: diff --git a/guide/migration.md b/guide/migration.md index 97059be0..025c56b6 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -138,15 +138,43 @@ $ pnpm run test:dev math.test.ts ``` ::: +<<<<<<< HEAD ### 移除废弃的 API +======= +### Replacing `vite-node` with [Module Runner](https://vite.dev/guide/api-environment-runtimes.html#modulerunner) + +Module Runner is a successor to `vite-node` implemented directly in Vite. Vitest now uses it directly instead of having a wrapper around Vite SSR handler. This means that certain features are no longer available: + +- `VITE_NODE_DEPS_MODULE_DIRECTORIES` environment variable was replaced with `VITEST_MODULE_DIRECTORIES` +- Vitest no longer injects `__vitest_executor` into every [test runner](/advanced/runner). Instead, it injects `moduleRunner` which is an instance of [`ModuleRunner`](https://vite.dev/guide/api-environment-runtimes.html#modulerunner) +- `vitest/execute` entry point was removed. It was always meant to be internal +- [Custom environments](/guide/environment) no longer need to provide a `transformMode` property. Instead, provide `viteEnvironment`. If it is not provided, Vitest will use the environment name to transform files on the server (see [`server.environments`](https://vite.dev/guide/api-environment-instances.html)) +- `vite-node` is no longer a dependency of Vitest +- `deps.optimizer.web` was renamed to [`deps.optimizer.client`](/config/#deps-optimizer-client). You can also use any custom names to apply optimizer configs when using other server environments + +Vite has its own externalization mechanism, but we decided to keep using the old one to reduce the amount of breaking changes. You can keep using [`server.deps`](/config/#server-deps) to inline or externalize packages. + +This update should not be noticeable unless you rely on advanced features mentioned above. + +### Deprecated APIs are Removed +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 Vitest 4.0 移除了以下废弃的配置项: +<<<<<<< HEAD - `poolMatchGlobs` 配置项,请使用 [`projects`](/guide/projects) 代替。 - `environmentMatchGlobs` 配置项,请使用 [`projects`](/guide/projects) 代替。 - `workspace` 配置项,请使用 [`projects`](/guide/projects) 代替。 此次发布还移除了所有废弃类型,这也解决了 Vitest 错误引入 `node` 类型的问题(详见 [#5481](https://github.com/vitest-dev/vitest/issues/5481) 和 [#6141](https://github.com/vitest-dev/vitest/issues/6141))。 +======= +- `poolMatchGlobs` config option. Use [`projects`](/guide/projects) instead. +- `environmentMatchGlobs` config option. Use [`projects`](/guide/projects) instead. +- `workspace` config option. Use [`projects`](/guide/projects) instead. +- `deps.external`, `deps.inline`, `deps.fallbackCJS` config options. Use `server.deps.external`, `server.deps.inline`, or `server.deps.fallbackCJS` instead. + +This release also removes all deprecated types. This finally fixes an issue where Vitest accidentally pulled in `@types/node` (see [#5481](https://github.com/vitest-dev/vitest/issues/5481) and [#6141](https://github.com/vitest-dev/vitest/issues/6141)). +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 ## 从 Jest 迁移 {#jest} diff --git a/guide/test-context.md b/guide/test-context.md index 6fa7d2e5..7110be0f 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -325,7 +325,7 @@ describe('use scoped values', () => { test.scoped({ dependency: 'new' }) test('uses scoped value', ({ dependant }) => { - // `dependant` uses the new overriden value that is scoped + // `dependant` uses the new overridden value that is scoped // to all tests in this suite expect(dependant).toEqual({ dependency: 'new' }) }) diff --git a/package.json b/package.json index 0e60310a..3203f32b 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,16 @@ "unocss": "latest", "unplugin-vue-components": "latest", "vite": "^6.3.5", +<<<<<<< HEAD "vite-plugin-pwa": "^1.0.0", "vitepress": "2.0.0-alpha.6", "vitepress-plugin-group-icons": "^1.6.0", +======= + "vite-plugin-pwa": "^0.21.2", + "vitepress": "2.0.0-alpha.9", + "vitepress-plugin-group-icons": "^1.6.1", + "vitepress-plugin-llms": "^1.7.2", +>>>>>>> 666ac87ab1a915e59288a0db12a0c6cb76f55843 "vitepress-plugin-tabs": "^0.7.1", "vitest": "^4.0.0-beta.2", "workbox-window": "^7.3.0" diff --git a/public/mailmeteor.svg b/public/mailmeteor.svg new file mode 100644 index 00000000..99d4aacc --- /dev/null +++ b/public/mailmeteor.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/vrt-gha-summary-no-update-dark.png b/public/vrt-gha-summary-no-update-dark.png new file mode 100644 index 00000000..78ad5ecd Binary files /dev/null and b/public/vrt-gha-summary-no-update-dark.png differ diff --git a/public/vrt-gha-summary-no-update-light.png b/public/vrt-gha-summary-no-update-light.png new file mode 100644 index 00000000..77de12a0 Binary files /dev/null and b/public/vrt-gha-summary-no-update-light.png differ diff --git a/public/vrt-gha-summary-update-dark.png b/public/vrt-gha-summary-update-dark.png new file mode 100644 index 00000000..6d8d498a Binary files /dev/null and b/public/vrt-gha-summary-update-dark.png differ diff --git a/public/vrt-gha-summary-update-light.png b/public/vrt-gha-summary-update-light.png new file mode 100644 index 00000000..e9381f79 Binary files /dev/null and b/public/vrt-gha-summary-update-light.png differ