TryGhost/Ghost · error · Error

[e2e fixture] resetEnvironment() must be called before resol

Error message

[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.

What it means

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.

Source

Thrown at e2e/helpers/playwright/fixture.ts:513

        await use(_testEnvironmentContext.holder);
    },

    resolvedIsolation: async ({_testEnvironmentContext}, use) => {
        await use(_testEnvironmentContext.resolvedIsolation);
    },

    resetEnvironment: async ({_testEnvironmentContext}, use) => {
        await use(async () => {
            if (_testEnvironmentContext.resolvedIsolation === 'per-test') {
                debug('resetEnvironment() is a no-op in per-test isolation mode');
                return;
            }

            // Only support resetEnvironment() before stateful fixtures such as the
            // baseURL, authenticated user session, or page have been materialized.
            const blocker = _testEnvironmentContext.getResetEnvironmentBlocker();
            if (blocker) {
                throw new Error(
                    `[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.'
                );
            }

            await _testEnvironmentContext.cycle();
        });
    },

    stripe: async ({stripeEnabled, baseURL, stripeServer}, use) => {
        if (!stripeEnabled || !baseURL || !stripeServer) {
            await use(undefined);
            return;
        }

        const webhookClient = new WebhookClient(baseURL);
        const service = new StripeTestService(stripeServer, webhookClient);

View on GitHub (pinned to 47d8b0e2ad)

Solutions

  1. Move resetEnvironment() into a beforeEach whose only fixture dependencies are resetEnvironment and fixtures valid after a recycle (none of baseURL/page/ghostAccountOwner).
  2. Split the hook: one beforeEach for resetEnvironment, a separate one (or in-test) for stateful fixtures.
  3. Use per-test isolation if you need fresh state per test instead of manual resets.
  4. Read the blocker name in the message to find which fixture was already resolved and remove that dependency from the reset hook.

Example fix

// before
beforeEach(async ({resetEnvironment, baseURL, page}) => {
    await resetEnvironment();
    await page.goto(baseURL);
});

// after
test.beforeEach(async ({resetEnvironment}) => {
    await resetEnvironment();
});

test('...', async ({baseURL, page}) => {
    await page.goto(baseURL);
});
Defensive patterns

Strategy: validation

Validate before calling

if (_testEnvironmentContext.getResetEnvironmentBlocker()) {
    throw new Error(`resetEnvironment blocked by ${_testEnvironmentContext.getResetEnvironmentBlocker()}`);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of TryGhost/Ghost@47d8b0e2ad (2026-08-13). Data as JSON: /api/errors/5492090c7a4957aa. Report an issue: GitHub.