AGENTS.md
packages/testing/playwright/AGENTS.mdAGENTS.md
Quality
96/100
Scores the file, not the repository.Length
1,908 words
48 headings · 21 code blocksRepository
199k
— · pushed 0 days agoLast changed
3 days ago
First indexed 3 days ago.1# AGENTS.md23## Commands45```bash6# Run tests locally7pnpm --filter=n8n-playwright test:local <file-path>8pnpm --filter=n8n-playwright test:local tests/e2e/credentials/crud.spec.ts910# Run with container capabilities (requires pnpm build:docker first)11pnpm --filter=n8n-playwright test:container:sqlite --grep @capability:email1213# Lint and typecheck14pnpm --filter=n8n-playwright lint15pnpm --filter=n8n-playwright typecheck16```1718Always trim output: `--reporter=list 2>&1 | tail -50`1920## Test Maintenance (Janitor)2122Static analysis for Playwright test architecture. Catches problems before they spread.2324> **CRITICAL: Always use TCR for code changes.**25> When janitor identifies violations and you fix them, use `pnpm janitor tcr --execute` to safely commit. Never manually commit janitor-related fixes - TCR ensures tests pass before the commit lands.2627### Golden Rules28291. **Analysis only?** Run `pnpm janitor` (no TCR needed)302. **Making code changes?** Use TCR: `pnpm janitor tcr --execute -m="chore: ..."`313. **Never** manually `git commit` janitor-related fixes - always go through TCR324. **Never** modify `.janitor-baseline.json` via TCR - baseline updates must be done manually3334### When to Use3536| User Says | Intent | Approach |37|-----------|--------|----------|38| "Clean up the test codebase" | Incremental cleanup | Create baseline first, then use `--max-diff-lines=500` for small PRs. |39| "Start tracking violations" | Enable incremental cleanup | Run `janitor baseline` to snapshot current state, commit `.janitor-baseline.json`. |40| "Add a test for X" | New test following patterns | After writing, run janitor to verify architecture compliance. |41| "Fix architecture drift" | Enforce layered architecture | Run `selector-purity` and `no-page-in-flow` rules. |42| "Find dead code" | Remove unused methods | Run `dead-code` rule with `--fix --write` for auto-removal. |43| "Find copy-paste code" | Detect duplicates | Run `duplicate-logic` rule to find structural duplicates. |44| "This file is messy" | Targeted cleanup | Analyze specific file, fix issues, TCR to safely commit. |45| "Refactor this page object" | Safe refactoring | Use TCR - changes commit if tests pass, revert if they fail. |46| "What tests would break?" | Impact analysis | Run `impact` command before changing shared code. |47| "Prepare for PR" | Pre-commit check | Run janitor on changed files to catch violations early. |4849### Architecture Rules5051The janitor enforces a layered architecture:5253```54Tests → Flows/Composables → Page Objects → Components → Playwright API55```5657| Rule | What It Catches |58|------|-----------------|59| `selector-purity` | Raw locators in tests/flows: `page.getByTestId()`, `someLocator.locator()` |60| `no-page-in-flow` | Flows accessing `page` directly (should use page objects) |61| `boundary-protection` | Pages importing other pages (creates coupling) |62| `scope-lockdown` | Unscoped locators that escape their container |63| `dead-code` | Unused public methods in page objects |64| `deduplication` | Same selector defined in multiple files |65| `duplicate-logic` | Copy-pasted code across tests/pages (AST fingerprinting) |66| `no-raw-editor-navigation` | Raw `page.goto()` to a `/workflow/` editor route in tests (use `n8n.start.*` so the canvas loader is awaited) |67| `valid-owner-annotation` | A spec with no team owner, or an owner not in the canonical list (`CANONICAL_OWNERS` in the rule, mirroring Notion "Ownership v2") |6869### Commands7071```bash72# Analyze entire codebase73pnpm janitor7475# Analyze specific file76pnpm janitor --file=pages/CanvasPage.ts --verbose7778# Run specific rule79pnpm janitor --rule=dead-code8081# Auto-fix (dead-code only)82pnpm janitor:fix --rule=dead-code8384# List all rules (short)85pnpm janitor --list8687# Show detailed rule info (for AI agents)88pnpm janitor rules --json8990# Discover test specs (for orchestration)91pnpm janitor discover9293# Distribute specs across shards94pnpm janitor orchestrate --shards=1495```9697### Baseline (Incremental Cleanup)9899For codebases with existing violations, create a baseline to enable incremental cleanup:100101```bash102# Create baseline - snapshots current violations103pnpm janitor baseline104105# Commit the baseline106git add .janitor-baseline.json && git commit -m "chore: add janitor baseline"107```108109Once baseline exists, janitor and TCR **only fail on NEW violations**. Pre-existing violations are tracked but don't block work.110111> **Safeguard:** TCR blocks commits that modify `.janitor-baseline.json`. This prevents accidentally "fixing" violations by updating the baseline instead of the actual code. Baseline updates must be done manually after fixing violations.112113```bash114# Update baseline after fixing violations (manual commit required)115pnpm janitor baseline116git add .janitor-baseline.json && git commit -m "chore: update baseline after cleanup"117```118119### Incremental Cleanup Strategy120121For large cleanups, keep diffs small and reviewable:122123```bash124# Show ALL violations (ignoring baseline) for cleanup work125pnpm janitor --ignore-baseline --json126127# Find easiest files to fix (lowest violation count)128pnpm janitor --ignore-baseline --json 2>/dev/null | jq '.fileReports | sort_by(.violationCount) | .[:5]'129130# TCR with max diff size (skip if changes are too large)131pnpm janitor tcr --max-diff-lines=500 --execute -m="chore: cleanup"132```133134**AI Cleanup Workflow:**1351. Use `--ignore-baseline` to see all violations (not just new ones)1362. Pick small fixes from the list1373. Fix violations, then TCR to safely commit1384. After fixing, run `pnpm janitor baseline` to update the baseline139140### TCR Workflow (Test && Commit || Revert)141142Safe refactoring loop: make changes, run affected tests, auto-commit or auto-revert.143144```bash145# Dry run - see what would happen146pnpm janitor tcr --verbose147148# Execute - actually commit/revert149pnpm janitor tcr --execute -m="chore: remove dead code"150151# With guardrails - skip if diff too large152pnpm janitor tcr --execute --max-diff-lines=500 -m="chore: cleanup"153```154155### After Writing New Tests156157Always run janitor after adding or modifying tests to catch architecture violations early:158159```bash160pnpm janitor --file=tests/my-new-test.spec.ts --verbose161```162163See `packages/testing/janitor/README.md` for full documentation.164165## Entry Points166167All tests should start with `n8n.start.*` methods. See `composables/TestEntryComposer.ts`.168169| Method | Use Case |170|--------|----------|171| `fromHome()` | Start from home page |172| `fromBlankCanvas()` | New workflow from scratch |173| `fromNewProjectBlankCanvas()` | Project-scoped workflow (returns projectId) |174| `fromNewProject()` | Project-scoped test, no canvas (returns projectId) |175| `fromImportedWorkflow(file)` | Test pre-built workflow JSON |176| `withUser(user)` | Isolated browser context per user |177| `withProjectFeatures()` | Enable sharing/folders/permissions |178179## Test Isolation180181Tests run in parallel. Design tests to be fully isolated so they don't interfere with each other.182183### Unique Identifiers184185Use `nanoid` for unique test data:186187```typescript188const credentialName = `Test Credential ${nanoid()}`;189const workflow = await api.workflows.createWorkflow({190 name: `Test Workflow ${nanoid()}`,191});192```193194### Dynamic User Creation195196Create users dynamically via the public API:197198```typescript199const member = await api.publicApi.createUser({200 email: `member-${nanoid()}@test.com`,201 firstName: 'Test',202 lastName: 'Member',203});204```205206### Isolated Browser Contexts207208For UI tests requiring multiple users, create isolated browser contexts:209210```typescript211// 1. Create users via public API212const member1 = await api.publicApi.createUser({ role: 'global:member' });213const member2 = await api.publicApi.createUser({ role: 'global:member' });214215// 2. Get isolated browser contexts216const member1Page = await n8n.start.withUser(member1);217const member2Page = await n8n.start.withUser(member2);218219// 3. Each operates independently (no session bleeding)220await member1Page.navigate.toWorkflows();221await member2Page.navigate.toCredentials();222```223224**Reference:** `tests/e2e/building-blocks/user-service.spec.ts`225226| Pattern | Why | Use Instead |227|---------|-----|-------------|228| `test.describe.serial` | Creates test dependencies | Parallel tests with isolated setup |229| Fresh DB per file | Tests need isolated container | `test.use({ capability: { env: { TEST_ISOLATION: 'name' } } })` |230| Fresh DB per test | Tests modify shared state | `@db:reset` tag on describe (container-only, combined with `test.use()`) |231| `n8n.api.signin()` | Session bleeding | `n8n.start.withUser()` |232| `Date.now()` for IDs | Race conditions | `nanoid()` |233| `waitForTimeout()` | Flaky | `waitForResponse()`, `toBeVisible()` |234| `.toHaveCount(N)` | Brittle | Named element assertions |235| Raw `page.goto()` | Bypasses setup | `n8n.navigate.*` methods |236237## Code Style238239- Use specialized locators: `page.getByRole('button')` over `page.locator('[role=button]')`240- Use `nanoid()` for unique identifiers (parallel-safe)241- API setup over UI setup when possible (faster, more reliable)242243## Architecture244245```246Tests (*.spec.ts)247 ↓ uses248Composables (*Composer.ts) - Multi-step business workflows249 ↓ orchestrates250Page Objects (*Page.ts) - UI interactions251 ↓ extends252BasePage - Common utilities253```254255See `CONTRIBUTING.md` for detailed patterns and conventions.256257## Debugging258259See [README.md#debugging](./README.md#debugging) for detailed instructions on:260- **Keepalive mode** - Keep containers running after tests with `N8N_CONTAINERS_KEEPALIVE=true`261- **Victoria exports** - Logs/metrics automatically attached on failure, importable locally via `scripts/import-victoria-data.mjs`262263## Test Migration & Refactoring264265**Test Name = Contract**266- Name declares intent, assertion proves it, everything else is flow267- Bad: `should open W1 as U2` (describes action)268- Good: `should allow sharee to edit shared workflow` (declares rule)269270**Coverage Parity Check**2711. Read old test name → what was the intent?2722. Find the explicit assertion that proved it2733. Verify new test has equivalent proof2744. No proof found? Document as intentional drop or gap275276**Legacy Tests (unauditable names/assertions)**277- Prioritize clarity over parity - can't audit what you can't read278- Document your best interpretation of intent279- Accept short-term risk, fix regressions forward280281See [Quality Corner: Test Migration Guide](https://www.notion.so/n8n/Best-Practices-Test-Migration-Refactoring) for full rationale and examples.282283## Reference Files284285| Purpose | File |286|---------|------|287| Multi-user testing | `tests/e2e/building-blocks/user-service.spec.ts` |288| Entry points | `composables/TestEntryComposer.ts` |289| Page object example | `pages/CanvasPage.ts` |290| Composable example | `composables/WorkflowComposer.ts` |291| API helpers | `services/api-helper.ts` |292| Capabilities | `fixtures/capabilities.ts` |293294```typescript295const member = await api.publicApi.createUser({...});296const memberN8n = await n8n.start.withUser(member);297298await memberN8n.navigate.toWorkflows();299await expect(memberN8n.workflows.cards.getWorkflow(workflowName)).toBeVisible();300```301### Isolated API Contexts302303For API-only operations as another user, create isolated API contexts (no browser needed):304305```typescript306const member = await api.publicApi.createUser({...});307const memberApi = await api.createApiForUser(member);308309const memberProject = await memberApi.projects.getMyPersonalProject();310await memberApi.credentials.createCredential({...});311```312313### Identity-Based Assertions314315Assert by identity (name) rather than count for parallel-safe tests:316317```typescript318await expect(credentialDropdown.getByText(testCredName)).toBeVisible();319await expect(credentialDropdown.getByText(devCredName)).toBeHidden();320```321322## Worker Isolation (Fresh Database)323324Use `test.use()` at file top-level with unique capability config:325326```typescript327// my-isolated-tests.spec.ts328import { test, expect } from '../fixtures/base';329330// Must be top-level, not inside describe block331test.use({ capability: { env: { TEST_ISOLATION: 'my-isolated-tests' } } });332333test('test with clean state', async ({ n8n }) => {334 // Fresh container with reset database335});336```337338For per-test database reset (when tests modify shared state like MFA), add `@db:reset` to the describe. **Note:** `@db:reset` is container-only - these tests won't run locally.339340```typescript341test.use({ capability: { env: { TEST_ISOLATION: 'my-stateful-tests' } } });342343test.describe('My stateful tests @db:reset', () => {344 // Each test gets a fresh database reset (container-only)345});346```347348## Data Setup349350Use API helpers for fast, reliable test data setup. Reserve UI interactions for testing UI behavior:351352```typescript353// API for data setup354const credential = await api.credentials.createCredential({355 name: `Test Credential ${nanoid()}`,356 type: 'notionApi',357 data: { apiKey: 'test' },358});359360const workflow = await api.workflows.createWorkflow({361 name: `Test Workflow ${nanoid()}`,362 nodes: [...],363});364365// UI for verification366await n8n.navigate.toCredentials();367await expect(n8n.credentials.cards.getCredential(credential.name)).toBeVisible();368```369370## Feature Enablement371372The `n8n` fixture automatically enables project features. For API-only tests (no `n8n` fixture), enable features explicitly:373374```typescript375test('API-only test', async ({ api }) => {376 await api.enableProjectFeatures();377 // ...378});379```380381### Feature Flag Overrides382383To test features behind feature flags (experiments), use `TestRequirements` with storage overrides:384385```typescript386import type { TestRequirements } from '../config/TestRequirements';387388const requirements: TestRequirements = {389 storage: {390 N8N_EXPERIMENT_OVERRIDES: JSON.stringify({ 'your_experiment': true }),391 },392};393394test.use({ requirements });395396test('test with feature flag enabled', async ({ n8n }) => {397 // Feature flag is now active for this test398});399```400401**Common patterns:**402403```typescript404// Single experiment405{ storage: { N8N_EXPERIMENT_OVERRIDES: JSON.stringify({ '025_new_canvas': true }) } }406407// Multiple experiments408{ storage: { N8N_EXPERIMENT_OVERRIDES: JSON.stringify({409 '025_new_canvas': true,410 '026_another_feature': 'variant_a'411}) } }412413// Combined with other requirements414const requirements: TestRequirements = {415 storage: {416 N8N_EXPERIMENT_OVERRIDES: JSON.stringify({ 'your_experiment': true }),417 },418 capability: {419 env: { TEST_ISOLATION: 'my-test-suite' },420 },421};422```423424**Reference:** `config/TestRequirements.ts` for full interface definition.425426## Shard Rebalancing427428When refactoring, adding, or moving significant numbers of tests, consider rebalancing test shards to maintain even CI distribution. See `docs/ORCHESTRATION.md` for details.429
Also in n8n-io/n8n
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| n8n-io/n8n.agents/skills/AGENTS.md · 199k | AGENTS.md | setuparchagent-behaviour | 58/100 | 3 days ago | |
| n8n-io/n8n.github/CLAUDE.md · 199k | CLAUDE.md | styleagent-behaviour | 48/100 | 3 days ago | |
| n8n-io/n8nAGENTS.md · 199k | AGENTS.md | setupbuildtestlint-format+8 | 96/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/ai-workflow-builder.ee/AGENTS.md · 199k | AGENTS.md | agent-behaviour | 53/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/db/AGENTS.md · 199k | AGENTS.md | database | 39/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/engine/AGENTS.md · 199k | AGENTS.md | archdo-not | 59/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/instance-ai/CLAUDE.md · 199k | CLAUDE.md | buildteststyletesting-strategy+1 | 89/100 | 3 days ago | |
| n8n-io/n8npackages/cli/AGENTS.md · 199k | AGENTS.md | lint-format | 55/100 | 3 days ago | |
| n8n-io/n8npackages/cli/src/modules/n8n-packages/CLAUDE.md · 199k | CLAUDE.md | stylearchdependenciesmonorepo+2 | 69/100 | 3 days ago | |
| n8n-io/n8npackages/frontend/AGENTS.md · 199k | AGENTS.md | style | 40/100 | 3 days ago | |
| n8n-io/n8npackages/frontend/editor-ui/src/app/stores/workflowDocument/CLAUDE.md · 199k | CLAUDE.md | styleagent-behaviour | 58/100 | 3 days ago | |
| n8n-io/n8npackages/nodes-base/AGENTS.md · 199k | AGENTS.md | teststylearchtypes+5 | 89/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| n8n-io/n8nscripts/instance-seeding/AGENTS.md · 199k | AGENTS.md | setupstyledo-not | 65/100 | 3 days ago |
Diff against .agents/skills/AGENTS.md Diff against .github/CLAUDE.md Diff against AGENTS.md Diff against packages/@n8n/ai-workflow-builder.ee/AGENTS.md Diff against packages/@n8n/db/AGENTS.md Diff against packages/@n8n/engine/AGENTS.md Diff against packages/@n8n/instance-ai/CLAUDE.md Diff against packages/cli/AGENTS.md Diff against packages/cli/src/modules/n8n-packages/CLAUDE.md Diff against packages/frontend/AGENTS.md Diff against packages/frontend/editor-ui/src/app/stores/workflowDocument/CLAUDE.md Diff against packages/nodes-base/AGENTS.md Diff against packages/@n8n/agents/AGENTS.md Diff against scripts/instance-seeding/AGENTS.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | 3 days ago | |
| elastic/elasticsearchx-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/AGENTS.md · 78k | AGENTS.md | buildtestlint-formatstyle+2 | 100/100 | 3 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 3 days ago | |
| aaif-goose/gooseAGENTS.md · 52k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 199k | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 67k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 2 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 3 days ago | |
| elastic/elasticsearchx-pack/plugin/inference/AGENTS.md · 78k | AGENTS.md | buildtestlint-formatstyle+3 | 100/100 | 3 days ago |
