Imported from anaghatestuser/Ghost (
e2e/AGENTS.md). Install upstream withnpx skills add anaghatestuser/Ghost --skill e2e. Copyright stays with the author.
AGENTS.md
E2E testing guidance for AI assistants (Claude, Codex, etc.) working with Ghost tests.
IMPORTANT: When creating or modifying E2E tests, always refer to .claude/E2E_TEST_WRITING_GUIDE.md for comprehensive testing guidelines and patterns.
Critical Rules
- Always follow ADRs in
../adr/folder (ADR-0001: AAA pattern, ADR-0002: Page Objects) - Always use yarn, never npm
- Always run after changes:
yarn lintandyarn test:types - Never use CSS/XPath selectors - only semantic locators or data-testid
- Prefer less comments and giving things clear names
Essential Commands
yarn test # Run all tests
yarn test tests/path/to/test.ts # Run specific test
yarn lint # Required after writing tests
yarn test:types # Check TypeScript errors
yarn build # Required after factory changes
yarn test --debug # See browser during execution, for debugging
PRESERVE_ENV=true yarn test # Debug failed tests (keeps containers)
Dev Environment Mode (Recommended)
When yarn dev is running, e2e tests automatically use a more efficient execution mode:
# Terminal 1: Start dev environment
yarn dev
# Terminal 2: Run e2e tests (automatically uses dev environment)
cd e2e && yarn test
Test Structure
Naming Conventions
- Test suites:
Ghost Admin - FeatureorGhost Public - Feature - Test names:
what is tested - expected outcome(lowercase) - One test = one scenario (never mix multiple scenarios)
AAA Pattern
test('action performed - expected result', async ({page}) => {
const analyticsPage = new AnalyticsGrowthPage(page);
const postFactory = createPostFactory(page.request);
const post = await postFactory.create({status: 'published'});
await analyticsPage.goto();
await analyticsPage.topContent.postsButton.click();
await expect(analyticsPage.topContent.contentCard).toContainText('No conversions');
});
Page Objects
Structure
export class AnalyticsPage extends AdminPage {
// Public readonly locators only
public readonly saveButton = this.page.getByRole('button', {name: 'Save'});
public readonly emailInput = this.page.getByLabel('Email');
// Semantic action methods
async saveSettings() {
await this.saveButton.click();
}
}
Rules
- Page Objects are located in
helpers/pages/ - Expose locators as
public readonlywhen used with assertions - Methods use semantic names (
login()notclickLoginButton()) - Use
waitFor()for guards, neverexpect()in page objects - Keep all assertions in test files
Locators (Strict Priority)
-
Semantic (always prefer):
getByRole('button', {name: 'Save'})getByLabel('Email')getByText('Success')
-
Test IDs (when semantic unavailable):
getByTestId('analytics-card')- Suggest adding
data-testidto Ghost codebase when needed
-
Never use: CSS selectors, XPath, nth-child, class names
Playwright MCP Usage
- Use
mcp__playwright__browser_snapshotto find elements - Use
mcp__playwright__browser_clickwith semantic descriptions - If no good locator exists, suggest
data-testidaddition to Ghost
Test Data
Factory Pattern (Required)
import {PostFactory, UserFactory} from '../data-factory';
const postFactory = createPostFactory(page.request);
const post = await postFactory.create({userId: user.id});
Best Practices
DO ✅
- Each test gets fresh Ghost instance (automatic isolation)
- Use factories for all test data
- Use Playwright's auto-waiting
- Run tests multiple times to ensure stability
- Use
test.only()for debugging single tests
DON'T ❌
- Hard-coded waits (
waitForTimeout) - networkidle in waits** (
networkidle) - Test dependencies (Test B needs Test A)
- Direct database manipulation
- Multiple scenarios in one test
- Assertions in page objects
- Manual login (auto-authenticated via fixture)
Project Structure
tests/admin/- Admin area teststests/public/- Public site testshelpers/pages/- Page objectshelpers/environment/- Container managementdata-factory/- Test data factories
Validation Checklist
After writing tests, verify:
- Test passes:
yarn test path/to/test.ts - Linting passes:
yarn lint - Types check:
yarn test:types - Follows AAA pattern with clear sections
- Uses page objects appropriately
- Uses semantic locators or data-testid only
- No hard-coded waits or CSS selectors
