mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 06:10:32 +00:00
## Summary Add Playwright e2e tests for the node replacement feature (swap nodes UI in the errors tab). ## Changes - **What**: 6 e2e test cases across two describe blocks covering single and multi-type node replacement flows. Tests verify swap nodes group visibility, in-place replacement, widget value preservation, Replace All across multiple types, output connection preservation, and success toast display. Includes typed mock data for `/api/node_replacements` and two workflow fixture files with fake missing node types mapped to real core nodes. ## Review Focus - Mock setup pattern in `setupNodeReplacement` — enables feature flag via `page.evaluate` and routes the API endpoint - Workflow fixture design — uses fake node types (E2E_OldSampler, E2E_OldUpscaler) that map to real registered types (KSampler, ImageScaleBy) - Assertion coverage for link preservation after replacement ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11242-test-add-e2e-tests-for-node-replacement-flows-3426d73d3650811e87d7f0d96fd66433) by [Unito](https://www.unito.io) --------- Co-authored-by: Connor Byrne <c.byrne@comfy.org>
94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
import type { Locator, Page } from '@playwright/test'
|
|
|
|
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
|
import { TestIds } from '@e2e/fixtures/selectors'
|
|
import type { NodeReplacementResponse } from '@/platform/nodeReplacement/types'
|
|
|
|
/**
|
|
* Mock `/api/node_replacements` and enable the node replacement feature.
|
|
*
|
|
* Unlike features that only consult settings (e.g. shareWorkflowDialog,
|
|
* managerDialog), node replacement gates on `api.serverFeatureFlags`. The
|
|
* server sends a `feature_flags` WS message that wholesale replaces
|
|
* `serverFeatureFlags`, racing with any test-side override done via
|
|
* `page.evaluate`. To make the flow deterministic across CI shards, this
|
|
* helper patches `WebSocket.prototype` so every incoming `feature_flags`
|
|
* message has `node_replacements: true` injected before the api's WS
|
|
* handler sees it. Reload the page so the patched WebSocket and persisted
|
|
* settings apply to a fresh app boot, then wait for the resulting
|
|
* `/api/node_replacements` fetch before returning.
|
|
*/
|
|
export async function setupNodeReplacement(
|
|
comfyPage: ComfyPage,
|
|
replacements: NodeReplacementResponse
|
|
): Promise<void> {
|
|
await comfyPage.page.route('**/api/node_replacements', (route) =>
|
|
route.fulfill({ json: replacements })
|
|
)
|
|
|
|
await comfyPage.settings.setSetting(
|
|
'Comfy.RightSidePanel.ShowErrorsTab',
|
|
true
|
|
)
|
|
await comfyPage.settings.setSetting('Comfy.NodeReplacement.Enabled', true)
|
|
|
|
await comfyPage.page.addInitScript(() => {
|
|
const proto = window.WebSocket.prototype
|
|
const originalAdd = proto.addEventListener
|
|
proto.addEventListener = function patchedAdd(
|
|
this: WebSocket,
|
|
type: string,
|
|
listener: EventListenerOrEventListenerObject | null,
|
|
options?: AddEventListenerOptions | boolean
|
|
) {
|
|
if (type === 'message' && typeof listener === 'function') {
|
|
const wrapped = function (this: WebSocket, event: Event) {
|
|
const msgEvent = event as MessageEvent
|
|
if (typeof msgEvent.data === 'string') {
|
|
try {
|
|
const msg = JSON.parse(msgEvent.data)
|
|
if (
|
|
msg &&
|
|
msg.type === 'feature_flags' &&
|
|
msg.data &&
|
|
typeof msg.data === 'object'
|
|
) {
|
|
msg.data.node_replacements = true
|
|
const patched = new MessageEvent('message', {
|
|
data: JSON.stringify(msg),
|
|
origin: msgEvent.origin,
|
|
lastEventId: msgEvent.lastEventId
|
|
})
|
|
return (listener as EventListener).call(this, patched)
|
|
}
|
|
} catch {
|
|
// not JSON or not a feature_flags message - pass through
|
|
}
|
|
}
|
|
return (listener as EventListener).call(this, event)
|
|
}
|
|
return originalAdd.call(this, type, wrapped as EventListener, options)
|
|
}
|
|
return originalAdd.call(
|
|
this,
|
|
type,
|
|
listener as EventListenerOrEventListenerObject,
|
|
options
|
|
)
|
|
}
|
|
})
|
|
|
|
const fetchPromise = comfyPage.page.waitForResponse(
|
|
(response) =>
|
|
response.url().includes('/api/node_replacements') && response.ok(),
|
|
{ timeout: 10000 }
|
|
)
|
|
|
|
await comfyPage.workflow.reloadAndWaitForApp()
|
|
await fetchPromise
|
|
}
|
|
|
|
export function getSwapNodesGroup(page: Page): Locator {
|
|
return page.getByTestId(TestIds.dialogs.swapNodesGroup)
|
|
}
|