vitest icon indicating copy to clipboard operation
vitest copied to clipboard

Coverage page is broken in the html report for @vitest/ui

Open thebanjomatic opened this issue 1 year ago • 6 comments

Describe the bug

When using the 'html' reporter for vitest, with the html coverage reporter also enabled, the UI contains a button to load the coverage report in an iframe, but the iframe fails to load because it is loaded from '/coverage/index.html'.

There are really two problems here.

  1. By iframing in the url starting with a / the browser will attempt to load http://my-host.io/coverage/index.html regardless of the url route your report is served from. Instead, it might be preferrable to use a relative path: http://my-host.io/report/12345/coverage/index.html etc.
  2. With the default configured options, the two reports are generated in separate directories: ./coverage and ./html This makes it harder to bundle all the report artifacts because they are in separate places.

Regarding #2 it would be nice if the html report directory was automatically copied into the vitest html report directory and just referenced via a relative url if the html coverage reporter is enabled. That would solve both issues at once for me without requiring extra configuration. That is my ideal fix, but I can also see the following being useful:

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      reporter: ['lcov', 'text-summary', ['html', {subdir: '../vitest-report/coverage'}]],
    },
    reporters: [['html', {outputFile: './vitest-report/index.html'}]],
  },
});

The above configuration correctly places the html coverage directory inside of the vitest html report directory, but the paths are not resolved correctly still, Additional work would be needed to support this: image

Related Issue: #6144

The above issue appears related in that they are trying to ultimately solve the same problem of the coverage not working depending on how the page is accessed or hosted, but I think the problems are deeper than what is described in the linked issue, so I created this new issue to track fixing some of those fundamental issues as at that point configuring the url to point to an externally hosted service is an orthogonal concern.

Reproduction

Use the following vitest config on a project:

import {defineConfig} from 'vitest/config'
export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      reporter: ['html'],
    },
    reporters: ['html'],
  },
});

Execute:

npx vitest run

Follow the instructions for previewing the test results:

npx vite preview --outDir html

Click the coverage button: image

Observe that the coverage page fails to load.

System Info

System:
    OS: Windows 11 10.0.22631
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12800H
    Memory: 10.12 GB / 31.64 GB
  Binaries:
    Node: 22.11.0 - ~\AppData\Local\fnm_multishells\20364_1732206955982\node.EXE
    Yarn: 4.5.1 - ~\AppData\Local\fnm_multishells\20364_1732206955982\yarn.CMD
    npm: 10.9.0 - ~\AppData\Local\fnm_multishells\20364_1732206955982\npm.CMD
    bun: 1.1.29 - ~\.bun\bin\bun.EXE
  Browsers:
    Edge: Chromium (127.0.2651.74)
    Internet Explorer: 11.0.22621.3527
  npmPackages:
    @vitejs/plugin-vue: ^5.1.4 => 5.1.4
    @vitest/coverage-v8: ^2.1.2 => 2.1.3
    @vitest/ui: 2.1.3 => 2.1.3
    vite: ^5.4.8 => 5.4.10
    vitest: ^2.1.2 => 2.1.3

Used Package Manager

yarn

Validations

thebanjomatic avatar Nov 21 '24 17:11 thebanjomatic

Hi, I know this issue was open some time ago but I just encountered this problem and it seems like it's not solved yet.

I managed to solve this problem with a little tweak. Instead of using the subdir field from the html reporter, I changed the whole reportsDirectory to the vitest-report dir. Then, if I want another report out of there, I change it with the file option. In the following example I'm doing that last part for the cobertura report:

import {defineConfig} from 'vitest/config'

export default defineConfig({
  test: {
    coverage: {
      enabled: true,
      reporter: ['html', ['cobertura', { file: '../../coverage/cobertura.xml' }]],
      reportsDirectory: './vitest-report/coverage',
    },
    reporters: [['html', {outputFile: './vitest-report/index.html'}]],
  },
});

This will put the vitest ui and coverage report inside the <root>/vitest-report folder without modifying the coverage path, and the cobertura report in <root>/coverage/.

It's a bit hacky but hope it helps while they fix the problem! 🙏

daniseijo avatar Apr 02 '25 14:04 daniseijo

Hi, I know this issue was open some time ago but I just encountered this problem and it seems like it's not solved yet.

I managed to solve this problem with a little tweak. Instead of using the subdir field from the html reporter, I changed the whole reportsDirectory to the vitest-report dir. Then, if I want another report out of there, I change it with the file option. In the following example I'm doing that last part for the cobertura report:

import {defineConfig} from 'vitest/config'

export default defineConfig({ test: { coverage: { enabled: true, reporter: ['html', ['cobertura', { file: '../../coverage/cobertura.xml' }]], reportsDirectory: './vitest-report/coverage', }, reporters: [['html', {outputFile: './vitest-report/index.html'}]], }, }); This will put the vitest ui and coverage report inside the <root>/vitest-report folder without modifying the coverage path, and the cobertura report in <root>/coverage/.

It's a bit hacky but hope it helps while they fix the problem! 🙏

Is it possible to enable this in some manner for html coverage reporter. I've the following config and i need to keep the coverage and test results in once folder to post the results in our service.

test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/setupTests.ts',
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
    reporters: ['default', 'html'],
    outputFile: {
      default: 'test-reports/default.html',
      html: 'test-reports/index.html',
    },
    coverage: {
      provider: 'v8',
      enabled: process.env.VITEST_COVERAGE === 'true',
      reportsDirectory: './test-reports/coverage',
      reporter: ['html', 'text', 'json'],
      exclude: [
        'src/main.tsx',
        'src/vite-env.d.ts',
        '**/index.ts',
        '**/*.d.ts',
        '*.config.{ts,js}',
        'test-reports/**',
      ],
      thresholds: {
        lines: 90,
        functions: 90,
        statements: 90,
        branches: 80,
      },
    },
  },

t29gupta avatar Sep 21 '25 17:09 t29gupta

I have this same issue

when clicking on coverage in the ui: Uncaught ReferenceError: __vite_ssr_exportName__ is not defined

const vitestConfig = defineVitestConfig({
  base: "/",
  test: {
    globals: true,
    environment: "jsdom",
    setupFiles: "./src/test/setupTests.ts",
    coverage: {
      reportsDirectory: "./static/coverage",
      reporter: ["text", "json", "html"],
      include: ["src/**/*.ts", "src/**/*.tsx"],
      provider: "v8",
    },
  },
});

export default ({ mode }: { mode: string }) => {
  if (mode === "test") return mergeConfig(shared, vitestConfig);
  throw new Error("Mode must be 'client', 'server', or 'test'");
};

 "test": "vitest --mode test",
    "test:ui": "vitest --ui --mode test --coverage.enabled=true",
    "coverage": "vitest run --mode test --coverage",
    "reporter:coverage": "vitest run --mode test --reporter=json --coverage || true",
<iframe id="vitest-ui-coverage" src="/coverage/index.html"></iframe>

Update:

i got it to work by adding reportOnFailure: true,

const vitestConfig = defineVitestConfig({
  base: "/",
  test: {
    globals: true,
    environment: "jsdom",
    setupFiles: "./src/test/setupTests.ts",
    coverage: {
      reportOnFailure: true,
      enabled: true,
      reportsDirectory: "./test-reports/coverage",
      reporter: ["text", "json", "html"],
      include: ["src/**/*.ts", "src/**/*.tsx"],
      provider: "v8",
    },
  },
});

so what i figured is going on is, coverage wont run unless all tests pass, therefore adding reportOnFailure: true, flags failed/unwritten tests as well. weird but seems to solve it for me.

Fanna1119 avatar Nov 09 '25 16:11 Fanna1119

Hi, I have the first mentioned problem in my CI The vitest report is hosted on e.g. ci.company.com/12345/index.html, I configured coverage according to docs and it tries to load coverage report from ci.company.com/coverage/index.html Is it possible to workaround this issue?

reistr avatar Nov 14 '25 15:11 reistr

Simply place the coverage report in the same folder as the Vitest UI test report. For example, if the Vitest UI test report’s outputDir is ./html, then the coverage report should be placed in ./html/coverage.

  test: {
    globals: true,
    environment: "jsdom",
    reporters: ["default", "html"],
    setupFiles: "./vitest.setup.ts",
    coverage: {
      enabled: true,
      reportsDirectory: "./html/coverage"
    },
  },

guychienll-uni avatar Nov 20 '25 09:11 guychienll-uni

Simply place the coverage report in the same folder as the Vitest UI test report. For example, if the Vitest UI test report’s outputDir is ./html, then the coverage report should be placed in ./html/coverage.

  test: {
    globals: true,
    environment: "jsdom",
    reporters: ["default", "html"],
    setupFiles: "./vitest.setup.ts",
    coverage: {
      enabled: true,
      reportsDirectory: "./html/coverage"
    },
  },

That do not work as the link to the coverage report is constructed relative to the host root and in my case vitest report is not hosted in the root. Seems that this line https://github.com/vitest-dev/vitest/blob/075ab35207ce7e6ad6bd3abb02ba6d14d5108c7b/packages/ui/client/composables/navigation.ts#L57 does not take into consideration current location

reistr avatar Nov 27 '25 15:11 reistr