{"record":{"id":"5492090c7a4957aa","repo":"TryGhost/Ghost","slug":"e2e-fixture-resetenvironment-must-be-called-be","errorCode":null,"errorMessage":"[e2e fixture] resetEnvironment() must be called before resolving \"${blocker}\". Use it in a beforeEach hook that only depends on resetEnvironment and fixtures that remain valid after a recycle.","messagePattern":"\\[e2e fixture\\] resetEnvironment\\(\\) must be called before resolving \"(.+?)\"\\. Use it in a beforeEach hook that only depends on resetEnvironment and fixtures that remain valid after a recycle\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"e2e/helpers/playwright/fixture.ts","lineNumber":513,"sourceCode":"        await use(_testEnvironmentContext.holder);\n    },\n\n    resolvedIsolation: async ({_testEnvironmentContext}, use) => {\n        await use(_testEnvironmentContext.resolvedIsolation);\n    },\n\n    resetEnvironment: async ({_testEnvironmentContext}, use) => {\n        await use(async () => {\n            if (_testEnvironmentContext.resolvedIsolation === 'per-test') {\n                debug('resetEnvironment() is a no-op in per-test isolation mode');\n                return;\n            }\n\n            // Only support resetEnvironment() before stateful fixtures such as the\n            // baseURL, authenticated user session, or page have been materialized.\n            const blocker = _testEnvironmentContext.getResetEnvironmentBlocker();\n            if (blocker) {\n                throw new Error(\n                    `[e2e fixture] resetEnvironment() must be called before resolving ` +\n                    `\"${blocker}\". Use it in a beforeEach hook that only depends on ` +\n                    'resetEnvironment and fixtures that remain valid after a recycle.'\n                );\n            }\n\n            await _testEnvironmentContext.cycle();\n        });\n    },\n\n    stripe: async ({stripeEnabled, baseURL, stripeServer}, use) => {\n        if (!stripeEnabled || !baseURL || !stripeServer) {\n            await use(undefined);\n            return;\n        }\n\n        const webhookClient = new WebhookClient(baseURL);\n        const service = new StripeTestService(stripeServer, webhookClient);","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/TryGhost/Ghost/blob/47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe/e2e/helpers/playwright/fixture.ts#L495-L531","documentation":"resetEnvironment() recycles the Ghost instance but is only valid before stateful fixtures (baseURL, ghostAccountOwner, page) materialize — those capture references invalidated by a recycle. _testEnvironmentContext tracks a 'blocker' (the name of the first materialized stateful fixture); if resetEnvironment() is called after one resolves, it throws naming the blocker. This is a usage-contract violation, not an infrastructure failure.","triggerScenarios":"A test's beforeEach depends on both resetEnvironment and baseURL/page/authenticated session. resetEnvironment() called inside or after a hook that already pulled baseURL. Fixture dependency graph places resetEnvironment downstream of a stateful fixture.","commonSituations":"Refactoring tests and adding resetEnvironment() to a beforeEach that already transitively depends on baseURL; mixing per-test resets with per-file isolation incorrectly; copying a hook pattern without checking fixture deps.","solutions":["Move resetEnvironment() into a beforeEach whose only fixture dependencies are resetEnvironment and fixtures valid after a recycle (none of baseURL/page/ghostAccountOwner).","Split the hook: one beforeEach for resetEnvironment, a separate one (or in-test) for stateful fixtures.","Use per-test isolation if you need fresh state per test instead of manual resets.","Read the blocker name in the message to find which fixture was already resolved and remove that dependency from the reset hook."],"exampleFix":"// before\nbeforeEach(async ({resetEnvironment, baseURL, page}) => {\n    await resetEnvironment();\n    await page.goto(baseURL);\n});\n\n// after\ntest.beforeEach(async ({resetEnvironment}) => {\n    await resetEnvironment();\n});\n\ntest('...', async ({baseURL, page}) => {\n    await page.goto(baseURL);\n});","handlingStrategy":"validation","validationCode":"if (_testEnvironmentContext.getResetEnvironmentBlocker()) {\n    throw new Error(`resetEnvironment blocked by ${_testEnvironmentContext.getResetEnvironmentBlocker()}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep resetEnvironment in a beforeEach that depends only on resetEnvironment (and recycle-safe fixtures).","Never mix resetEnvironment with baseURL/page/ghostAccountOwner in the same hook.","Prefer per-test isolation over manual resets when you need fresh state per test."],"tags":["e2e","fixture","playwright","lifecycle","usage-contract"],"backgroundTag":null,"analyzedSha":"47d8b0e2ad2fd4757d3bc45f46c3ac165ff8a1fe","analyzedAt":"2026-08-13T01:25:26.651Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}