From ac6971ca24bd3ae30c04f2b5b60ca8d177c9b805 Mon Sep 17 00:00:00 2001 From: Ayush <63203492+macayu17@users.noreply.github.com> Date: Fri, 22 May 2026 12:14:50 +0530 Subject: [PATCH] fix: preserve `vi.defineHelper` callsite for async error stack (#10415) Co-authored-by: Hiroshi Ogawa --- packages/vitest/src/integrations/vi.ts | 18 +++- .../fixtures/assertion-helper/basic.test.ts | 32 +++++++ test/e2e/test/assertion-helper.test.ts | 83 ++++++++++++++++--- 3 files changed, 120 insertions(+), 13 deletions(-) diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 54f081de2..c640df5ca 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -615,8 +615,17 @@ function createVitest(): VitestUtils { return function __VITEST_HELPER__(this: any, ...args: any[]): any { const result = fn.apply(this, args) if (result && typeof result === 'object' && typeof result.then === 'function') { + const stackTraceError = new Error('STACK_TRACE_ERROR') return (async function __VITEST_HELPER__() { - return await result + try { + return await result + } + catch (error) { + if (error instanceof Error && !error.stack?.includes('__VITEST_HELPER__')) { + copyStackTrace(error, stackTraceError) + } + throw error + } })() } return result @@ -859,3 +868,10 @@ function getImporter(name: string) { const stack = parseSingleStack(stackArray[importerStackIndex + 1]) return stack?.file || '' } + +function copyStackTrace(target: Error, source: Error) { + if (source.stack !== undefined) { + target.stack = source.stack.replace(source.message, target.message) + } + return target +} diff --git a/test/e2e/fixtures/assertion-helper/basic.test.ts b/test/e2e/fixtures/assertion-helper/basic.test.ts index e8df6c7a5..a5834a33b 100644 --- a/test/e2e/fixtures/assertion-helper/basic.test.ts +++ b/test/e2e/fixtures/assertion-helper/basic.test.ts @@ -131,3 +131,35 @@ test("async helper with context pass", async () => { test("async helper with context fail", async () => { await helperObject.assertEqualPropAsync(4321); }); + +const checkWithWaitFor = vi.defineHelper(async () => { + await vi.waitFor(() => { + expect(1).toBe(2); + }, { timeout: 20, interval: 10 }); +}); + +const checkWithWaitUntil = vi.defineHelper(async () => { + await vi.waitUntil(() => { + expect(1).toBe(2); + }, { timeout: 20, interval: 10 }); +}); + +const throwPlainAsyncError = vi.defineHelper(async () => { + await new Promise((_resolve, reject) => { + setTimeout(() => { + reject(new Error("async error from helper")); + }, 1); + }); +}); + +test("waitFor keeps helper callsite", async () => { + await checkWithWaitFor(); +}); + +test("waitUntil keeps helper callsite", async () => { + await checkWithWaitUntil(); +}); + +test("plain async error", async () => { + await throwPlainAsyncError(); +}); diff --git a/test/e2e/test/assertion-helper.test.ts b/test/e2e/test/assertion-helper.test.ts index 9615c4d7a..7d1838c11 100644 --- a/test/e2e/test/assertion-helper.test.ts +++ b/test/e2e/test/assertion-helper.test.ts @@ -13,7 +13,7 @@ it('assertion helper', async () => { ❯ basic.test.ts:105:3 - ⎯⎯⎯⎯⎯⎯ Failed Tests 10 ⎯⎯⎯⎯⎯⎯⎯ + ⎯⎯⎯⎯⎯⎯ Failed Tests 13 ⎯⎯⎯⎯⎯⎯⎯ FAIL basic.test.ts > sync AssertionError: expected 'sync' to deeply equal 'x' @@ -29,7 +29,7 @@ it('assertion helper', async () => { 23| }); 24| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/14]⎯ FAIL basic.test.ts > async AssertionError: expected 'async' to deeply equal 'x' @@ -45,7 +45,7 @@ it('assertion helper', async () => { 27| }); 28| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/14]⎯ FAIL basic.test.ts > soft AssertionError: expected 'soft' to deeply equal 'x' @@ -61,7 +61,7 @@ it('assertion helper', async () => { 31| }); 32| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/14]⎯ FAIL basic.test.ts > soft async AssertionError: expected 'soft async' to deeply equal 'x' @@ -77,7 +77,7 @@ it('assertion helper', async () => { 35| }); 36| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/14]⎯ FAIL basic.test.ts > nested AssertionError: expected 'nested' to deeply equal 'x' @@ -93,7 +93,7 @@ it('assertion helper', async () => { 47| }); 48| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/14]⎯ FAIL basic.test.ts > multiple soft AssertionError: expected 'first' to deeply equal 'x' @@ -109,7 +109,7 @@ it('assertion helper', async () => { 78| myEqualSoft("second", "y"); 79| }); - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/14]⎯ FAIL basic.test.ts > multiple soft AssertionError: expected 'second' to deeply equal 'y' @@ -125,7 +125,7 @@ it('assertion helper', async () => { 79| }); 80| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/14]⎯ FAIL basic.test.ts > custom error Error: custom error from helper @@ -137,7 +137,7 @@ it('assertion helper', async () => { 88| }); 89| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/14]⎯ FAIL basic.test.ts > non-helper wrapper AssertionError: expected 'wrapper' to deeply equal 'x' @@ -154,7 +154,7 @@ it('assertion helper', async () => { 94| ❯ basic.test.ts:96:3 - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/14]⎯ FAIL basic.test.ts > helper with context fail AssertionError: expected 4321 to deeply equal 1234 @@ -173,7 +173,7 @@ it('assertion helper', async () => { 125| }); 126| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/14]⎯ FAIL basic.test.ts > async helper with context fail AssertionError: expected 4321 to deeply equal 1234 @@ -192,7 +192,57 @@ it('assertion helper', async () => { 133| }); 134| - ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/11]⎯ + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/14]⎯ + + FAIL basic.test.ts > waitFor keeps helper callsite + AssertionError: expected 1 to be 2 // Object.is equality + + - Expected + + Received + + - 2 + + 1 + + ❯ basic.test.ts:156:9 + 154| + 155| test("waitFor keeps helper callsite", async () => { + 156| await checkWithWaitFor(); + | ^ + 157| }); + 158| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[12/14]⎯ + + FAIL basic.test.ts > waitUntil keeps helper callsite + AssertionError: expected 1 to be 2 // Object.is equality + + - Expected + + Received + + - 2 + + 1 + + ❯ basic.test.ts:160:9 + 158| + 159| test("waitUntil keeps helper callsite", async () => { + 160| await checkWithWaitUntil(); + | ^ + 161| }); + 162| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[13/14]⎯ + + FAIL basic.test.ts > plain async error + Error: async error from helper + ❯ basic.test.ts:164:9 + 162| + 163| test("plain async error", async () => { + 164| await throwPlainAsyncError(); + | ^ + 165| }); + 166| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/14]⎯ " `) @@ -226,6 +276,9 @@ it('assertion helper', async () => { ], "pass async": "passed", "pass sync": "passed", + "plain async error": [ + "async error from helper", + ], "return async": "passed", "return sync": "passed", "soft": [ @@ -237,6 +290,12 @@ it('assertion helper', async () => { "sync": [ "expected 'sync' to deeply equal 'x'", ], + "waitFor keeps helper callsite": [ + "expected 1 to be 2 // Object.is equality", + ], + "waitUntil keeps helper callsite": [ + "expected 1 to be 2 // Object.is equality", + ], }, } `) -- 2.51.2