Skip to content

feat(browser): add failure-only trace view mode - #11612

Merged
sheremet-va merged 24 commits into
vitest-dev:mainfrom
hi-ogawa:fix/issue-10310
Oct 26, 2026
Merged

sheremet-va merged 24 commits into
vitest-dev:mainfrom
hi-ogawa:fix/issue-10310

Conversation

@hi-ogawa

@hi-ogawa hi-ogawa commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Trace view records every step of every test and sends each one to the server as it happens, with a snapshot per step, so every passing test pays for it. This PR adds a 'failure-only' mode that keeps traces only for failed tests, at no cost to passing tests. The trace timeline printed on failure (#11594) works the same in this mode, because it reads the failed test's trace.

Usage

browser: {
  traceView: 'failure-only',
  // or
  traceView: { mode: 'failure-only', ... }
}

browser.traceView.enabled is deprecated in favor of browser.traceView.mode, which covers the same on and off switch and also selects the new mode.

Performance

'failure-only' adds no measurable cost to passing tests. Each number below is the average time of one operation in a passing test over 100 calls after 10 warmup calls, taking the median of 3 runs on macOS (Apple Silicon) with headless chromium. The page has a 1000-row list, so the per-step DOM snapshot of 'on' shows up clearly.

Operation in a passing test Trace view off 'failure-only' 'on'
click 50.0 ms same as off 134.3 ms (+169%)
expect.element assertion 2.6 ms same as off 108.0 ms (×42)

Real suites with small pages see less overhead from 'on'. For example, test/browser on chromium (135 passing tests) takes about 3.9s with trace view off, 3.9–4.0s with 'failure-only', and 4.2s with 'on'.

A failed test in 'failure-only' takes one snapshot and sends its trace once at the end. That cost isn't measured here.

A passing test still records each step in the browser. The main cost is capturing the step's location with new Error().stack, about 0.004 ms per step, plus a small entry object with a timestamp. It never takes a snapshot, loads the snapshot library, or sends anything to the server, so its cost is effectively zero. That makes 'failure-only' a candidate for the default trace view mode, which is left as a follow-up.

Reproduction

vitest.config.ts

import { playwright } from '@vitest/browser-playwright'
import { defineConfig } from 'vitest/config'

const mode = (process.env.TRACE_MODE ?? 'off') as 'off' | 'on' | 'failure-only'

export default defineConfig({
  test: {
    testTimeout: 120_000,
    browser: {
      enabled: true,
      provider: playwright(),
      instances: [{ browser: 'chromium' }],
      headless: true,
      traceView: mode,
      commands: {
        report: (_ctx, name: string, msPerOp: number) => {
          console.log(`${mode} ${name}: ${msPerOp.toFixed(2)}ms`)
        },
      },
    },
  },
})

bench.test.ts

import { beforeAll, expect, test } from 'vitest'
import { commands, page } from 'vitest/browser'

declare module 'vitest/browser' {
  interface BrowserCommands {
    report: (name: string, msPerOp: number) => Promise<void>
  }
}

async function bench(name: string, fn: () => Promise<void>) {
  for (let i = 0; i < 10; i++) {
    await fn()
  }
  const start = performance.now()
  for (let i = 0; i < 100; i++) {
    await fn()
  }
  await commands.report(name, (performance.now() - start) / 100)
}

beforeAll(() => {
  const rows = Array.from({ length: 1000 }, (_, i) =>
    `<li><span>row ${i}</span> <a href="#${i}">link ${i}</a> <input value="v${i}"></li>`).join('')
  document.body.innerHTML = `<button>inc</button><ul>${rows}</ul>`
})

test('click', async () => {
  await bench('click', () => page.getByRole('button', { name: 'inc' }).click())
})

test('expect.element', async () => {
  await bench('expect.element', () => expect.element(page.getByText('row 1', { exact: true })).toBeVisible())
})
for mode in off failure-only on; do TRACE_MODE=$mode npx vitest run; done

Implementation

The idea is:

  • import rrweb-snapshot at the time of first snapshot
  • accumulate BrowserTraceEntry array on client side till end instead of sending each entry via rpc
  • only take snapshot for failed onAfterRetryTask

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@netlify

netlify Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for vitest-dev ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 92b2ac3
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/6ad7379d586f0cfa45031eef
😎 Deploy Preview https://deploy-preview-11612--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

hi-ogawa and others added 17 commits October 20, 2026 18:02
# Conflicts:
#	docs/config/browser/traceview.md
#	packages/ui/client/components/trace/TraceView.vue
#	packages/vitest/src/node/config/resolveConfig.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hi-ogawa
hi-ogawa marked this pull request as ready for review October 20, 2026 11:03
@hi-ogawa hi-ogawa added this to the 5.1.0 milestone Oct 21, 2026
hi-ogawa and others added 2 commits October 21, 2026 11:55
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… mode"

This reverts commit 394809a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sheremet-va
sheremet-va previously approved these changes Oct 26, 2026
ariaSnapshot: true,
}
const traceViewOptions = typeof traceView === 'object' ? traceView : undefined
const traceViewMode =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should show a warning if traceView.enabled is set. We have logger.deprecate

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sheremet-va
sheremet-va previously approved these changes Oct 26, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
hi-ogawa and others added 2 commits October 26, 2026 15:38
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Accepting a mode made the flag consume a following test filter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@hi-ogawa
hi-ogawa requested a review from sheremet-va October 26, 2026 07:00
Comment on lines +420 to +425
transform(value) {
if (typeof value === 'boolean') {
return { mode: value ? 'on' : 'off' }
}
return value
},

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This normalization is for avoid triggering traceView.enabled deprecation warning when users enable it through cli --browser.traceView.

@sheremet-va
sheremet-va merged commit d5d7d2f into vitest-dev:main Oct 26, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Trace View: Failure-Only Trace Recording

2 participants