mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-03 05:38:26 +00:00
Compare commits
2 Commits
codex/cove
...
docs/conso
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70182c8ee4 | ||
|
|
092409ee93 |
@@ -10,8 +10,7 @@ You are reviewing Playwright E2E test code in `browser_tests/`. Focus on issues
|
||||
Reference docs (read if you need full context):
|
||||
|
||||
- `browser_tests/README.md` — setup, patterns, screenshot workflow
|
||||
- `browser_tests/AGENTS.md` — directory structure, fixture overview
|
||||
- `docs/guidance/playwright.md` — type assertion rules, test tags, forbidden patterns
|
||||
- `browser_tests/AGENTS.md` — directory structure, fixture overview, type assertion rules, test tags, forbidden patterns
|
||||
- `.claude/skills/writing-playwright-tests/SKILL.md` — anti-patterns, retry patterns, Vue Nodes vs LiteGraph decision guide
|
||||
|
||||
## Checks
|
||||
@@ -49,7 +48,7 @@ Reference docs (read if you need full context):
|
||||
|
||||
11. **Missing test tags** — Every `test.describe` should have `tag` with at least one of: `@smoke`, `@slow`, `@screenshot`, `@canvas`, `@node`, `@widget`, `@mobile`, `@2x`. See `.claude/skills/writing-playwright-tests/SKILL.md` for when to use each.
|
||||
|
||||
12. **`as any` type assertions** — Forbidden in E2E tests. Use specific type assertions or test-local type helpers. See `docs/guidance/playwright.md` for acceptable patterns.
|
||||
12. **`as any` type assertions** — Forbidden in E2E tests. Use specific type assertions or test-local type helpers. See `browser_tests/AGENTS.md` for acceptable patterns.
|
||||
|
||||
13. **Screenshot tests without masking dynamic content** — Timestamps, version numbers, or other non-deterministic content in screenshots will cause flakes. Use `mask` option.
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ A regression test that never proves red does not pin the bug.
|
||||
| New store test or store mock | [`docs/testing/vitest-patterns.md`](../../../docs/testing/vitest-patterns.md) setup + [`docs/testing/store-testing.md`](../../../docs/testing/store-testing.md) |
|
||||
| New component test | Top note in [`docs/testing/component-testing.md`](../../../docs/testing/component-testing.md) |
|
||||
| `vue-i18n` in a component test | [`docs/testing/vitest-patterns.md`](../../../docs/testing/vitest-patterns.md) + [`src/components/searchbox/v2/__test__/testUtils.ts`](../../../src/components/searchbox/v2/__test__/testUtils.ts) |
|
||||
| Cast around a mock | [`docs/guidance/typescript.md`](../../../docs/guidance/typescript.md) -> "Type Assertion Hierarchy" |
|
||||
| Cast around a mock | [`src/AGENTS.md`](../../../src/AGENTS.md) -> "TypeScript" (type assertion hierarchy) |
|
||||
|
||||
## Key Files to Read
|
||||
|
||||
|
||||
37
.github/copilot-instructions.md
vendored
37
.github/copilot-instructions.md
vendored
@@ -1,37 +0,0 @@
|
||||
Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
|
||||
|
||||
Use setup() function for component logic
|
||||
|
||||
Utilize ref and reactive for reactive state
|
||||
|
||||
Implement computed properties with computed()
|
||||
|
||||
Use watch and watchEffect for side effects
|
||||
|
||||
Implement lifecycle hooks with onMounted, onUpdated, etc.
|
||||
|
||||
Utilize provide/inject for dependency injection
|
||||
|
||||
Use vue 3.5 style of default prop declaration.
|
||||
|
||||
Use Tailwind CSS for styling
|
||||
|
||||
Leverage VueUse functions for performance-enhancing styles
|
||||
|
||||
Use es-toolkit for utility functions
|
||||
|
||||
Use TypeScript for type safety
|
||||
|
||||
Implement proper props and emits definitions
|
||||
|
||||
Utilize Vue 3's Teleport component when needed
|
||||
|
||||
Use Suspense for async components
|
||||
|
||||
Implement proper error handling
|
||||
|
||||
Follow Vue 3 style guide and naming conventions
|
||||
|
||||
Use Vite for fast development and building
|
||||
|
||||
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Storybook Guidelines
|
||||
|
||||
See `@docs/guidance/storybook.md` for story patterns (auto-loaded for `*.stories.ts`).
|
||||
See the Storybook section of `src/AGENTS.md` for story structure and variant conventions.
|
||||
|
||||
## Available Context
|
||||
|
||||
|
||||
10
AGENTS.md
10
AGENTS.md
@@ -1,6 +1,6 @@
|
||||
# Repository Guidelines
|
||||
|
||||
See @docs/guidance/\*.md for file-type-specific conventions (auto-loaded by glob).
|
||||
Directory-specific conventions live in nested `AGENTS.md` files — `src/AGENTS.md` (TypeScript, unit tests, Vue, Storybook, design standards) and `browser_tests/AGENTS.md` (Playwright E2E) — which agents load automatically when working in those directories.
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
@@ -185,13 +185,13 @@ This project uses **pnpm**. Always prefer scripts defined in `package.json` (e.g
|
||||
|
||||
## Design Standards
|
||||
|
||||
Before implementing any user-facing feature, consult the [Comfy Design Standards](https://www.figma.com/design/QreIv5htUaSICNuO2VBHw0/Comfy-Design-Standards) Figma file. Use the Figma MCP to fetch it live — the file is the single source of truth and may be updated by designers at any time.
|
||||
When adding new UI or changing visual design, consult the [Comfy Design Standards](https://www.figma.com/design/QreIv5htUaSICNuO2VBHw0/Comfy-Design-Standards) Figma file. Use the Figma MCP to fetch it live — the file is the single source of truth and may be updated by designers at any time. Skip this for refactors, bugfixes, and logic-only changes; if the Figma MCP is unavailable, say so in the PR description instead of skipping silently.
|
||||
|
||||
See `docs/guidance/design-standards.md` for Figma file keys, section node IDs, and component references.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
See @docs/testing/\*.md for detailed patterns.
|
||||
Read `docs/testing/<topic>.md` (unit-testing, component-testing, store-testing, vitest-patterns) for detailed patterns when writing the corresponding kind of test.
|
||||
|
||||
- Frameworks:
|
||||
- Vitest (unit/component, happy-dom)
|
||||
@@ -218,7 +218,7 @@ See @docs/testing/\*.md for detailed patterns.
|
||||
3. Keep your module mocks contained
|
||||
Do not use global mutable state within the test file
|
||||
Use `vi.hoisted()` if necessary to allow for per-test Arrange phase manipulation of deeper mock state
|
||||
4. For Component testing, prefer [@testing-library/vue](https://testing-library.com/docs/vue-testing-library/intro/) with `@testing-library/user-event` for user-centric, behavioral tests. [Vue Test Utils](https://test-utils.vuejs.org/) is also accepted, especially for tests that need direct access to the component wrapper (e.g., `findComponent`, `emitted()`). Follow the advice [about making components easy to test](https://test-utils.vuejs.org/guide/essentials/easy-to-test.html)
|
||||
4. For Component testing, use [@testing-library/vue](https://testing-library.com/docs/vue-testing-library/intro/) with `@testing-library/user-event` for user-centric, behavioral tests — an ESLint rule bans `@vue/test-utils` imports in new tests. Follow the advice [about making components easy to test](https://test-utils.vuejs.org/guide/essentials/easy-to-test.html)
|
||||
5. Aim for behavioral coverage of critical and new features
|
||||
|
||||
### Playwright / Browser / E2E Tests
|
||||
@@ -226,7 +226,7 @@ See @docs/testing/\*.md for detailed patterns.
|
||||
1. Follow the Best Practices described [in the Playwright documentation](https://playwright.dev/docs/best-practices)
|
||||
2. Do not use waitForTimeout, use Locator actions and [retrying assertions](https://playwright.dev/docs/test-assertions#auto-retrying-assertions)
|
||||
3. Tags like `@mobile`, `@2x` are respected by config and should be used for relevant tests
|
||||
4. Type all API mock responses in `route.fulfill()` using generated types or schemas from `packages/ingest-types`, `packages/registry-types`, `src/workbench/extensions/manager/types/generatedManagerTypes.ts`, or `src/schemas/` — see `docs/guidance/playwright.md` for the full source-of-truth table
|
||||
4. Type all API mock responses in `route.fulfill()` using generated types or schemas from `packages/ingest-types`, `packages/registry-types`, `src/workbench/extensions/manager/types/generatedManagerTypes.ts`, or `src/schemas/` — see `browser_tests/AGENTS.md` for the full source-of-truth table
|
||||
|
||||
## External Resources
|
||||
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
# 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
|
||||
See `browser_tests/FLAKE_PREVENTION_RULES.md` when triaging or editing
|
||||
flaky browser tests.
|
||||
|
||||
## Core Rules
|
||||
|
||||
- Follow [Playwright Best Practices](https://playwright.dev/docs/best-practices)
|
||||
- Never use `waitForTimeout` — use retrying assertions (`toBeVisible`,
|
||||
`toHaveText`), `expect.poll()`, or `toPass()`
|
||||
- Prefer specific selectors (role, label, test-id)
|
||||
- Prefer helpers from `fixtures/helpers/` that wrap real user interactions
|
||||
(`comfyPage.settings.setSetting`, `comfyPage.workflow.loadWorkflow`,
|
||||
`comfyPage.nodeOps`) over hand-rolled equivalents
|
||||
- Tag viewport-specific tests: `@mobile` (mobile viewport), `@2x` (high DPI)
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```text
|
||||
@@ -62,6 +72,46 @@ flowchart TD
|
||||
E -- No, coordinates actions<br/>across the app --> H[fixtures/helpers/]
|
||||
```
|
||||
|
||||
## Test Structure: Arrange/Act/Assert
|
||||
|
||||
1. All mock setup, state resets, and fixture arrangement belongs in `test.beforeEach()` or Playwright fixtures
|
||||
2. Inside `test()`, only act (user actions) and assert
|
||||
3. Never call `clearAllMocks` or reset mock state mid-test
|
||||
|
||||
```typescript
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.workflow.loadWorkflow('test.json')
|
||||
})
|
||||
test('should do something', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.topbar.click()
|
||||
await expect(comfyPage.menu.nodeLibraryTab.root).toBeVisible()
|
||||
})
|
||||
```
|
||||
|
||||
## Creating New Test Helpers
|
||||
|
||||
New domain-specific test helpers (e.g., `AssetHelper`, `JobHelper`) must be
|
||||
registered as Playwright fixtures via `base.extend()` — do **not** add them as
|
||||
properties on `ComfyPage`. Extend `@playwright/test` directly (not
|
||||
`comfyPageFixture`) so fixtures stay composable via `mergeTests`; the callback
|
||||
after `use()` gives automatic teardown.
|
||||
|
||||
```typescript
|
||||
// browser_tests/fixtures/assetFixture.ts
|
||||
import { test as base } from '@playwright/test'
|
||||
|
||||
export const test = base.extend<{
|
||||
assetHelper: AssetHelper
|
||||
}>({
|
||||
assetHelper: async ({ page }, use) => {
|
||||
const helper = new AssetHelper(page)
|
||||
await helper.setup()
|
||||
await use(helper)
|
||||
await helper.cleanup() // automatic teardown
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Page Object Locator Style
|
||||
|
||||
Define UI element locators as `public readonly` properties assigned in the constructor — not as getter methods. Getters that simply return a locator add unnecessary indirection and hide the object shape from IDE auto-complete.
|
||||
@@ -95,7 +145,7 @@ export class MyDialog extends BaseDialog {
|
||||
|
||||
When a class has cached locator properties, prefer reusing them in methods rather than rebuilding locators from scratch.
|
||||
|
||||
## Polling Assertions
|
||||
## 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).
|
||||
|
||||
@@ -113,6 +163,98 @@ await expect(async () => {
|
||||
|
||||
Reserve `toPass()` for blocks with multiple assertions or complex async logic that can't be expressed as a single polled value.
|
||||
|
||||
Assert preconditions explicitly with a custom message so failures point to the
|
||||
broken assumption; use `expect.soft()` to verify multiple invariants without
|
||||
aborting on the first failure:
|
||||
|
||||
```typescript
|
||||
expect(node.widgets, 'Widget count changed — update test fixture').toHaveLength(
|
||||
4
|
||||
)
|
||||
expect.soft(menuItem1).toBeVisible()
|
||||
expect.soft(menuItem2).toBeVisible()
|
||||
```
|
||||
|
||||
Add assertion methods directly on the page object or helper class
|
||||
(`await node.expectPinned()`) — do not add custom matchers to `comfyExpect`.
|
||||
Page object methods are discoverable via IntelliSense without special imports.
|
||||
|
||||
## Window Globals & Type Assertions
|
||||
|
||||
`window.app`, `window.graph`, and `window.LiteGraph` are optional in the main
|
||||
app types. Non-null assertions (`!`) are allowed in E2E tests only:
|
||||
`window.app!.graph!.nodes`. Specific assertions like
|
||||
`id: 'TestSetting' as TestSettingId` are acceptable; `as any` and
|
||||
`as unknown as SomeType` are forbidden. Access internal state via
|
||||
`page.evaluate` and stores directly — don't change public API types to expose
|
||||
internals.
|
||||
|
||||
## When to Use `page.evaluate`
|
||||
|
||||
Acceptable (use sparingly): reading internal state that has no UI
|
||||
representation, or setting up test fixtures (registering extensions, mock
|
||||
error handlers). Never for actions that have a UI equivalent.
|
||||
|
||||
```typescript
|
||||
// ✅ Reading internal state
|
||||
const nodeCount = await page.evaluate(() => window.app!.graph!.nodes.length)
|
||||
|
||||
// ❌ Bad: setting a widget value programmatically
|
||||
await page.evaluate(() => {
|
||||
node.widgets![0].value = 512
|
||||
})
|
||||
// ✅ Good: interact like a user
|
||||
await widgetLocator.click()
|
||||
await widgetLocator.fill('512')
|
||||
```
|
||||
|
||||
## Test Data
|
||||
|
||||
- Check `browser_tests/assets/` for fixtures; use realistic ComfyUI workflows
|
||||
- When multiple nodes share the same title, use
|
||||
`vueNodes.getNodeByTitle(name).nth(n)` — Playwright strict mode will fail on
|
||||
ambiguous locators
|
||||
- When creating fixture data, import existing Zod schemas and types from
|
||||
`src/` instead of inventing ad-hoc shapes: `src/schemas/apiSchema.ts` (API
|
||||
responses, WebSocket messages), `src/schemas/nodeDefSchema.ts` and
|
||||
`src/schemas/nodeDef/nodeDefSchemaV2.ts` (node definitions),
|
||||
`src/platform/remote/comfyui/jobs/jobTypes.ts` (Jobs API),
|
||||
`src/platform/workflow/validation/schemas/workflowSchema.ts` (workflows),
|
||||
`src/types/metadataTypes.ts` (asset metadata)
|
||||
|
||||
## Typed API Mocks
|
||||
|
||||
Type every `route.fulfill()` response body using existing schemas or generated
|
||||
types — never untyped inline JSON. This catches shape mismatches at compile
|
||||
time instead of through flaky runtime failures.
|
||||
|
||||
| Endpoint category | Type source |
|
||||
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| Cloud-only (hub, billing, workflows) | `@comfyorg/ingest-types` (`packages/ingest-types`, auto-generated from OpenAPI) |
|
||||
| Registry (releases, nodes, publishers) | `@comfyorg/registry-types` (`packages/registry-types`, auto-generated from OpenAPI) |
|
||||
| Manager (queue tasks, packages) | `generatedManagerTypes.ts` (`src/workbench/extensions/manager/types/`, auto-generated from OpenAPI) |
|
||||
| Python backend (queue, history, settings, features) | Manual Zod schemas in `src/schemas/apiSchema.ts` |
|
||||
| Node definitions | `src/schemas/nodeDefSchema.ts` |
|
||||
| Templates | `src/platform/workflow/templates/types/template.ts` |
|
||||
|
||||
```typescript
|
||||
// ✅ Import the type and annotate mock data
|
||||
import type { ReleaseNote } from '@/platform/updates/common/releaseService'
|
||||
|
||||
const mockRelease: ReleaseNote = {
|
||||
id: 1,
|
||||
project: 'comfyui',
|
||||
version: 'v0.3.44',
|
||||
attention: 'medium',
|
||||
content: '## New Features',
|
||||
published_at: new Date().toISOString()
|
||||
}
|
||||
body: JSON.stringify([mockRelease])
|
||||
|
||||
// ❌ Untyped inline JSON — schema drift goes unnoticed
|
||||
body: JSON.stringify([{ id: 1, project: 'comfyui', version: 'v0.3.44', ... }])
|
||||
```
|
||||
|
||||
## Gotchas
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
@@ -121,6 +263,13 @@ Reserve `toPass()` for blocks with multiple assertions or complex async logic th
|
||||
| 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 |
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
pnpm test:browser:local # Run all E2E tests
|
||||
pnpm test:browser:local --ui # Interactive UI mode
|
||||
```
|
||||
|
||||
## After Making Changes
|
||||
|
||||
- Run `pnpm typecheck:browser` after modifying TypeScript files in this directory
|
||||
|
||||
@@ -31,4 +31,4 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
|
||||
- Tests requiring VueNodes rendering enable it in `beforeEach` via `comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)` and call `comfyPage.vueNodes.waitForNodes()`.
|
||||
- Verify node state changes via user-facing indicators (text labels like "Bypassed"/"Muted", pin indicator test IDs) rather than internal properties.
|
||||
- Color changes are verified via `page.evaluate` accessing node properties, per the guidance in `docs/guidance/playwright.md`.
|
||||
- Color changes are verified via `page.evaluate` accessing node properties, per the guidance in `browser_tests/AGENTS.md`.
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
---
|
||||
globs:
|
||||
- 'src/components/**/*.vue'
|
||||
- 'src/views/**/*.vue'
|
||||
---
|
||||
|
||||
# Comfy Design Standards
|
||||
|
||||
Applies when implementing or modifying user-facing components and views.
|
||||
Reference for implementing user-facing components and views anywhere in the
|
||||
codebase (`src/components/`, `src/views/`, `src/platform/`, `src/workbench/`,
|
||||
`apps/`).
|
||||
|
||||
## Before Implementing UI Changes
|
||||
## When to Consult
|
||||
|
||||
Consult the **Comfy Design Standards** Figma file to ensure your changes follow the agreed-upon design principles. Use the Figma MCP tool to fetch the current standards:
|
||||
When adding new UI or changing visual design, fetch the relevant section of
|
||||
the **Comfy Design Standards** Figma file before implementing. Skip this for
|
||||
refactors, bugfixes, and logic-only changes. If the Figma MCP is unavailable,
|
||||
say so in the PR description instead of skipping silently.
|
||||
|
||||
The Figma file is the single source of truth. Always fetch it live — do not
|
||||
rely on cached assumptions:
|
||||
|
||||
```javascript
|
||||
get_figma_data({ fileKey: 'QreIv5htUaSICNuO2VBHw0', nodeId: '0:1' })
|
||||
```
|
||||
|
||||
The Figma file is the single source of truth. Always fetch it live — do not rely on cached assumptions.
|
||||
|
||||
> **Note:** The Figma MCP is read-only. It cannot detect changes or diffs between versions. Always fetch the latest state before implementing.
|
||||
|
||||
### Key Sections
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
---
|
||||
globs:
|
||||
- '**/*.spec.ts'
|
||||
---
|
||||
|
||||
# Playwright E2E Test Conventions
|
||||
|
||||
See `docs/testing/*.md` for detailed patterns.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Follow [Playwright Best Practices](https://playwright.dev/docs/best-practices)
|
||||
- Do NOT use `waitForTimeout` — use Locator actions and retrying assertions
|
||||
- Prefer specific selectors (role, label, test-id)
|
||||
- Test across viewports
|
||||
|
||||
## Window Globals
|
||||
|
||||
Browser tests access `window.app`, `window.graph`, and `window.LiteGraph` which are
|
||||
optional in the main app types. Use non-null assertions (`!`) in E2E tests only:
|
||||
|
||||
```typescript
|
||||
window.app!.graph!.nodes
|
||||
window.LiteGraph!.registered_node_types
|
||||
```
|
||||
|
||||
TODO: Consolidate into a central utility (e.g., `getApp()`) with runtime type checking.
|
||||
|
||||
## Type Assertions
|
||||
|
||||
Use specific type assertions when needed, never `as any`.
|
||||
|
||||
Acceptable:
|
||||
|
||||
```typescript
|
||||
window.app!.extensionManager
|
||||
id: 'TestSetting' as TestSettingId
|
||||
type TestSettingId = keyof Settings
|
||||
```
|
||||
|
||||
Forbidden:
|
||||
|
||||
```typescript
|
||||
settings: testData as any
|
||||
data as unknown as SomeType
|
||||
```
|
||||
|
||||
Access internal state via `page.evaluate` and stores directly — don't change public API types to expose internals.
|
||||
|
||||
## Assertion Best Practices
|
||||
|
||||
Assert preconditions explicitly with a custom message so failures point to the broken assumption:
|
||||
|
||||
```typescript
|
||||
expect(node.widgets, 'Widget count changed — update test fixture').toHaveLength(
|
||||
4
|
||||
)
|
||||
await node.move(100, 200)
|
||||
|
||||
expect.soft(menuItem1).toBeVisible()
|
||||
expect.soft(menuItem2).toBeVisible()
|
||||
|
||||
// Bad — bare expect on a precondition gives no context when it fails
|
||||
expect(node.widgets).toHaveLength(4)
|
||||
```
|
||||
|
||||
- `expect(x, 'reason')` for precondition checks unrelated to the test's purpose
|
||||
- `expect.soft()` to verify multiple invariants without aborting on the first failure
|
||||
|
||||
## Test Structure: Arrange/Act/Assert
|
||||
|
||||
1. All mock setup, state resets, and fixture arrangement belongs in `test.beforeEach()` or Playwright fixtures
|
||||
2. Inside `test()`, only act (user actions) and assert
|
||||
3. Never call `clearAllMocks` or reset mock state mid-test
|
||||
|
||||
```typescript
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.workflow.loadWorkflow('test.json')
|
||||
})
|
||||
test('should do something', async ({ comfyPage }) => {
|
||||
await comfyPage.menu.topbar.click()
|
||||
await expect(comfyPage.menu.nodeLibraryTab.root).toBeVisible()
|
||||
})
|
||||
```
|
||||
|
||||
## Creating New Test Helpers
|
||||
|
||||
New domain-specific test helpers (e.g., `AssetHelper`, `JobHelper`) should be
|
||||
registered as Playwright fixtures via `base.extend()` rather than attached as
|
||||
properties on `ComfyPage`. This enables automatic setup/teardown.
|
||||
|
||||
### Extend `base` from Playwright
|
||||
|
||||
Keep each fixture self-contained by extending `@playwright/test` directly.
|
||||
Compose fixtures together with `mergeTests` when a test needs multiple helpers.
|
||||
|
||||
```typescript
|
||||
// browser_tests/fixtures/assetFixture.ts
|
||||
import { test as base } from '@playwright/test'
|
||||
|
||||
export const test = base.extend<{
|
||||
assetHelper: AssetHelper
|
||||
}>({
|
||||
assetHelper: async ({ page }, use) => {
|
||||
const helper = new AssetHelper(page)
|
||||
await helper.setup()
|
||||
await use(helper)
|
||||
await helper.cleanup() // automatic teardown
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- **Do NOT** add new helpers as properties on `ComfyPage`
|
||||
- Each fixture gets automatic cleanup via the callback after `use()`
|
||||
- Keep fixtures modular — extend `@playwright/test` base, not
|
||||
`comfyPageFixture`, so they can be composed via `mergeTests`
|
||||
|
||||
## Custom Assertions
|
||||
|
||||
Add assertion methods directly on the page object or helper class instead of extending `comfyExpect`. Page object methods are discoverable via IntelliSense without special imports.
|
||||
|
||||
```typescript
|
||||
// ✅ Page object assertions
|
||||
await node.expectPinned()
|
||||
await node.expectBypassed()
|
||||
|
||||
// ❌ Do not add custom matchers to comfyExpect
|
||||
```
|
||||
|
||||
## Test Tags
|
||||
|
||||
- `@mobile` — Mobile viewport tests
|
||||
- `@2x` — High DPI tests
|
||||
|
||||
## Test Data
|
||||
|
||||
- Check `browser_tests/assets/` for fixtures
|
||||
- Use realistic ComfyUI workflows
|
||||
- When multiple nodes share the same title, use `vueNodes.getNodeByTitle(name).nth(n)` — Playwright strict mode will fail on ambiguous locators
|
||||
|
||||
## Fixture Data & Schemas
|
||||
|
||||
When creating test fixture data, import or reference existing Zod schemas and TypeScript
|
||||
types from `src/` instead of inventing ad-hoc shapes. This keeps test data in sync with
|
||||
production types.
|
||||
|
||||
Key schema locations:
|
||||
|
||||
- `src/schemas/apiSchema.ts` — API response types (`PromptResponse`, `SystemStats`, `User`, `UserDataFullInfo`, WebSocket messages)
|
||||
- `src/schemas/nodeDefSchema.ts` — Node definition schema (`ComfyNodeDef`, `InputSpec`, `ComboInputSpec`)
|
||||
- `src/schemas/nodeDef/nodeDefSchemaV2.ts` — V2 node definition schema
|
||||
- `src/platform/remote/comfyui/jobs/jobTypes.ts` — Jobs API Zod schemas (`zJobDetail`, `zJobsListResponse`, `zRawJobListItem`)
|
||||
- `src/platform/workflow/validation/schemas/workflowSchema.ts` — Workflow validation (`ComfyWorkflowJSON`, `ComfyApiWorkflow`)
|
||||
- `src/types/metadataTypes.ts` — Asset metadata types
|
||||
|
||||
## Typed API Mocks
|
||||
|
||||
When mocking API responses with `route.fulfill()`, **always** type the response body
|
||||
using existing schemas or generated types — never use untyped inline JSON objects.
|
||||
This catches shape mismatches at compile time instead of through flaky runtime failures.
|
||||
|
||||
All three generated-type packages (`ingest-types`, `registry-types`, `generatedManagerTypes`)
|
||||
are auto-generated from their respective OpenAPI specs. Prefer these as the single
|
||||
source of truth for any mock that targets their endpoints.
|
||||
|
||||
### Sources of truth
|
||||
|
||||
| Endpoint category | Type source |
|
||||
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| Cloud-only (hub, billing, workflows) | `@comfyorg/ingest-types` (`packages/ingest-types`, auto-generated from OpenAPI) |
|
||||
| Registry (releases, nodes, publishers) | `@comfyorg/registry-types` (`packages/registry-types`, auto-generated from OpenAPI) |
|
||||
| Manager (queue tasks, packages) | `generatedManagerTypes.ts` (`src/workbench/extensions/manager/types/`, auto-generated from OpenAPI) |
|
||||
| Python backend (queue, history, settings, features) | Manual Zod schemas in `src/schemas/apiSchema.ts` |
|
||||
| Node definitions | `src/schemas/nodeDefSchema.ts` |
|
||||
| Templates | `src/platform/workflow/templates/types/template.ts` |
|
||||
|
||||
### Patterns
|
||||
|
||||
```typescript
|
||||
// ✅ Import the type and annotate mock data
|
||||
import type { ReleaseNote } from '@/platform/updates/common/releaseService'
|
||||
|
||||
const mockRelease: ReleaseNote = {
|
||||
id: 1,
|
||||
project: 'comfyui',
|
||||
version: 'v0.3.44',
|
||||
attention: 'medium',
|
||||
content: '## New Features',
|
||||
published_at: new Date().toISOString()
|
||||
}
|
||||
body: JSON.stringify([mockRelease])
|
||||
|
||||
// ❌ Untyped inline JSON — schema drift goes unnoticed
|
||||
body: JSON.stringify([{ id: 1, project: 'comfyui', version: 'v0.3.44', ... }])
|
||||
```
|
||||
|
||||
## When to Use `page.evaluate`
|
||||
|
||||
### Acceptable (use sparingly)
|
||||
|
||||
Reading internal state that has no UI representation (prefer locator
|
||||
assertions whenever possible):
|
||||
|
||||
```typescript
|
||||
// Reading graph/store values
|
||||
const nodeCount = await page.evaluate(() => window.app!.graph!.nodes.length)
|
||||
const linkSlot = await page.evaluate(() => window.app!.graph!.links.get(1)?.target_slot)
|
||||
|
||||
// Reading computed properties or store state
|
||||
await page.evaluate(() => useWorkflowStore().activeWorkflow)
|
||||
|
||||
// Setting up test fixtures (registering extensions, mock error handlers)
|
||||
await page.evaluate(() => {
|
||||
window.app!.registerExtension({ name: 'TestExt', settings: [...] })
|
||||
})
|
||||
```
|
||||
|
||||
### Avoid
|
||||
|
||||
Performing actions that have a UI equivalent — use Playwright locators and user
|
||||
interactions instead:
|
||||
|
||||
```typescript
|
||||
// Bad: setting a widget value programmatically
|
||||
await page.evaluate(() => { node.widgets![0].value = 512 })
|
||||
// Good: click the widget and type the value
|
||||
await widgetLocator.click()
|
||||
await widgetLocator.fill('512')
|
||||
|
||||
// Bad: dispatching synthetic DOM events
|
||||
await page.evaluate(() => { btn.dispatchEvent(new MouseEvent('click', ...)) })
|
||||
// Good: use Playwright's click
|
||||
await page.getByTestId('more-options-button').click()
|
||||
|
||||
// Bad: calling store actions that correspond to user interactions
|
||||
await page.evaluate(() => { app.queuePrompt(0) })
|
||||
// Good: click the Queue button
|
||||
await page.getByRole('button', { name: 'Queue' }).click()
|
||||
```
|
||||
|
||||
### Preferred
|
||||
|
||||
Use helper methods from `browser_tests/fixtures/helpers/` that wrap real user
|
||||
interactions (e.g., `comfyPage.settings.setSetting`, `comfyPage.nodeOps`,
|
||||
`comfyPage.workflow.loadWorkflow`).
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
pnpm test:browser:local # Run all E2E tests
|
||||
pnpm test:browser:local --ui # Interactive UI mode
|
||||
```
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
globs:
|
||||
- '**/*.stories.ts'
|
||||
---
|
||||
|
||||
# Storybook Conventions
|
||||
|
||||
## File Placement
|
||||
|
||||
Place `*.stories.ts` files alongside their components:
|
||||
|
||||
```
|
||||
src/components/MyComponent/
|
||||
├── MyComponent.vue
|
||||
└── MyComponent.stories.ts
|
||||
```
|
||||
|
||||
## Story Structure
|
||||
|
||||
```typescript
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
import ComponentName from './ComponentName.vue'
|
||||
|
||||
const meta: Meta<typeof ComponentName> = {
|
||||
title: 'Category/ComponentName',
|
||||
component: ComponentName,
|
||||
parameters: { layout: 'centered' }
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
/* props */
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Required Story Variants
|
||||
|
||||
Include when applicable:
|
||||
|
||||
- **Default** - Minimal props
|
||||
- **WithData** - Realistic data
|
||||
- **Loading** - Loading state
|
||||
- **Error** - Error state
|
||||
- **Empty** - No data
|
||||
|
||||
## Mock Data
|
||||
|
||||
Use realistic ComfyUI schemas for mocks (node definitions, components).
|
||||
|
||||
## Running Storybook
|
||||
|
||||
```bash
|
||||
pnpm storybook # Development server
|
||||
pnpm build-storybook # Production build
|
||||
```
|
||||
@@ -1,78 +0,0 @@
|
||||
---
|
||||
globs:
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
- '**/*.vue'
|
||||
---
|
||||
|
||||
# TypeScript Conventions
|
||||
|
||||
## Type Safety
|
||||
|
||||
- Never use `any` type - use proper TypeScript types
|
||||
- Never use `as any` type assertions - fix the underlying type issue
|
||||
- Type assertions are a last resort; they lead to brittle code
|
||||
- Avoid `@ts-expect-error` - fix the underlying issue instead
|
||||
|
||||
### Type Assertion Hierarchy
|
||||
|
||||
When you must handle uncertain types, prefer these approaches in order:
|
||||
|
||||
1. ✅ **No assertion** — Properly typed from the start
|
||||
2. ✅ **Type narrowing** — `if ('prop' in obj)` or type guards
|
||||
3. ⚠️ **Specific assertion** — `as SpecificType` when you truly know the type
|
||||
4. ⚠️ **`unknown` with narrowing** — For genuinely unknown data
|
||||
5. ❌ **`as any`** — FORBIDDEN
|
||||
|
||||
### Zod Schema Rules
|
||||
|
||||
- Never use `z.any()` — it disables validation and propagates `any` into types
|
||||
- Use `z.unknown()` if the type is genuinely unknown, then narrow it
|
||||
- Never add test-only settings/types to production schemas
|
||||
|
||||
### Public API Contracts
|
||||
|
||||
- Keep public API types stable (e.g., `ExtensionManager` interface)
|
||||
- Don't expose internal implementation types (e.g., Pinia store internals)
|
||||
- Reactive refs (`ComputedRef<T>`) should be unwrapped before exposing
|
||||
|
||||
## Avoiding Circular Dependencies
|
||||
|
||||
Extract type guards and their associated interfaces into **leaf modules** — files with only `import type` statements. This keeps them safe to import from anywhere without pulling in heavy transitive dependencies.
|
||||
|
||||
```typescript
|
||||
// ✅ myTypes.ts — leaf module (only type imports)
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
export interface MyView extends IBaseWidget {
|
||||
/* ... */
|
||||
}
|
||||
export function isMyView(w: IBaseWidget): w is MyView {
|
||||
return 'myProp' in w
|
||||
}
|
||||
|
||||
// ❌ myView.ts — heavy module (runtime imports from stores, utils, etc.)
|
||||
// Importing the type guard from here drags in the entire dependency tree.
|
||||
```
|
||||
|
||||
## Utility Libraries
|
||||
|
||||
- Use `es-toolkit` for utility functions (not lodash)
|
||||
|
||||
## API Utilities
|
||||
|
||||
When making API calls in `src/`:
|
||||
|
||||
```typescript
|
||||
// ✅ Correct - use api helpers
|
||||
const response = await api.get(api.apiURL('/prompt'))
|
||||
const template = await fetch(api.fileURL('/templates/default.json'))
|
||||
|
||||
// ❌ Wrong - direct URL construction
|
||||
const response = await fetch('/api/prompt')
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
- Sanitize HTML with `DOMPurify.sanitize()`
|
||||
- Never log secrets or sensitive data
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
globs:
|
||||
- '**/*.test.ts'
|
||||
---
|
||||
|
||||
# Vitest Unit Test Conventions
|
||||
|
||||
See `docs/testing/*.md` for detailed patterns.
|
||||
|
||||
## Test Quality
|
||||
|
||||
- Do not write change detector tests (tests that just assert defaults)
|
||||
- Do not write tests dependent on non-behavioral features (styles, classes)
|
||||
- Do not write tests that just test mocks - ensure real code is exercised
|
||||
- Be parsimonious; avoid redundant tests
|
||||
|
||||
## Mocking
|
||||
|
||||
- Use Vitest's mocking utilities (`vi.mock`, `vi.spyOn`)
|
||||
- Keep module mocks contained - no global mutable state
|
||||
- Use `vi.hoisted()` for per-test mock manipulation
|
||||
- Don't mock what you don't own
|
||||
|
||||
## Component Testing
|
||||
|
||||
- Use `@testing-library/vue` with `@testing-library/user-event` for component tests (an ESLint rule bans `@vue/test-utils` in new tests)
|
||||
- Follow advice about making components easy to test
|
||||
- Wait for reactivity with `await nextTick()` after state changes
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
pnpm test:unit # Run all unit tests
|
||||
pnpm test:unit path/to/file # Filter by substring of test file path
|
||||
pnpm test:unit foo.test.ts -t "name" # Filter by test name (regex; it()/test() only, not describe())
|
||||
```
|
||||
|
||||
Do not use the `--` separator before vitest args; pnpm forwards extra args automatically, and `--` mangles quoted args (e.g. `-t "two words"`) on Windows PowerShell.
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
globs:
|
||||
- '**/*.vue'
|
||||
---
|
||||
|
||||
# Vue Component Conventions
|
||||
|
||||
Applies to all `.vue` files anywhere in the codebase.
|
||||
|
||||
## Vue 3 Composition API
|
||||
|
||||
- Use `<script setup lang="ts">` for component logic
|
||||
- Destructure props (Vue 3.5 style with defaults) like `const { color = 'blue' } = defineProps<...>()`
|
||||
- Use `ref`/`reactive` for state
|
||||
- Use `computed()` for derived state
|
||||
- Use lifecycle hooks: `onMounted`, `onUpdated`, etc.
|
||||
|
||||
## Component Communication
|
||||
|
||||
- Prefer `emit/@event-name` for state changes (promotes loose coupling)
|
||||
- Use `defineExpose` only for imperative operations (`form.validate()`, `modal.open()`)
|
||||
- Proper props and emits definitions
|
||||
|
||||
## VueUse Composables
|
||||
|
||||
Prefer VueUse composables over manual event handling:
|
||||
|
||||
- `useElementHover` instead of manual mouseover/mouseout listeners
|
||||
- `useIntersectionObserver` for visibility detection instead of scroll handlers
|
||||
- `useFocusTrap` for modal/dialog focus management
|
||||
- `useEventListener` for auto-cleanup event listeners
|
||||
|
||||
Prefer Vue native options when available:
|
||||
|
||||
- `defineModel` instead of `useVModel` for two-way binding with props
|
||||
|
||||
## Styling
|
||||
|
||||
- Use inline Tailwind CSS only (no `<style>` blocks)
|
||||
- Use `cn()` from `@comfyorg/tailwind-utils` for conditional classes
|
||||
- Refer to packages/design-system/src/css/style.css for design tokens and tailwind configuration
|
||||
- Exception: when third-party libraries render runtime DOM outside Vue templates
|
||||
(for example xterm internals inside PrimeVue terminal wrappers), scoped
|
||||
`:deep()` selectors are allowed. Add a brief inline comment explaining why the
|
||||
exception is required.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Extract complex conditionals to `computed`
|
||||
- In unmounted hooks, implement cleanup for async operations
|
||||
105
src/AGENTS.md
105
src/AGENTS.md
@@ -1,5 +1,28 @@
|
||||
# Source Code Guidelines
|
||||
|
||||
## TypeScript
|
||||
|
||||
- Never use `any` or `as any` (enforced by oxlint) — fix the underlying type issue
|
||||
- Avoid `@ts-expect-error` — fix the underlying issue instead
|
||||
- When handling uncertain types, prefer in order:
|
||||
1. Properly typed from the start
|
||||
2. Type narrowing — `if ('prop' in obj)` or type guards
|
||||
3. Specific assertion (`as SpecificType`) when you truly know the type
|
||||
4. `unknown` with narrowing for genuinely unknown data
|
||||
- Zod: never `z.any()` — it disables validation and propagates `any` into
|
||||
types. Use `z.unknown()` and narrow. Never add test-only settings/types to
|
||||
production schemas
|
||||
- Extract type guards and their interfaces into leaf modules (files with only
|
||||
`import type` statements) so they are safe to import from anywhere without
|
||||
pulling in heavy transitive dependencies or creating cycles
|
||||
- Keep public API types stable (e.g. `ExtensionManager`); don't expose
|
||||
internal implementation types (e.g. Pinia store internals); unwrap reactive
|
||||
refs (`ComputedRef<T>`) before exposing
|
||||
- Use `es-toolkit` for utility functions
|
||||
- API calls: use the api helpers — `api.get(api.apiURL('/prompt'))`,
|
||||
`fetch(api.fileURL('/templates/default.json'))` — never construct
|
||||
`/api/...` URLs by hand
|
||||
|
||||
## Error Handling
|
||||
|
||||
- User-friendly and actionable messages
|
||||
@@ -19,9 +42,83 @@
|
||||
- Clean up subscriptions
|
||||
- Only expose state/actions that are used externally; keep internal state private
|
||||
|
||||
## General Guidelines
|
||||
## i18n
|
||||
|
||||
- Use `es-toolkit` for utility functions
|
||||
- Use TypeScript for type safety
|
||||
- Avoid `@ts-expect-error` - fix the underlying issue
|
||||
- Use `vue-i18n` for ALL user-facing strings (`src/locales/en/main.json`)
|
||||
|
||||
## Unit Tests (`*.test.ts`)
|
||||
|
||||
- Use `@testing-library/vue` with `@testing-library/user-event` (an ESLint
|
||||
rule bans `@vue/test-utils` imports in new tests)
|
||||
- No change-detector tests (asserting defaults), no tests of non-behavioral
|
||||
features (styles, classes), no tests that only exercise mocks — ensure real
|
||||
code is exercised. Be parsimonious; avoid redundant tests
|
||||
- Mocking: use `vi.mock`/`vi.spyOn`; keep module mocks contained (no global
|
||||
mutable state — use `vi.hoisted()` for per-test arrangement); don't mock
|
||||
what you don't own
|
||||
- Wait for reactivity with `await nextTick()` after state changes
|
||||
- Read `docs/testing/<topic>.md` (unit-testing, component-testing,
|
||||
store-testing, vitest-patterns) when writing the corresponding kind of test
|
||||
|
||||
```bash
|
||||
pnpm test:unit # Run all unit tests
|
||||
pnpm test:unit path/to/file # Filter by substring of test file path
|
||||
pnpm test:unit foo.test.ts -t "name" # Filter by test name (it()/test() only)
|
||||
```
|
||||
|
||||
Do not use a `--` separator before vitest args; pnpm forwards extra args
|
||||
automatically, and `--` mangles quoted args (e.g. `-t "two words"`) on Windows
|
||||
PowerShell.
|
||||
|
||||
## Vue Components (`*.vue`)
|
||||
|
||||
The root `AGENTS.md` covers Composition API conventions. Additionally:
|
||||
|
||||
- Prefer `emit`/`@event-name` for state changes; use `defineExpose` only for
|
||||
imperative operations (`form.validate()`, `modal.open()`)
|
||||
- Prefer VueUse composables over manual event handling: `useElementHover`,
|
||||
`useIntersectionObserver`, `useFocusTrap`, `useEventListener`
|
||||
(auto-cleanup). Prefer Vue-native `defineModel` over `useVModel`
|
||||
- Inline Tailwind only — no `<style>` blocks. Exception: third-party libraries
|
||||
that render runtime DOM outside Vue templates (e.g. xterm internals) may use
|
||||
scoped `:deep()` selectors with a brief comment explaining why
|
||||
- Design tokens and Tailwind configuration:
|
||||
`packages/design-system/src/css/style.css`
|
||||
- In unmount hooks, clean up async operations
|
||||
|
||||
## Design Standards
|
||||
|
||||
When adding new UI or changing visual design, fetch the relevant section of
|
||||
the Comfy Design Standards Figma file first — file keys and section node IDs
|
||||
are in `docs/guidance/design-standards.md`. Skip this for refactors, bugfixes,
|
||||
and logic-only changes. If the Figma MCP is unavailable, say so in the PR
|
||||
description instead of skipping silently.
|
||||
|
||||
## Storybook (`*.stories.ts`)
|
||||
|
||||
Place stories alongside their components. Use realistic ComfyUI schemas for
|
||||
mock data (node definitions, components).
|
||||
|
||||
```typescript
|
||||
import type { Meta, StoryObj } from '@storybook/vue3'
|
||||
import ComponentName from './ComponentName.vue'
|
||||
|
||||
const meta: Meta<typeof ComponentName> = {
|
||||
title: 'Category/ComponentName',
|
||||
component: ComponentName,
|
||||
parameters: { layout: 'centered' }
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {}
|
||||
}
|
||||
```
|
||||
|
||||
Variants: always include `Default`; add `Loading` and `Error` if the component
|
||||
fetches data, `Empty` if it renders a collection, `WithData` for realistic
|
||||
content.
|
||||
|
||||
Run with `pnpm storybook` (dev server) or `pnpm build-storybook`.
|
||||
|
||||
Reference in New Issue
Block a user