mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 02:32:18 +00:00
refactor: deduplicate skill docs, add idiomatic retry patterns
Amp-Thread-ID: https://ampcode.com/threads/T-019c165d-cebf-7393-847a-914e5858bd9a Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -57,6 +57,7 @@ const slot = node.getOutputSlot('MODEL')
|
|||||||
| **API mocking** | [testing/mocking.md](testing/mocking.md) |
|
| **API mocking** | [testing/mocking.md](testing/mocking.md) |
|
||||||
| **Test assets** | [testing/assets.md](testing/assets.md) |
|
| **Test assets** | [testing/assets.md](testing/assets.md) |
|
||||||
| **Debug flaky tests** | [reference/debugging.md](reference/debugging.md) |
|
| **Debug flaky tests** | [reference/debugging.md](reference/debugging.md) |
|
||||||
|
| **Async retry patterns** | [reference/debugging.md](reference/debugging.md#retry-patterns) |
|
||||||
| **All fixture methods** | [reference/fixtures.md](reference/fixtures.md) |
|
| **All fixture methods** | [reference/fixtures.md](reference/fixtures.md) |
|
||||||
| **Quick cheatsheet** | [reference/cheatsheet.md](reference/cheatsheet.md) |
|
| **Quick cheatsheet** | [reference/cheatsheet.md](reference/cheatsheet.md) |
|
||||||
|
|
||||||
|
|||||||
@@ -82,23 +82,7 @@ await comfyPage.nextFrame()
|
|||||||
|
|
||||||
## Connecting Nodes
|
## Connecting Nodes
|
||||||
|
|
||||||
```typescript
|
See [nodes.md](nodes.md#connect-slots) for node connection patterns.
|
||||||
// Get output slot
|
|
||||||
const outputNode = comfyPage.getNodeRefByTitle('CLIP Loader')
|
|
||||||
const outputSlot = outputNode.getOutputSlot('CLIP')
|
|
||||||
|
|
||||||
// Get input slot
|
|
||||||
const inputNode = comfyPage.getNodeRefByTitle('CLIP Text Encode')
|
|
||||||
const inputSlot = inputNode.getInputSlot('clip')
|
|
||||||
|
|
||||||
// Connect via drag
|
|
||||||
await comfyMouse.dragFromTo(
|
|
||||||
await outputSlot.getPosition(),
|
|
||||||
await inputSlot.getPosition(),
|
|
||||||
{ steps: 10 }
|
|
||||||
)
|
|
||||||
await comfyPage.nextFrame()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Screenshot Testing
|
## Screenshot Testing
|
||||||
|
|
||||||
|
|||||||
@@ -85,31 +85,15 @@ await comfyPage.nextFrame()
|
|||||||
|
|
||||||
## Common Gotchas
|
## Common Gotchas
|
||||||
|
|
||||||
### 1. Missing `nextFrame()`
|
See [debugging.md](../reference/debugging.md) for detailed fixes.
|
||||||
|
|
||||||
Canvas changes don't render immediately:
|
| Issue | Solution | Details |
|
||||||
|
|-------|----------|---------|
|
||||||
```typescript
|
| Canvas not updating | Add `nextFrame()` after canvas ops | [canvas.md](canvas.md#critical-always-use-nextframe) |
|
||||||
await comfyPage.canvas.click(100, 200)
|
| Double-click unreliable | Use `{ delay: 5 }` option | [canvas.md](canvas.md#click-operations) |
|
||||||
await comfyPage.nextFrame() // ← Required!
|
| Screenshot mismatch | Linux-only, use PR label | [debugging.md](../reference/debugging.md#debugging-screenshots) |
|
||||||
```
|
| Keyboard not working | Focus canvas first | [canvas.md](canvas.md#focus-before-keyboard) |
|
||||||
|
| Flaky async assertions | Use `expect.poll()` or `toPass()` | [debugging.md](../reference/debugging.md#retry-patterns) |
|
||||||
### 2. Double-Click Reliability
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
await element.dblclick({ delay: 5 })
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Screenshot Tests Are Linux-Only
|
|
||||||
|
|
||||||
Don't commit local screenshots. Use `New Browser Test Expectations` PR label.
|
|
||||||
|
|
||||||
### 4. Focus Before Keyboard
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
await comfyPage.canvas.focus()
|
|
||||||
await comfyPage.page.keyboard.press('Delete')
|
|
||||||
```
|
|
||||||
|
|
||||||
## Fresh Page Setup
|
## Fresh Page Setup
|
||||||
|
|
||||||
|
|||||||
@@ -14,18 +14,9 @@ await comfyPage.loadWorkflow('nodes/reroute')
|
|||||||
await comfyPage.loadWorkflow('canvas/pan_zoom')
|
await comfyPage.loadWorkflow('canvas/pan_zoom')
|
||||||
```
|
```
|
||||||
|
|
||||||
### Asset Directory Structure
|
### Asset Organization
|
||||||
|
|
||||||
```
|
See [assets.md](../testing/assets.md) for full directory structure and best practices.
|
||||||
browser_tests/assets/
|
|
||||||
├── default.json # Basic starting workflow
|
|
||||||
├── canvas/ # Canvas state tests
|
|
||||||
├── groups/ # Group-related workflows
|
|
||||||
├── nodes/ # Node-specific workflows
|
|
||||||
├── widgets/ # Widget test workflows
|
|
||||||
├── workflows/ # Complex workflow scenarios
|
|
||||||
└── images/ # Image assets for drag-drop
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating New Assets
|
### Creating New Assets
|
||||||
|
|
||||||
|
|||||||
@@ -99,15 +99,16 @@ const fixture = await comfyPage.vueNodes.getFixtureByTitle('Load Checkpoint')
|
|||||||
// Fixture maintains stable reference even if title changes
|
// Fixture maintains stable reference even if title changes
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Vue Nodes Settings
|
## Vue Nodes Settings
|
||||||
|
|
||||||
|
The essential setting for Vue Nodes:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
|
||||||
await comfyPage.setSetting('Comfy.Minimap.Visible', false)
|
|
||||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See [setup.md](../core/setup.md#common-settings) for other common settings.
|
||||||
|
|
||||||
## Example: Complete Vue Node Test
|
## Example: Complete Vue Node Test
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
@@ -210,18 +210,9 @@ await comfyPage.page.waitForFunction(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tags Quick Reference
|
## Tags
|
||||||
|
|
||||||
| Tag | Purpose | Run with |
|
See [setup.md](../core/setup.md#test-tags) for tag definitions.
|
||||||
| ------------- | -------------------- | ------------------------- |
|
|
||||||
| `@smoke` | Fast essential tests | `--grep @smoke` |
|
|
||||||
| `@slow` | Long-running tests | `--grep-invert @slow` |
|
|
||||||
| `@screenshot` | Visual regression | `--grep @screenshot` |
|
|
||||||
| `@mobile` | Mobile viewport | `--project=mobile-chrome` |
|
|
||||||
| `@2x` | HiDPI scale | `--project=chromium-2x` |
|
|
||||||
| `@canvas` | Canvas tests | `--grep @canvas` |
|
|
||||||
| `@node` | Node tests | `--grep @node` |
|
|
||||||
| `@widget` | Widget tests | `--grep @widget` |
|
|
||||||
|
|
||||||
## Run Commands
|
## Run Commands
|
||||||
|
|
||||||
|
|||||||
@@ -115,15 +115,55 @@ DEBUG=pw:api npx playwright test
|
|||||||
pnpm exec playwright test --ui
|
pnpm exec playwright test --ui
|
||||||
```
|
```
|
||||||
|
|
||||||
## Retry Pattern
|
## Retry Patterns
|
||||||
|
|
||||||
For inherently async operations:
|
Playwright provides two idiomatic approaches for async assertions.
|
||||||
|
|
||||||
|
### expect.poll() - Preferred for Single Values
|
||||||
|
|
||||||
|
Use when polling a single value until it matches:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Poll until node count matches
|
||||||
|
await expect
|
||||||
|
.poll(() => comfyPage.getGraphNodesCount(), { timeout: 3000 })
|
||||||
|
.toBe(5)
|
||||||
|
|
||||||
|
// Poll with custom intervals
|
||||||
|
await expect
|
||||||
|
.poll(() => widget.getValue(), { intervals: [100, 200, 500], timeout: 2000 })
|
||||||
|
.toBe('expected')
|
||||||
|
```
|
||||||
|
|
||||||
|
### expect().toPass() - For Multiple Assertions
|
||||||
|
|
||||||
|
Use when multiple conditions must be true together:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await expect(async () => {
|
await expect(async () => {
|
||||||
const value = await widget.getValue()
|
expect(await input.getWidget(0).getValue()).toBe('foo')
|
||||||
expect(value).toBe(100)
|
expect(await output1.getWidget(0).getValue()).toBe('foo')
|
||||||
}).toPass({ timeout: 2000 })
|
expect(await output2.getWidget(0).getValue()).toBe('')
|
||||||
|
}).toPass({ timeout: 2_000 })
|
||||||
|
```
|
||||||
|
|
||||||
|
### When to Use Each
|
||||||
|
|
||||||
|
| Pattern | Use Case |
|
||||||
|
|---------|----------|
|
||||||
|
| `expect.poll()` | Single value polling, cleaner syntax |
|
||||||
|
| `expect().toPass()` | Multiple assertions that must all pass |
|
||||||
|
| `locator.waitFor()` | Waiting for element state changes |
|
||||||
|
| Auto-retrying assertions | `toBeVisible()`, `toHaveText()`, etc. |
|
||||||
|
|
||||||
|
### ❌ Never Use waitForTimeout
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ Bad - arbitrary delay, flaky
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
|
// ✅ Good - wait for actual condition
|
||||||
|
await expect.poll(() => getData()).toBe(expected)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Debugging Screenshots
|
## Debugging Screenshots
|
||||||
|
|||||||
@@ -118,20 +118,7 @@ const result = await comfyPage.page.evaluate(() => {
|
|||||||
|
|
||||||
## WebSocket Mocking
|
## WebSocket Mocking
|
||||||
|
|
||||||
Mock WebSocket status updates:
|
See [mocking.md](mocking.md#websocket-mocking) for WebSocket patterns.
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Get WebSocket fixture
|
|
||||||
const { websocket } = await comfyPage.getWebSocket()
|
|
||||||
|
|
||||||
// Send mock status
|
|
||||||
await websocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
type: 'status',
|
|
||||||
data: { exec_info: { queue_remaining: 0 } }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vue Node Testing
|
## Vue Node Testing
|
||||||
|
|
||||||
|
|||||||
@@ -124,18 +124,6 @@ test('should upload image file', async ({ comfyPage }) => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
## Organizing Test Assets
|
## Asset Organization
|
||||||
|
|
||||||
Assets should be organized by feature:
|
See [assets.md](assets.md) for directory structure and best practices.
|
||||||
|
|
||||||
```
|
|
||||||
browser_tests/assets/
|
|
||||||
├── widgets/ # Widget-specific workflows
|
|
||||||
│ ├── load_image_widget.json
|
|
||||||
│ └── boolean_widget.json
|
|
||||||
├── workflowInMedia/ # Files with embedded workflows
|
|
||||||
├── nodes/ # Node-specific workflows
|
|
||||||
└── image32x32.webp # Shared image assets
|
|
||||||
```
|
|
||||||
|
|
||||||
See [patterns/assets.md](assets.md) for full asset organization guide.
|
|
||||||
|
|||||||
@@ -77,19 +77,20 @@ await expect(widget.locator).toBeVisible()
|
|||||||
|
|
||||||
### 1. Wait for Value Change
|
### 1. Wait for Value Change
|
||||||
|
|
||||||
Widget values may not update instantly:
|
Widget values may not update instantly. Use retry patterns:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await widget.setValue(100)
|
await widget.setValue(100)
|
||||||
await comfyPage.nextFrame()
|
await comfyPage.nextFrame()
|
||||||
|
|
||||||
// Retry assertion
|
// Use poll for single value
|
||||||
await expect(async () => {
|
await expect
|
||||||
const value = await widget.getValue()
|
.poll(() => widget.getValue(), { timeout: 2000 })
|
||||||
expect(value).toBe(100)
|
.toBe(100)
|
||||||
}).toPass({ timeout: 2000 })
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See [debugging.md](../reference/debugging.md#retry-patterns) for more retry patterns.
|
||||||
|
|
||||||
### 2. Combo Widget Selection
|
### 2. Combo Widget Selection
|
||||||
|
|
||||||
Click-based selection is more reliable than setValue for combos:
|
Click-based selection is more reliable than setValue for combos:
|
||||||
|
|||||||
Reference in New Issue
Block a user