Ghost Core's server end-to-end framework provides one place to access request
agents, fixtures, mocks, and snapshot matchers. These are Vitest suites which
exercise Ghost directly; they are separate from the Playwright browser suite in
the repository's e2e/ directory.
const {agentProvider, fixtureManager, mockManager, matchers} =
require('../utils/e2e-framework');
let agent;
beforeAll(async function () {
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init('members');
await agent.loginAsOwner();
});The framework provides agents for the Admin, Content, and Members APIs. Agents boot Ghost with suitable defaults, reset the database, configure the API base path, and provide authentication helpers.
Requests use async/await. Assert the response status, body, and relevant headers. Use the snapshot matchers for generated IDs, dates, ETags, and locations, and add focused assertions for important behavior such as ordering and side effects.
Use fixtureManager.init() to insert fixture tasks in a known state. The
framework includes the owner fixture by default. Named tasks add the data
required by a test; for example, users creates staff users for each role and
user:inactive adds an inactive user.
Older tests may use localUtils.doAuth() or testUtils.setup(), but new
end-to-end tests should use the fixture manager and request agents.
mockManager handles external effects. Use mockManager.mockMail() to inspect
outgoing email and match HTML, plain text, and metadata snapshots. Restore mocks
after each test.
The webhook mock receiver follows the same pattern for outgoing webhooks.
Real network access is disabled when the framework boots. Use a focused mock or
an existing mockManager helper for external services.
When a test uses Nock directly, match the expected method and path and inspect
the request body when it is part of the contract. When a path contains a dynamic
value, constrain the matcher to the expected path and value format rather than
using a catch-all expression. Use .times() when the number of requests matters.
Avoid .persist() unless the behaviour genuinely makes an open-ended number of
calls; persistent interceptors can hide unexpected requests. Use
nock.cleanAll() to remove direct Nock interceptors after the test, or
mockManager.restore() when resetting framework-managed state. Do not use
nock.restore() for cleanup: it disables interception and requires
nock.activate() before Nock can intercept another request. Use Nock's pending
mocks to diagnose an expectation that was never met.
Request agents provide matchBodySnapshot() and matchHeaderSnapshot(). Use
the most specific matcher available for values that change between runs, such
as object IDs, UUIDs, dates, ETags, and resource locations.
To update a snapshot for one test, run:
SNAPSHOT_UPDATE=1 pnpm test:single test/e2e-api/path/to/test.test.jsReview every changed .snap file before committing it. A generated snapshot is
not proof that the response is correct.
- Keep tests fast and make only the requests needed for the behavior.
- Use
expectEmptyBodyfor responses such as204. - Test side effects as well as the response.
- Give tests names that remain clear in snapshot files.
- Do not use the old test utilities in new tests.
From ghost:
pnpm test:e2e
pnpm test:single test/e2e-api/path/to/test.test.jsThe main groups are test/e2e-api/, test/e2e-frontend/,
test/e2e-webhooks/, and test/e2e-server/. Isolated tests use an
.isolated.test.js or .isolated.test.ts suffix inside test/e2e-server/.
Start from a nearby test in the same group because boot options, agent choice,
and cleanup requirements vary by boundary.