Stabilize flaky Playwright tests by improving test reliability. This PR aims to identify and fix flaky e2e tests. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10817-test-stabilize-flaky-Playwright-tests-3366d73d365081ada40de73ce11af625) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com>
4.3 KiB
E2E Testing Guidelines
See @docs/guidance/playwright.md for Playwright best practices (auto-loaded for *.spec.ts).
See @browser_tests/FLAKE_PREVENTION_RULES.md when triaging or editing
flaky browser tests.
Directory Structure
browser_tests/
├── assets/ - Test data (JSON workflows, images)
├── fixtures/
│ ├── ComfyPage.ts - Main fixture (delegates to helpers)
│ ├── ComfyMouse.ts - Mouse interaction helper
│ ├── VueNodeHelpers.ts - Vue Nodes 2.0 helpers
│ ├── selectors.ts - Centralized TestIds
│ ├── data/ - Static test data (mock API responses, workflow JSONs, node definitions)
│ ├── components/ - Page object components (locators, user interactions)
│ │ ├── ContextMenu.ts
│ │ ├── SettingDialog.ts
│ │ ├── SidebarTab.ts
│ │ └── Topbar.ts
│ ├── helpers/ - Focused helper classes (domain-specific actions)
│ │ ├── CanvasHelper.ts
│ │ ├── CommandHelper.ts
│ │ ├── KeyboardHelper.ts
│ │ ├── NodeOperationsHelper.ts
│ │ ├── SettingsHelper.ts
│ │ ├── WorkflowHelper.ts
│ │ └── ...
│ └── utils/ - Pure utility functions (no page dependency)
├── helpers/ - Test-specific utilities
└── tests/ - Test files (*.spec.ts)
Architectural Separation
fixtures/data/— Static test data only. Mock API responses, workflow JSONs, node definitions. No code, no imports from Playwright.fixtures/components/— Page object components. Encapsulate locators and user interactions for a specific UI area.fixtures/helpers/— Focused helper classes. Domain-specific actions that coordinate multiple page objects (e.g. canvas operations, workflow loading).fixtures/utils/— Pure utility functions. NoPagedependency; stateless helpers that can be used anywhere.
Polling Assertions
Prefer expect.poll() over expect(async () => { ... }).toPass() when the block contains a single async call with a single assertion. expect.poll() is more readable and gives better error messages (shows actual vs expected on failure).
// ✅ Correct — single async call + single assertion
await expect
.poll(() => comfyPage.nodeOps.getGraphNodesCount(), { timeout: 250 })
.toBe(0)
// ❌ Avoid — nested expect inside toPass
await expect(async () => {
expect(await comfyPage.nodeOps.getGraphNodesCount()).toBe(0)
}).toPass({ timeout: 250 })
Reserve toPass() for blocks with multiple assertions or complex async logic that can't be expressed as a single polled value.
Gotchas
| Symptom | Cause | Fix |
|---|---|---|
subtree intercepts pointer events on DOM widgets |
Canvas z-999 overlay intercepts click() |
Use Playwright's locator.dispatchEvent('contextmenu', { bubbles: true, cancelable: true, button: 2 }) |
| Context menu empty or wrong items | Node not selected | Select node first: vueNodes.selectNode() or nodeRef.click('title') |
navigateIntoSubgraph timeout |
Node too small in test asset JSON | Use node size [400, 200] minimum |
After Making Changes
- Run
pnpm typecheck:browserafter modifying TypeScript files in this directory - Run
pnpm exec eslint browser_tests/path/to/file.tsto lint specific files - Run
pnpm exec oxlint browser_tests/path/to/file.tsto check with oxlint
Skill Documentation
A Playwright test-writing skill exists at .claude/skills/writing-playwright-tests/SKILL.md.
The skill documents meta-level guidance only (gotchas, anti-patterns, decision guides). It does not duplicate fixture APIs - agents should read the fixture code directly in browser_tests/fixtures/.