From ddcb67ce63bc188a27eac2934afcdda034f7a2b7 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 13:09:27 +0900 Subject: [PATCH 01/12] fix(runner): fix fixture cleanup freeze when test timeout --- packages/runner/src/fixture.ts | 38 +++++++++++++++++++ .../test-extend/fixture-error.test.ts | 14 ++++++- .../test/__snapshots__/runner.test.ts.snap | 3 +- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index d85cd6e3fbd7..bdef9583d4ca 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -78,6 +78,44 @@ export function withFixtures(fn: Function, testContext?: TestContext) { if (!pendingFixtures.length) return fn(context) + async function resolveFixtures() { + for (const fixture of pendingFixtures) { + // fixture could be already initialized during "before" hook + if (!fixtureValueMap.has(fixture)) { + if (fixture.isFn) { + // wait for `use` call to extract fixture value + const useArg = await new Promise((resolveUseArg, rejectUseArg) => { + const fixtureReturn = fixture.value(context, (useArg: unknown) => { + resolveUseArg(useArg) + // continue fixture function during cleanup + return new Promise((resolveUseReturn) => { + cleanupFnArray.push(resolveUseReturn) + }) + }) + if (fixtureReturn instanceof Promise) { + // TODO: this rejection can also happen the cleanup code after "use", + // which is not desired? + fixtureReturn.catch(rejectUseArg) + } + else { + throw new TypeError('fixture function must be asynchronous') + } + }) + fixtureValueMap.set(fixture, useArg) + } + else { + fixtureValueMap.set(fixture, fixture.value) + } + cleanupFnArray.unshift(() => { + fixtureValueMap.delete(fixture) + }) + } + context![fixture.prop] = fixtureValueMap.get(fixture)! + } + } + + return resolveFixtures().then(() => fn(context)) + let cursor = 0 return new Promise((resolve, reject) => { diff --git a/test/fails/fixtures/test-extend/fixture-error.test.ts b/test/fails/fixtures/test-extend/fixture-error.test.ts index 70fd35d436c6..4f815b9cae8d 100644 --- a/test/fails/fixtures/test-extend/fixture-error.test.ts +++ b/test/fails/fixtures/test-extend/fixture-error.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expectTypeOf, test } from 'vitest' +import { afterEach, beforeEach, describe, expectTypeOf, test, expect } from 'vitest' describe('error thrown in beforeEach fixtures', () => { const myTest = test.extend<{ a: never }>({ @@ -38,3 +38,15 @@ describe('error thrown in test fixtures', () => { // eslint-disable-next-line unused-imports/no-unused-vars myTest('fixture errors', ({ a }) => {}) }) + +describe('correctly fails when test times out', () => { + const myTest = test.extend<{ a: number }>({ + a: async ({}, use) => { + await use(2) + }, + }) + myTest('test times out', async ({ a }) => { + await new Promise((resolve) => setTimeout(resolve, 1000)) + expect(a).toBe(2) + }, 20) +}) diff --git a/test/fails/test/__snapshots__/runner.test.ts.snap b/test/fails/test/__snapshots__/runner.test.ts.snap index 7c060bad49dc..7b74b599b1a3 100644 --- a/test/fails/test/__snapshots__/runner.test.ts.snap +++ b/test/fails/test/__snapshots__/runner.test.ts.snap @@ -43,7 +43,8 @@ TypeError: failure" exports[`should fail test-extend/circular-dependency.test.ts > test-extend/circular-dependency.test.ts 1`] = `"Error: circular fixture dependency"`; exports[`should fail test-extend/fixture-error.test.ts > test-extend/fixture-error.test.ts 1`] = ` -"Error: Error thrown in test fixture +"Error: Test timed out in 20ms. +Error: Error thrown in test fixture Error: Error thrown in afterEach fixture Error: Error thrown in beforeEach fixture" `; From b1b3c334b9169c5120cd20c55b6b50388d246eec Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 13:24:59 +0900 Subject: [PATCH 02/12] chore: comment --- packages/runner/src/fixture.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index bdef9583d4ca..c9b9a17a7edc 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -93,8 +93,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) { }) }) if (fixtureReturn instanceof Promise) { - // TODO: this rejection can also happen the cleanup code after "use", - // which is not desired? + // TODO: this rejection can also happen during the cleanup code after "use", which is probably not desired? fixtureReturn.catch(rejectUseArg) } else { From d29d506b1816b5066bd1ecc9efae3ccd86d66756 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 13:40:08 +0900 Subject: [PATCH 03/12] fix: don't silence fixture teardown error --- packages/runner/src/fixture.ts | 12 ++++++++++-- .../fixtures/test-extend/fixture-error.test.ts | 13 +++++++++++++ test/fails/test/__snapshots__/runner.test.ts.snap | 3 ++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index c9b9a17a7edc..db498521176a 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -84,17 +84,25 @@ export function withFixtures(fn: Function, testContext?: TestContext) { if (!fixtureValueMap.has(fixture)) { if (fixture.isFn) { // wait for `use` call to extract fixture value + let isFixtureTeardown = false const useArg = await new Promise((resolveUseArg, rejectUseArg) => { const fixtureReturn = fixture.value(context, (useArg: unknown) => { resolveUseArg(useArg) + isFixtureTeardown = true // continue fixture function during cleanup return new Promise((resolveUseReturn) => { cleanupFnArray.push(resolveUseReturn) }) }) if (fixtureReturn instanceof Promise) { - // TODO: this rejection can also happen during the cleanup code after "use", which is probably not desired? - fixtureReturn.catch(rejectUseArg) + fixtureReturn.catch((e) => { + // re-throw if error is thrown during fixture teardown + if (isFixtureTeardown) + throw e + + // otherwise treat as test failure which called this fixture + rejectUseArg(e) + }) } else { throw new TypeError('fixture function must be asynchronous') diff --git a/test/fails/fixtures/test-extend/fixture-error.test.ts b/test/fails/fixtures/test-extend/fixture-error.test.ts index 4f815b9cae8d..ae478bd93fad 100644 --- a/test/fails/fixtures/test-extend/fixture-error.test.ts +++ b/test/fails/fixtures/test-extend/fixture-error.test.ts @@ -50,3 +50,16 @@ describe('correctly fails when test times out', () => { expect(a).toBe(2) }, 20) }) + +describe('error thrown during fixture teardown', () => { + const myTest = test.extend<{ a: string }>({ + a: async ({}, use) => { + await use("hello"); + throw new Error('Error fixture teardown') + }, + }) + + myTest('fixture errors', ({ a }) => { + expect(a).toBe("hello"); + }) +}) diff --git a/test/fails/test/__snapshots__/runner.test.ts.snap b/test/fails/test/__snapshots__/runner.test.ts.snap index 7b74b599b1a3..76fa246f0171 100644 --- a/test/fails/test/__snapshots__/runner.test.ts.snap +++ b/test/fails/test/__snapshots__/runner.test.ts.snap @@ -43,7 +43,8 @@ TypeError: failure" exports[`should fail test-extend/circular-dependency.test.ts > test-extend/circular-dependency.test.ts 1`] = `"Error: circular fixture dependency"`; exports[`should fail test-extend/fixture-error.test.ts > test-extend/fixture-error.test.ts 1`] = ` -"Error: Test timed out in 20ms. +"Error: Error fixture teardown +Error: Test timed out in 20ms. Error: Error thrown in test fixture Error: Error thrown in afterEach fixture Error: Error thrown in beforeEach fixture" From 998983cd1a6ba73b06b720711e2cc6c94d8d267b Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 14:11:21 +0900 Subject: [PATCH 04/12] chore: remove unused code --- packages/runner/src/fixture.ts | 44 ---------------------------------- 1 file changed, 44 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index db498521176a..668a2aaeb919 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -122,50 +122,6 @@ export function withFixtures(fn: Function, testContext?: TestContext) { } return resolveFixtures().then(() => fn(context)) - - let cursor = 0 - - return new Promise((resolve, reject) => { - async function use(fixtureValue: any) { - const fixture = pendingFixtures[cursor++] - context![fixture.prop] = fixtureValue - - if (!fixtureValueMap.has(fixture)) { - fixtureValueMap.set(fixture, fixtureValue) - cleanupFnArray.unshift(() => { - fixtureValueMap.delete(fixture) - }) - } - - if (cursor < pendingFixtures.length) { - await next() - } - else { - // When all fixtures setup, call the test function - try { - resolve(await fn(context)) - } - catch (err) { - reject(err) - } - return new Promise((resolve) => { - cleanupFnArray.push(resolve) - }) - } - } - - async function next() { - const fixture = pendingFixtures[cursor] - const { isFn, value } = fixture - if (fixtureValueMap.has(fixture)) - return use(fixtureValueMap.get(fixture)) - else - return isFn ? value(context, use) : use(value) - } - - const setupFixturePromise = next().catch(reject) - cleanupFnArray.unshift(() => setupFixturePromise) - }) } } From 35002592c8a1cec73126d4c201467b75afded129 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 14:59:15 +0900 Subject: [PATCH 05/12] refactor: minor --- packages/runner/src/fixture.ts | 61 ++++++++++++++++------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index 668a2aaeb919..b165efe8bc22 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -81,43 +81,38 @@ export function withFixtures(fn: Function, testContext?: TestContext) { async function resolveFixtures() { for (const fixture of pendingFixtures) { // fixture could be already initialized during "before" hook - if (!fixtureValueMap.has(fixture)) { - if (fixture.isFn) { - // wait for `use` call to extract fixture value - let isFixtureTeardown = false - const useArg = await new Promise((resolveUseArg, rejectUseArg) => { - const fixtureReturn = fixture.value(context, (useArg: unknown) => { - resolveUseArg(useArg) - isFixtureTeardown = true - // continue fixture function during cleanup - return new Promise((resolveUseReturn) => { - cleanupFnArray.push(resolveUseReturn) - }) + if (fixtureValueMap.has(fixture)) + continue + + let fixtureValue: unknown + if (fixture.isFn) { + // wait for `use` call to extract fixture value + let isFixtureTeardown = false + fixtureValue = await new Promise((resolveUseArg, rejectUseArg) => { + fixture.value(context, (useArg: unknown) => { + resolveUseArg(useArg) + isFixtureTeardown = true + // continue fixture function during cleanup + return new Promise((resolveUseReturn) => { + cleanupFnArray.push(resolveUseReturn) }) - if (fixtureReturn instanceof Promise) { - fixtureReturn.catch((e) => { - // re-throw if error is thrown during fixture teardown - if (isFixtureTeardown) - throw e - - // otherwise treat as test failure which called this fixture - rejectUseArg(e) - }) - } - else { - throw new TypeError('fixture function must be asynchronous') - } + }).catch((e: unknown) => { + // re-throw if error is thrown during fixture teardown + if (isFixtureTeardown) + throw e + // otherwise treat as test failure which called this fixture + rejectUseArg(e) }) - fixtureValueMap.set(fixture, useArg) - } - else { - fixtureValueMap.set(fixture, fixture.value) - } - cleanupFnArray.unshift(() => { - fixtureValueMap.delete(fixture) }) } - context![fixture.prop] = fixtureValueMap.get(fixture)! + else { + fixtureValue = fixture.value + } + fixtureValueMap.set(fixture, fixtureValue) + context![fixture.prop] = fixtureValue + cleanupFnArray.unshift(() => { + fixtureValueMap.delete(fixture) + }) } } From a05f7073476486daa0bb80eea184792a41eed8ca Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 15:03:04 +0900 Subject: [PATCH 06/12] chore: minor --- packages/runner/src/fixture.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index b165efe8bc22..188e3ff738db 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -100,7 +100,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) { // re-throw if error is thrown during fixture teardown if (isFixtureTeardown) throw e - // otherwise treat as test failure which called this fixture + // otherwise it as a failure of a test calling this fixture rejectUseArg(e) }) }) @@ -108,8 +108,8 @@ export function withFixtures(fn: Function, testContext?: TestContext) { else { fixtureValue = fixture.value } - fixtureValueMap.set(fixture, fixtureValue) context![fixture.prop] = fixtureValue + fixtureValueMap.set(fixture, fixtureValue) cleanupFnArray.unshift(() => { fixtureValueMap.delete(fixture) }) From 6364cab60140e7a55b79f8acdf2cd7ca5b72759a Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 19:02:00 +0900 Subject: [PATCH 07/12] chore: comment --- packages/runner/src/fixture.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index 34550e8ddc5c..4b2c1b6bc82c 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -92,7 +92,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) { fixture.value(context, (useArg: unknown) => { resolveUseArg(useArg) isFixtureTeardown = true - // continue fixture function during cleanup + // suspend fixture function until cleanup return new Promise((resolveUseReturn) => { cleanupFnArray.push(resolveUseReturn) }) @@ -100,7 +100,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) { // re-throw if error is thrown during fixture teardown if (isFixtureTeardown) throw e - // otherwise it as a failure of a test calling this fixture + // otherwise treat it as a test failure which calls this fixture rejectUseArg(e) }) }) From f7dbb412d19998bce472e022789dbbae20bf2410 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 19:03:22 +0900 Subject: [PATCH 08/12] chore: remove fix for #4680 --- packages/runner/src/fixture.ts | 10 +--------- .../fixtures/test-extend/fixture-error.test.ts | 13 ------------- test/fails/test/__snapshots__/runner.test.ts.snap | 3 +-- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index 4b2c1b6bc82c..c11c0a2cd602 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -87,22 +87,14 @@ export function withFixtures(fn: Function, testContext?: TestContext) { let fixtureValue: unknown if (fixture.isFn) { // wait for `use` call to extract fixture value - let isFixtureTeardown = false fixtureValue = await new Promise((resolveUseArg, rejectUseArg) => { fixture.value(context, (useArg: unknown) => { resolveUseArg(useArg) - isFixtureTeardown = true // suspend fixture function until cleanup return new Promise((resolveUseReturn) => { cleanupFnArray.push(resolveUseReturn) }) - }).catch((e: unknown) => { - // re-throw if error is thrown during fixture teardown - if (isFixtureTeardown) - throw e - // otherwise treat it as a test failure which calls this fixture - rejectUseArg(e) - }) + }).catch(rejectUseArg) }) } else { diff --git a/test/fails/fixtures/test-extend/fixture-error.test.ts b/test/fails/fixtures/test-extend/fixture-error.test.ts index ae478bd93fad..4f815b9cae8d 100644 --- a/test/fails/fixtures/test-extend/fixture-error.test.ts +++ b/test/fails/fixtures/test-extend/fixture-error.test.ts @@ -50,16 +50,3 @@ describe('correctly fails when test times out', () => { expect(a).toBe(2) }, 20) }) - -describe('error thrown during fixture teardown', () => { - const myTest = test.extend<{ a: string }>({ - a: async ({}, use) => { - await use("hello"); - throw new Error('Error fixture teardown') - }, - }) - - myTest('fixture errors', ({ a }) => { - expect(a).toBe("hello"); - }) -}) diff --git a/test/fails/test/__snapshots__/runner.test.ts.snap b/test/fails/test/__snapshots__/runner.test.ts.snap index 5ee589c515ee..b628660bd975 100644 --- a/test/fails/test/__snapshots__/runner.test.ts.snap +++ b/test/fails/test/__snapshots__/runner.test.ts.snap @@ -43,8 +43,7 @@ TypeError: failure" exports[`should fail test-extend/circular-dependency.test.ts > test-extend/circular-dependency.test.ts 1`] = `"Error: Circular fixture dependency detected: a <- b <- a"`; exports[`should fail test-extend/fixture-error.test.ts > test-extend/fixture-error.test.ts 1`] = ` -"Error: Error fixture teardown -Error: Test timed out in 20ms. +"Error: Test timed out in 20ms. Error: Error thrown in test fixture Error: Error thrown in afterEach fixture Error: Error thrown in beforeEach fixture" From 1da458e9b030fec02e7731847054088b242d937c Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 6 Dec 2023 19:14:06 +0900 Subject: [PATCH 09/12] chore: comment --- packages/runner/src/fixture.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index c11c0a2cd602..d1695de618f9 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -94,7 +94,7 @@ export function withFixtures(fn: Function, testContext?: TestContext) { return new Promise((resolveUseReturn) => { cleanupFnArray.push(resolveUseReturn) }) - }).catch(rejectUseArg) + }).catch(rejectUseArg) // treat fixture function error (before `use` call) as test failure }) } else { From 2839171f88a001feeee40a83f4e451d58f4ecc29 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 7 Dec 2023 09:11:33 +0900 Subject: [PATCH 10/12] refactor: minor --- packages/runner/src/fixture.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index d1695de618f9..9fdcfe074efc 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -84,24 +84,24 @@ export function withFixtures(fn: Function, testContext?: TestContext) { if (fixtureValueMap.has(fixture)) continue - let fixtureValue: unknown + let resolvedValue: unknown if (fixture.isFn) { // wait for `use` call to extract fixture value - fixtureValue = await new Promise((resolveUseArg, rejectUseArg) => { + resolvedValue = await new Promise((resolveUseArg, rejectUseArg) => { fixture.value(context, (useArg: unknown) => { resolveUseArg(useArg) // suspend fixture function until cleanup return new Promise((resolveUseReturn) => { cleanupFnArray.push(resolveUseReturn) }) - }).catch(rejectUseArg) // treat fixture function error (before `use` call) as test failure + }).catch(rejectUseArg) // treat fixture function error (until `use` call) as test failure }) } else { - fixtureValue = fixture.value + resolvedValue = fixture.value } - context![fixture.prop] = fixtureValue - fixtureValueMap.set(fixture, fixtureValue) + context![fixture.prop] = resolvedValue + fixtureValueMap.set(fixture, resolvedValue) cleanupFnArray.unshift(() => { fixtureValueMap.delete(fixture) }) From e613a864cd79d9ac9117fcab913cbb1c248ac9c3 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 7 Dec 2023 09:12:07 +0900 Subject: [PATCH 11/12] test: fixture only in beforeEach/afterEach --- test/core/test/test-extend.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/core/test/test-extend.test.ts b/test/core/test/test-extend.test.ts index de191aa10911..3e2e4a24ba75 100644 --- a/test/core/test/test-extend.test.ts +++ b/test/core/test/test-extend.test.ts @@ -177,6 +177,28 @@ describe('test.extend()', () => { }) }) + describe('fixture only in beforeEach', () => { + beforeEach(({ todoList }) => { + expect(todoList).toEqual([1, 2, 3]) + expect(todoFn).toBeCalledTimes(1) + }) + + myTest('no fixture in test', () => { + expect(todoFn).toBeCalledTimes(1) + }) + }) + + describe('fixture only in afterEach', () => { + afterEach(({ todoList }) => { + expect(todoList).toEqual([1, 2, 3]) + expect(todoFn).toBeCalledTimes(1) + }) + + myTest('no fixture in test', () => { + expect(todoFn).toBeCalledTimes(0) + }) + }) + describe('fixture call times', () => { const apiFn = vi.fn(() => true) const serviceFn = vi.fn(() => true) @@ -203,6 +225,8 @@ describe('test.extend()', () => { beforeEach(({ api, service }) => { expect(api).toBe(true) expect(service).toBe(true) + expect(apiFn).toBeCalledTimes(1) + expect(serviceFn).toBeCalledTimes(1) }) testAPI('Should init 1 time', ({ api }) => { From 361e7a7c2e804b1a1481cb2eca231a3b10fd3e38 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 7 Dec 2023 17:53:55 +0900 Subject: [PATCH 12/12] refactor: replace Promise with createDefer --- packages/runner/src/fixture.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/runner/src/fixture.ts b/packages/runner/src/fixture.ts index 9fdcfe074efc..c210b09df1e7 100644 --- a/packages/runner/src/fixture.ts +++ b/packages/runner/src/fixture.ts @@ -1,3 +1,4 @@ +import { createDefer } from '@vitest/utils' import { getFixture } from './map' import type { TestContext } from './types' @@ -87,15 +88,15 @@ export function withFixtures(fn: Function, testContext?: TestContext) { let resolvedValue: unknown if (fixture.isFn) { // wait for `use` call to extract fixture value - resolvedValue = await new Promise((resolveUseArg, rejectUseArg) => { - fixture.value(context, (useArg: unknown) => { - resolveUseArg(useArg) - // suspend fixture function until cleanup - return new Promise((resolveUseReturn) => { - cleanupFnArray.push(resolveUseReturn) - }) - }).catch(rejectUseArg) // treat fixture function error (until `use` call) as test failure - }) + const useFnArgPromise = createDefer() + fixture.value(context, async (useFnArg: unknown) => { + useFnArgPromise.resolve(useFnArg) + // suspend fixture teardown until cleanup + const teardownPromise = createDefer() + cleanupFnArray.push(teardownPromise.resolve) + await teardownPromise + }).catch(useFnArgPromise.reject) // treat fixture function error (until `use` call) as test failure + resolvedValue = await useFnArgPromise } else { resolvedValue = fixture.value