151 lines
5.7 KiB
Markdown
151 lines
5.7 KiB
Markdown
# 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
|
|
1. **Always follow ADRs** in `../adr/` folder (ADR-0001: AAA pattern, ADR-0002: Page Objects)
|
|
2. **Always use pnpm**, never npm
|
|
3. **Always run after changes**: `pnpm lint` and `pnpm test:types`
|
|
4. **Never use CSS/XPath selectors** - only semantic locators or data-testid
|
|
5. **Prefer less comments and giving things clear names**
|
|
|
|
## Running E2E Tests
|
|
|
|
**`pnpm dev` must be running before you run E2E tests.** The E2E test runner auto-detects
|
|
whether the admin dev server is reachable at `http://127.0.0.1:5174`. If it is, tests run
|
|
in **dev mode** (fast, no pre-built Docker image required). If not, tests fall back to
|
|
**build mode** which requires a `ghost-e2e:local` Docker image that is only built in CI.
|
|
|
|
**If you see the error `Build image not found: ghost-e2e:local`, it means `pnpm dev` is
|
|
not running.** Start it first, wait for the admin dev server to be ready, then re-run tests.
|
|
|
|
```bash
|
|
# Terminal 1 (or background): Start dev environment from the repo root
|
|
pnpm dev
|
|
|
|
# Wait for the admin dev server to be reachable (http://127.0.0.1:5174)
|
|
|
|
# Terminal 2: Run e2e tests from the e2e/ directory
|
|
pnpm test # Run all tests
|
|
pnpm test tests/path/to/test.ts # Run specific test
|
|
pnpm lint # Required after writing tests
|
|
pnpm test:types # Check TypeScript errors
|
|
pnpm build # Required after factory changes
|
|
pnpm test --debug # See browser during execution, for debugging
|
|
PRESERVE_ENV=true pnpm test # Debug failed tests (keeps containers)
|
|
```
|
|
## Test Structure
|
|
|
|
### Naming Conventions
|
|
- **Test suites**: `Ghost Admin - Feature` or `Ghost Public - Feature`
|
|
- **Test names**: `what is tested - expected outcome` (lowercase)
|
|
- **One test = one scenario** (never mix multiple scenarios)
|
|
|
|
### AAA Pattern
|
|
```typescript
|
|
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
|
|
```typescript
|
|
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 readonly` when used with assertions
|
|
- Methods use semantic names (`login()` not `clickLoginButton()`)
|
|
- Use `waitFor()` for guards, never `expect()` in page objects
|
|
- Keep all assertions in test files
|
|
|
|
## Locators (Strict Priority)
|
|
|
|
1. **Semantic** (always prefer):
|
|
- `getByRole('button', {name: 'Save'})`
|
|
- `getByLabel('Email')`
|
|
- `getByText('Success')`
|
|
|
|
2. **Test IDs** (when semantic unavailable):
|
|
- `getByTestId('analytics-card')`
|
|
- Suggest adding `data-testid` to Ghost codebase when needed
|
|
|
|
3. **Never use**: CSS selectors, XPath, nth-child, class names
|
|
|
|
### Playwright MCP Usage
|
|
- Use `mcp__playwright__browser_snapshot` to find elements
|
|
- Use `mcp__playwright__browser_click` with semantic descriptions
|
|
- If no good locator exists, suggest `data-testid` addition to Ghost
|
|
|
|
## Test Data
|
|
|
|
### Factory Pattern (Required)
|
|
```typescript
|
|
import {PostFactory, UserFactory} from '../data-factory';
|
|
|
|
const postFactory = createPostFactory(page.request);
|
|
const post = await postFactory.create({userId: user.id});
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### DO ✅
|
|
- Use `usePerTestIsolation()` from `@/helpers/playwright/isolation` if a file needs per-test isolation
|
|
- Treat `config` and `labs` as environment-identity inputs: changing them should be an intentional part of test setup
|
|
- Use `resetEnvironment()` only in `beforeEach` hooks when you need a forced recycle inside per-file mode
|
|
- Keep `stripeEnabled` tests in per-test mode; the fixture forces this automatically
|
|
- 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 ❌
|
|
- Use `test.describe.parallel(...)` or `test.describe.serial(...)` in e2e tests
|
|
- Use nested `test.describe.configure({mode: ...})` (mode toggles are root-level only)
|
|
- Call `resetEnvironment()` after resolving `baseURL`, `page`, `pageWithAuthenticatedUser`, or `ghostAccountOwner`
|
|
- 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 tests
|
|
- `tests/public/` - Public site tests
|
|
- `helpers/pages/` - Page objects
|
|
- `helpers/environment/` - Container management
|
|
- `data-factory/` - Test data factories
|
|
|
|
## Validation Checklist
|
|
After writing tests, verify:
|
|
1. Test passes: `pnpm test path/to/test.ts`
|
|
2. Linting passes: `pnpm lint`
|
|
3. Types check: `pnpm test:types`
|
|
4. Follows AAA pattern with clear sections
|
|
5. Uses page objects appropriately
|
|
6. Uses semantic locators or data-testid only
|
|
7. No hard-coded waits or CSS selectors
|