···163163164164 test(`stack trace points to correct file in every browser`, () => {
165165 // depending on the browser it references either `.toBe()` or `expect()`
166166- expect(stderr).toMatch(/test\/failing.test.ts:10:(12|17)/)
166166+ expect(stderr).toMatch(/test\/failing.test.ts:11:(12|17)/)
167167168168 // column is 18 in safari, 8 in others
169169 expect(stderr).toMatch(/throwError src\/error.ts:8:(18|8)/)
170170171171 expect(stderr).toContain('The call was not awaited. This method is asynchronous and must be awaited; otherwise, the call will not start to avoid unhandled rejections.')
172172- expect(stderr).toMatch(/test\/failing.test.ts:18:(27|36)/)
173173- expect(stderr).toMatch(/test\/failing.test.ts:19:(27|33)/)
174174- expect(stderr).toMatch(/test\/failing.test.ts:20:(27|39)/)
172172+ expect(stderr).toMatch(/test\/failing.test.ts:19:(27|36)/)
173173+ expect(stderr).toMatch(/test\/failing.test.ts:20:(27|33)/)
174174+ expect(stderr).toMatch(/test\/failing.test.ts:21:(27|39)/)
175175+176176+ expect(stderr).toMatch(/bundled-lib\/src\/b.js:2:(8|18)/)
177177+ expect(stderr).toMatch(/bundled-lib\/src\/index.js:5:(15|17)/)
178178+ expect(stderr).toMatch(/test\/failing.test.ts:25:(2|8)/)
175179 })
176180177181 test('popup apis should log a warning', () => {
+5
test/browser/test/failing.test.ts
···11import { page } from '@vitest/browser/context'
22+import { index } from '@vitest/bundled-lib'
23import { expect, it } from 'vitest'
34import { throwError } from '../src/error'
45···1819 page.getByRole('button').dblClick()
1920 page.getByRole('button').click()
2021 page.getByRole('button').tripleClick()
2222+})
2323+2424+it('correctly prints error from a bundled file', () => {
2525+ index()
2126})
+49-7
packages/browser/src/node/projectParent.ts
···1010 Vitest,
1111} from 'vitest/node'
1212import type { BrowserServerState } from './state'
1313+import { readFileSync } from 'node:fs'
1314import { readFile } from 'node:fs/promises'
1415import { parseErrorStacktrace, parseStacktrace, type StackTraceParserOptions } from '@vitest/utils/source-map'
1515-import { join, resolve } from 'pathe'
1616+import { dirname, join, resolve } from 'pathe'
1617import { BrowserServerCDPHandler } from './cdp'
1718import builtinCommands from './commands/index'
1819import { distRoot } from './constants'
···41424243 public config: ResolvedConfig
43444545+ // cache for non-vite source maps
4646+ private sourceMapCache = new Map<string, any>()
4747+4448 constructor(
4549 public project: TestProject,
4650 public base: string,
···5054 this.stackTraceOptions = {
5155 frameFilter: project.config.onStackTrace,
5256 getSourceMap: (id) => {
5757+ if (this.sourceMapCache.has(id)) {
5858+ return this.sourceMapCache.get(id)
5959+ }
5360 const result = this.vite.moduleGraph.getModuleById(id)?.transformResult
6161+ // this can happen for bundled dependencies in node_modules/.vite
6262+ if (result && !result.map) {
6363+ const sourceMapUrl = this.retrieveSourceMapURL(result.code)
6464+ if (!sourceMapUrl) {
6565+ return null
6666+ }
6767+ const filepathDir = dirname(id)
6868+ const sourceMapPath = resolve(filepathDir, sourceMapUrl)
6969+ const map = JSON.parse(readFileSync(sourceMapPath, 'utf-8'))
7070+ this.sourceMapCache.set(id, map)
7171+ return map
7272+ }
5473 return result?.map
5574 },
5656- getFileName: (id) => {
7575+ getUrlId: (id) => {
5776 const mod = this.vite.moduleGraph.getModuleById(id)
5858- if (mod?.file) {
5959- return mod.file
7777+ if (mod) {
7878+ return id
6079 }
6161- const modUrl = this.vite.moduleGraph.urlToModuleMap.get(id)
6262- if (modUrl?.file) {
6363- return modUrl.file
8080+ const resolvedPath = resolve(project.config.root, id.slice(1))
8181+ const modUrl = this.vite.moduleGraph.getModuleById(resolvedPath)
8282+ if (modUrl) {
8383+ return resolvedPath
8484+ }
8585+ // some browsers (looking at you, safari) don't report queries in stack traces
8686+ // the next best thing is to try the first id that this file resolves to
8787+ const files = this.vite.moduleGraph.getModulesByFile(resolvedPath)
8888+ if (files && files.size) {
8989+ return files.values().next().value!.id!
6490 }
6591 return id
6692 },
···234260 .split('/')
235261 const decodedTestFile = decodeURIComponent(testFile)
236262 return { sessionId, testFile: decodedTestFile }
263263+ }
264264+265265+ private retrieveSourceMapURL(source: string): string | null {
266266+ const re
267267+ = /\/\/[@#]\s*sourceMappingURL=([^\s'"]+)\s*$|\/\*[@#]\s*sourceMappingURL=[^\s*'"]+\s*\*\/\s*$/gm
268268+ // Keep executing the search to find the *last* sourceMappingURL to avoid
269269+ // picking up sourceMappingURLs from comments, strings, etc.
270270+ let lastMatch, match
271271+ // eslint-disable-next-line no-cond-assign
272272+ while ((match = re.exec(source))) {
273273+ lastMatch = match
274274+ }
275275+ if (!lastMatch) {
276276+ return null
277277+ }
278278+ return lastMatch[1]
237279 }
238280}