Files
DuckQ1u 93d1b7c3d3
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
first commit
2026-04-22 19:51:20 +07:00

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