Skip to content

Commit 29c364d

Browse files
authored
feat(ui)!: change html reporter default output to .vitest (#10620)
Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com>
1 parent 4c26d76 commit 29c364d

28 files changed

Lines changed: 91 additions & 101 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,34 +323,34 @@ jobs:
323323
run: |
324324
set +e
325325
pnpm --filter=./packages/ui --no-bail test:ui --merge-reports
326-
cp ./packages/ui/.vitest/html/index.html ./packages/ui/.vitest/html/test-ui.html
326+
cp ./packages/ui/.vitest/index.html ./packages/ui/.vitest/test-ui.html
327327
pnpm --filter=./test/unit --filter=./test/e2e --no-bail --sequential test --merge-reports
328-
cp -rf ./test/unit/.vitest/html/index.html ./test/unit/.vitest/html/test-unit.html
329-
cp -rf ./test/e2e/.vitest/html/index.html ./test/e2e/.vitest/html/test-e2e.html
328+
cp -rf ./test/unit/.vitest/index.html ./test/unit/.vitest/test-unit.html
329+
cp -rf ./test/e2e/.vitest/index.html ./test/e2e/.vitest/test-e2e.html
330330
env:
331331
VITEST_CI_MERGE_REPORTS: 'true'
332332

333333
- name: Upload Test UI Report
334334
id: upload-ui-report
335335
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
336336
with:
337-
path: ./packages/ui/.vitest/html/test-ui.html
337+
path: ./packages/ui/.vitest/test-ui.html
338338
archive: false
339339
retention-days: 7
340340

341341
- name: Upload Test Unit Report
342342
id: upload-unit-report
343343
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
344344
with:
345-
path: ./test/unit/.vitest/html/test-unit.html
345+
path: ./test/unit/.vitest/test-unit.html
346346
archive: false
347347
retention-days: 7
348348

349349
- name: Upload Test E2E Report
350350
id: upload-e2e-report
351351
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
352352
with:
353-
path: ./test/e2e/.vitest/html/test-e2e.html
353+
path: ./test/e2e/.vitest/test-e2e.html
354354
archive: false
355355
retention-days: 7
356356

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ test/browser/**/__benchmarks__
3030
test/browser/fixtures/update-snapshot/basic.test.ts
3131
test/e2e/fixtures/browser-multiple/basic-*
3232
*.tsbuildinfo
33-
# exclude static html reporter folder
34-
test/browser/html/
35-
test/unit/html/
3633
.vitest
3734
explainFiles.txt
3835
.vitest-dump

docs/config/outputfile.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ outline: deep
88
- **Type:** `string | Record<string, string>`
99
- **CLI:** `--outputFile=<path>`, `--outputFile.json=./path`
1010

11-
Write test results to a file when the `--reporter=json`, `--reporter=html` or `--reporter=junit` option is also specified.
11+
Write test results to a file when the `--reporter=json` or `--reporter=junit` option is also specified.
1212
By providing an object instead of a string you can define individual outputs when using multiple reporters.

docs/guide/migration.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ Vitest no longer serves the browser orchestrator UI from a bare `/__vitest_test_
174174

175175
If you manually opened the browser preview by copying the Vite server URL or visiting `/__vitest_test__/` directly, use the URL opened or printed by Vitest instead.
176176

177+
### Generated Reports and Artifacts Use the `.vitest` Directory
178+
179+
Vitest now uses a single `.vitest` directory at the project root as the shared artifact root, so one `.vitest` entry in `.gitignore` is enough. Defaults that moved this major:
180+
181+
- **Attachments** ([`attachmentsDir`](/config/attachmentsdir)): `.vitest-attachements/``.vitest/attachments/`
182+
- **Blob reporter** and `--merge-reports`: `.vitest-reports/blob-*.json``.vitest/blob/blob-*.json`
183+
- **HTML reporter** ([`html`](/guide/reporters#html-reporter)): `html/index.html``.vitest/index.html`, and its option changed from `outputFile` (a file) to `outputDir` (a directory)
184+
177185
## Migrating to Vitest 4.0 {#vitest-4}
178186

179187
::: warning Prerequisites

docs/guide/reporters.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export default defineConfig({
6060

6161
## Reporter Output
6262

63-
By default, Vitest's reporters will print their output to the terminal. When using the `json`, `html` or `junit` reporters, you can instead write your tests' output to a file by including an `outputFile` [configuration option](/config/outputfile) either in your Vite configuration file or via CLI.
63+
By default, Vitest's reporters will print their output to the terminal. When using the `json` or `junit` reporters, you can instead write your tests' output to a file by including an `outputFile` [configuration option](/config/outputfile) either in your Vite configuration file or via CLI. The `html` reporter writes a report directory instead; see its [`outputDir`](#html-reporter) option.
6464

6565
:::code-group
6666
```bash [CLI]
@@ -506,7 +506,7 @@ export default defineConfig({
506506

507507
Generates an HTML file to view test results through an interactive [GUI](/guide/ui). After the file has been generated, Vitest will keep a local development server running and provide a link to view the report in a browser.
508508

509-
Output file can be specified using the [`outputFile`](/config/outputfile) configuration option. If no `outputFile` option is provided, a new HTML file will be created.
509+
The report artifact root can be specified using the reporter's `outputDir` option. The report entry is written to `<outputDir>/index.html` and the UI assets files live under `<outputDir>/ui/`. By default `outputDir` is `.vitest`, the shared Vitest artifact directory, so attachments (`.vitest/attachments`) and coverage (`.vitest/coverage`) are reused without being copied.
510510

511511
:::code-group
512512
```bash [CLI]

docs/guide/ui.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ If you still want to see how your tests are running in real time in the terminal
5151
To preview your HTML report, you can use the [vite preview](https://vitejs.dev/guide/cli.html#vite-preview) command:
5252

5353
```sh
54-
npx vite preview --outDir ./html
54+
npx vite preview --outDir .vitest
5555
```
5656

57-
You 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.
57+
You can configure the output location with the HTML reporter's `outputDir` option. It points to the report artifact root, and the report entry is written to `<outputDir>/index.html`. The default value is `.vitest`, the shared Vitest artifact directory.
5858
:::
5959

6060
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.
@@ -67,7 +67,7 @@ To view the HTML report from CI, for example in GitHub Actions, upload the outpu
6767
id: upload-report
6868
with:
6969
name: vitest-report
70-
path: html/
70+
path: .vitest/
7171

7272
- name: Viewer link in summary
7373
run: echo "[View HTML report](https://viewer.vitest.dev/?url=${{ steps.upload-report.outputs.artifact-url }})" >> $GITHUB_STEP_SUMMARY
@@ -80,7 +80,7 @@ When you use `singleFile: true`, you can upload the report as a single file and
8080
```yaml
8181
- uses: actions/upload-artifact@v7
8282
with:
83-
path: html/index.html
83+
path: .vitest/index.html
8484
archive: false
8585
```
8686
:::

examples/lit/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
__traces__
22
__screenshots__
3-
html

packages/ui/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
__screenshots__
2-
html/

packages/ui/client/composables/attachments.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ export function getAttachmentUrl(attachment: TestAttachment): string {
77
const contentType = attachment.contentType ?? 'application/octet-stream'
88
if (attachment.path) {
99
if (isReport) {
10-
// html reporter copies attachments to /data/ folder
11-
return `./data/${basename(attachment.path)}`
10+
return `./attachments/${basename(attachment.path)}`
1211
}
1312
return `/__vitest_attachment__?path=${encodeURIComponent(attachment.path)}&contentType=${contentType}&token=${(window as any).VITEST_API_TOKEN}`
1413
}

packages/ui/node/reporter.ts

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { SerializedError, TestAttachment } from 'vitest'
2-
import type { HTMLOptions, Reporter, ResolvedConfig, RunnerTask, RunnerTestFile, TestModule, Vitest } from 'vitest/node'
2+
import type { HTMLOptions, Reporter, RunnerTask, RunnerTestFile, TestModule, Vitest } from 'vitest/node'
33
import type { HTMLReportMetadata } from '../client/composables/client/static'
44
import { existsSync, promises as fs, readFileSync } from 'node:fs'
55
import { promisify } from 'node:util'
@@ -12,18 +12,6 @@ import { distClientRoot } from './paths'
1212

1313
const gzipAsync = promisify(gzip)
1414

15-
function getOutputFile(config: ResolvedConfig) {
16-
if (!config.outputFile) {
17-
return
18-
}
19-
20-
if (typeof config.outputFile === 'string') {
21-
return config.outputFile
22-
}
23-
24-
return config.outputFile.html
25-
}
26-
2715
export default class HTMLReporter implements Reporter {
2816
ctx!: Vitest
2917
options: HTMLOptions
@@ -36,12 +24,10 @@ export default class HTMLReporter implements Reporter {
3624

3725
async onInit(ctx: Vitest): Promise<void> {
3826
this.ctx = ctx
39-
const htmlFile
40-
= this.options.outputFile
41-
|| getOutputFile(this.ctx.config)
42-
|| 'html/index.html'
43-
const htmlFilePath = resolve(this.ctx.config.root, htmlFile)
44-
this.reporterDir = dirname(htmlFilePath)
27+
this.reporterDir = resolve(
28+
this.ctx.config.root,
29+
this.options.outputDir || '.vitest',
30+
)
4531
}
4632

4733
async onTestRunEnd(
@@ -57,28 +43,46 @@ export default class HTMLReporter implements Reporter {
5743
await inlineAttachments(result.files)
5844
}
5945

60-
// copy ui assets
61-
await fs.cp(distClientRoot, this.reporterDir, { recursive: true })
62-
63-
// create index.html and metadata
6446
const rawData = stringify(result)
6547
const data = await gzipAsync(rawData, {
6648
level: zlibConstants.Z_BEST_COMPRESSION,
6749
})
68-
await handleIndexHtml({
69-
srcDir: distClientRoot,
70-
dstDir: this.reporterDir,
71-
data,
72-
singleFile: this.options.singleFile,
73-
})
7450

75-
// copy attachments
76-
// TODO: unify attachmentsDir and html outputFile, so both live together without extra copy
77-
if (!this.options.singleFile && existsSync(this.ctx.config.attachmentsDir)) {
78-
const destAttachmentsDir = resolve(this.reporterDir, 'data')
79-
await fs.rm(destAttachmentsDir, { recursive: true, force: true })
80-
await fs.mkdir(destAttachmentsDir, { recursive: true })
81-
await fs.cp(this.ctx.config.attachmentsDir, destAttachmentsDir, { recursive: true })
51+
await fs.mkdir(this.reporterDir, { recursive: true })
52+
53+
if (this.options.singleFile) {
54+
// write a single self-contained `<outputDir>/index.html`
55+
await handleIndexHtml({
56+
srcDir: distClientRoot,
57+
dstDir: this.reporterDir,
58+
data,
59+
singleFile: true,
60+
})
61+
}
62+
else {
63+
// copy ui assets into `<outputDir>/ui`
64+
const uiDir = resolve(this.reporterDir, 'ui')
65+
await fs.rm(uiDir, { recursive: true, force: true })
66+
await fs.cp(distClientRoot, uiDir, { recursive: true })
67+
// no need of ui/index.html
68+
await fs.rm(resolve(uiDir, 'index.html'), { force: true })
69+
// create `<outputDir>/index.html` and `<outputDir>/ui/html.meta.json.gz`
70+
await handleIndexHtml({
71+
srcDir: distClientRoot,
72+
dstDir: this.reporterDir,
73+
data,
74+
singleFile: false,
75+
})
76+
77+
// copy attachments into `<outputDir>/attachments` if needed.
78+
// the default location matches so no extra copy.
79+
const attachmentsDir = this.ctx.config.attachmentsDir
80+
const destAttachmentsDir = resolve(this.reporterDir, 'attachments')
81+
if (existsSync(attachmentsDir) && attachmentsDir !== destAttachmentsDir) {
82+
await fs.rm(destAttachmentsDir, { recursive: true, force: true })
83+
await fs.mkdir(destAttachmentsDir, { recursive: true })
84+
await fs.cp(attachmentsDir, destAttachmentsDir, { recursive: true })
85+
}
8286
}
8387

8488
this.ctx.logger.log(
@@ -99,8 +103,8 @@ export default class HTMLReporter implements Reporter {
99103
const destCoverageDir = resolve(this.reporterDir, 'coverage')
100104
if (coverageHtmlDir === destCoverageDir) {
101105
// skip and preserve already generated coverage report.
102-
// this can happen when users configures `outputFile`
103-
// next to `coverage.reportsDirectory`.
106+
// this can happen when the report `outputDir` resolves next to
107+
// `coverage.reportsDirectory` (e.g. default `.vitest/coverage`).
104108
return
105109
}
106110
await fs.rm(destCoverageDir, { recursive: true, force: true })
@@ -204,8 +208,10 @@ async function handleIndexHtml(options: {
204208
}
205209
else {
206210
const dataFile = 'html.meta.json.gz'
207-
await fs.writeFile(resolve(options.dstDir, dataFile), options.data)
208-
metadataCode = `fetch(new URL("./${dataFile}", window.location.href)).then(async res => new Uint8Array(await res.arrayBuffer()))`
211+
await fs.writeFile(resolve(options.dstDir, 'ui', dataFile), options.data)
212+
metadataCode = `fetch(new URL("./ui/${dataFile}", window.location.href)).then(async res => new Uint8Array(await res.arrayBuffer()))`
213+
// rewrite the asset path from `./*` to `./ui/*`
214+
html = html.replace(/\b(href|src)="\.\//g, '$1="./ui/')
209215
}
210216

211217
await fs.writeFile(

0 commit comments

Comments
 (0)