···522522```
523523:::
524524525525+Set `singleFile` to generate a self-contained HTML report:
526526+527527+```ts [vitest.config.ts]
528528+export default defineConfig({
529529+ test: {
530530+ reporters: [
531531+ ['html', { singleFile: true }],
532532+ ],
533533+ },
534534+})
535535+```
536536+537537+When `singleFile` is enabled, Vitest inlines the UI assets, metadata, and test attachments into a single self-contained `index.html`. This makes the report easy to share, upload, or download as one artifact instead of preserving the whole `html` output directory.
538538+539539+::: warning
540540+`singleFile` has two caveats:
541541+542542+- The file can grow very large because everything is embedded inline — slow to open, memory-hungry, and possibly over the size limits of artifact viewers or static hosts.
543543+- Coverage HTML reports are not inlined yet and remain as separate files.
544544+545545+Prefer the default multi-file report when the suite has many or large attachments, or when you need coverage included in the bundle.
546546+:::
547547+525548::: tip
526549This reporter requires installed [`@vitest/ui`](/guide/ui) package.
527550:::
+12-1
docs/guide/ui.md
···5353You can configure output with [`outputFile`](/config/outputfile) config option. You need to specify `.html` path there. For example, `./html/index.html` is the default value.
5454:::
55555656+If you need a portable report that can be opened or shared as one file, see [`singleFile`](/guide/reporters#html-reporter) in the HTML reporter documentation.
5757+5658::: tip
5759To view the HTML report from CI, for example in GitHub Actions, upload the output directory as an artifact:
58605961```yaml
6060-- uses: actions/upload-artifact@v4
6262+- uses: actions/upload-artifact@v7
6163 id: upload-report
6264 with:
6365 name: vitest-report
···6870```
69717072This adds a link to the job summary. Click it to open the report in [Vitest Viewer](https://viewer.vitest.dev/) directly in the browser. You can also download the artifact manually and extract it, then run `vite preview` locally as above.
7373+7474+When you use `singleFile: true`, you can upload the report as a single file and it will become viewable directly GitHub artifacts with `archive: false` option:
7575+7676+```yaml
7777+- uses: actions/upload-artifact@v7
7878+ with:
7979+ path: html/index.html
8080+ archive: false
8181+```
7182:::
72837384## Module Graph
···7878export interface TestAttachment {
7979 /** MIME type of the attachment (e.g., 'image/png', 'text/plain') */
8080 contentType?: string
8181- /** File system path to the attachment */
8181+ /** Local file path or external HTTP(S) URL to the attachment. Relative paths are resolved from the project root. */
8282 path?: string
8383 /** Inline attachment content as a string or raw binary data */
8484 body?: string | Uint8Array
···94949595The `TestAttachment` interface represents a file or data attachment associated with a test artifact.
96969797-Attachments can be either file-based (via `path`) or inline content (via `body`). The `contentType` helps consumers understand how to interpret the attachment data.
9797+Attachments can be either path-based (via `path`) or inline content (via `body`). The `contentType` helps consumers understand how to interpret the attachment data.
9898+9999+Attachment `path` can point to a local file or an external `http`/`https` URL. Relative local paths are resolved from the project root. Local files are copied into Vitest's attachments directory before reporters receive them. External URLs are preserved as-is.
9810099101If you pass a string `body`, Vitest assumes it is already base64-encoded unless you set `bodyEncoding: 'utf-8'`. When you pass `body` as a `Uint8Array`, Vitest automatically encodes it as base64. The `bodyEncoding` option only applies to inline `body` attachments, not `path` attachments.
100102
···12631263/**
12641264 * Represents a file or data attachment associated with a test artifact.
12651265 *
12661266- * Attachments can be either file-based (via `path`) or inline content (via `body`).
12661266+ * Attachments can be either path-based (via `path`) or inline content (via `body`).
12671267 * The `contentType` helps consumers understand how to interpret the attachment data.
12681268 */
12691269export interface TestAttachment {
12701270 /** MIME type of the attachment (e.g., 'image/png', 'text/plain') */
12711271 contentType?: string
12721272- /** File system path to the attachment */
12721272+ /** Local file path or external HTTP(S) URL to the attachment. Relative paths are resolved from the project root. */
12731273 path?: string
12741274 /** Inline attachment content as a string or raw binary data */
12751275 body?: string | Uint8Array | undefined
···7676 ctx.state.idMap = reactive(ctx.state.idMap)
77777878 async function registerMetadata() {
7979- const res = await fetch(window.METADATA_PATH!)
8080- const content = new Uint8Array(await res.arrayBuffer())
7979+ const content = await window.HTML_REPORT_METADATA!
8180 let metadata: HTMLReportMetadata
8281 // Check for gzip magic numbers (0x1f 0x8b) to determine if content is compressed.
8382 // This handles cases where a static server incorrectly sets Content-Encoding: gzip
+11
packages/vitest/src/node/reporters/html.ts
···11export interface HTMLOptions {
22+ /**
33+ * Path to the generated HTML report.
44+ *
55+ * @default 'html/index.html'
66+ */
27 outputFile?: string
88+ /**
99+ * Inline report assets, metadata, and attachments into the generated HTML file.
1010+ *
1111+ * @default false
1212+ */
1313+ singleFile?: boolean
314}