mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-04 05:02:17 +00:00
Add Playwright tests for async execution
This commit is contained in:
272
browser_tests/tests/previewWithMetadata.spec.ts
Normal file
272
browser_tests/tests/previewWithMetadata.spec.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { expect, mergeTests } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture } from '../fixtures/ComfyPage'
|
||||
import { webSocketFixture } from '../fixtures/ws'
|
||||
import {
|
||||
ExecutionTestHelper,
|
||||
PreviewTestHelper
|
||||
} from '../helpers/ExecutionTestHelper'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
test.describe('Preview with Metadata', () => {
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
let executionHelper: ExecutionTestHelper
|
||||
let previewHelper: PreviewTestHelper
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
executionHelper = new ExecutionTestHelper(comfyPage.page)
|
||||
previewHelper = new PreviewTestHelper(comfyPage.page)
|
||||
})
|
||||
|
||||
test.afterEach(async () => {
|
||||
if (executionHelper) {
|
||||
await executionHelper.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test('Handles b_preview_with_metadata event correctly', async ({
|
||||
comfyPage,
|
||||
ws
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('execution/parallel_async_nodes')
|
||||
|
||||
// Clear any existing previews
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].nodePreviewImages = {}
|
||||
})
|
||||
|
||||
// Set up handler to track preview events and execution
|
||||
await executionHelper.setupEventTracking()
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['__previewHandled'] = false
|
||||
const api = window['app'].api
|
||||
|
||||
// Add handler to track preview events
|
||||
api.addEventListener('b_preview_with_metadata', (event) => {
|
||||
const { displayNodeId, blob } = event.detail
|
||||
// Create URL from the blob in the event
|
||||
const url = URL.createObjectURL(blob)
|
||||
window['app'].nodePreviewImages[displayNodeId] = [url]
|
||||
window['__previewHandled'] = true
|
||||
window['__lastPreviewUrl'] = url
|
||||
})
|
||||
})
|
||||
|
||||
// Start real execution to test event handling in context
|
||||
await comfyPage.executeCommand('Comfy.QueuePrompt')
|
||||
|
||||
// Wait for execution to start
|
||||
await executionHelper.waitForExecutionStart()
|
||||
|
||||
// Trigger b_preview_with_metadata event (simulating what backend would send)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const api = window['app'].api
|
||||
const event = new CustomEvent('b_preview_with_metadata', {
|
||||
detail: {
|
||||
blob: new Blob(['test'], { type: 'image/png' }),
|
||||
nodeId: '2',
|
||||
displayNodeId: '2',
|
||||
parentNodeId: '2',
|
||||
realNodeId: '2',
|
||||
promptId: 'test-prompt-id'
|
||||
}
|
||||
})
|
||||
api.dispatchEvent(event)
|
||||
})
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Wait for preview to be handled
|
||||
await comfyPage.page.waitForFunction(
|
||||
() => window['__previewHandled'] === true,
|
||||
{ timeout: 5000 }
|
||||
)
|
||||
|
||||
// Check that preview was set for the correct node
|
||||
const result = await comfyPage.page.evaluate(() => {
|
||||
return {
|
||||
previewImages: window['app'].nodePreviewImages,
|
||||
lastUrl: window['__lastPreviewUrl']
|
||||
}
|
||||
})
|
||||
|
||||
expect(result.previewImages['2']).toBeDefined()
|
||||
expect(result.previewImages['2']).toHaveLength(1)
|
||||
expect(result.previewImages['2'][0]).toBe(result.lastUrl)
|
||||
})
|
||||
|
||||
test('Clears old previews when new preview arrives', async ({
|
||||
comfyPage,
|
||||
ws
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('execution/parallel_async_nodes')
|
||||
|
||||
// Set up initial preview
|
||||
const initialBlobUrl = await comfyPage.page.evaluate(() => {
|
||||
const blob = new Blob(['initial image'], { type: 'image/png' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
window['app'].nodePreviewImages['3'] = [url]
|
||||
return url
|
||||
})
|
||||
|
||||
// Create spy to track URL revocations
|
||||
await previewHelper.setupPreviewTracking()
|
||||
|
||||
// Mock the handler to revoke old previews
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const api = window['app'].api
|
||||
api.addEventListener('b_preview_with_metadata', (event) => {
|
||||
const { displayNodeId } = event.detail
|
||||
window['app'].revokePreviews(displayNodeId)
|
||||
const newBlob = new Blob(['new image'], { type: 'image/png' })
|
||||
const newUrl = URL.createObjectURL(newBlob)
|
||||
window['app'].nodePreviewImages[displayNodeId] = [newUrl]
|
||||
})
|
||||
})
|
||||
|
||||
// Trigger new preview for same node
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const api = window['app'].api
|
||||
const event = new CustomEvent('b_preview_with_metadata', {
|
||||
detail: {
|
||||
blob: new Blob(['new image'], { type: 'image/png' }),
|
||||
nodeId: '3',
|
||||
displayNodeId: '3',
|
||||
parentNodeId: '3',
|
||||
realNodeId: '3',
|
||||
promptId: 'test-prompt-id'
|
||||
}
|
||||
})
|
||||
api.dispatchEvent(event)
|
||||
})
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Check that old URL was revoked
|
||||
const finalRevokedUrls = await previewHelper.getRevokedUrls()
|
||||
expect(finalRevokedUrls).toContain(initialBlobUrl)
|
||||
|
||||
// Check that new preview replaced old one
|
||||
const newPreviewImages = await previewHelper.getNodePreviews('3')
|
||||
|
||||
expect(newPreviewImages).toHaveLength(1)
|
||||
expect(newPreviewImages[0]).not.toBe(initialBlobUrl)
|
||||
})
|
||||
|
||||
test('Associates preview with correct display node in subgraph', async ({
|
||||
comfyPage,
|
||||
ws
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('execution/parallel_async_nodes')
|
||||
|
||||
// Mock handler that stores metadata
|
||||
await previewHelper.setupPreviewTracking()
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['__previewMetadata'] = {}
|
||||
const api = window['app'].api
|
||||
api.addEventListener('b_preview_with_metadata', (event) => {
|
||||
const { displayNodeId, nodeId, parentNodeId, realNodeId, promptId } =
|
||||
event.detail
|
||||
window['__previewMetadata'][displayNodeId] = {
|
||||
nodeId,
|
||||
displayNodeId,
|
||||
parentNodeId,
|
||||
realNodeId,
|
||||
promptId
|
||||
}
|
||||
// Still create the preview
|
||||
const url = URL.createObjectURL(event.detail.blob)
|
||||
window['app'].nodePreviewImages[displayNodeId] = [url]
|
||||
})
|
||||
})
|
||||
|
||||
// Simulate preview from a subgraph node
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const api = window['app'].api
|
||||
const event = new CustomEvent('b_preview_with_metadata', {
|
||||
detail: {
|
||||
blob: new Blob(['subgraph preview'], { type: 'image/png' }),
|
||||
nodeId: '10:5:3', // Nested execution ID
|
||||
displayNodeId: '10', // Top-level display node
|
||||
parentNodeId: '10:5', // Parent subgraph
|
||||
realNodeId: '3', // Actual node ID within subgraph
|
||||
promptId: 'test-prompt-id'
|
||||
}
|
||||
})
|
||||
api.dispatchEvent(event)
|
||||
})
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Check that preview is associated with display node
|
||||
const metadata = await comfyPage.page.evaluate(
|
||||
() => window['__previewMetadata']
|
||||
)
|
||||
expect(metadata['10']).toBeDefined()
|
||||
expect(metadata['10'].nodeId).toBe('10:5:3')
|
||||
expect(metadata['10'].displayNodeId).toBe('10')
|
||||
expect(metadata['10'].parentNodeId).toBe('10:5')
|
||||
expect(metadata['10'].realNodeId).toBe('3')
|
||||
|
||||
// Check that preview exists for display node
|
||||
const previews = await comfyPage.page.evaluate(
|
||||
() => window['app'].nodePreviewImages
|
||||
)
|
||||
expect(previews['10']).toBeDefined()
|
||||
expect(previews['10']).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('Maintains backward compatibility with b_preview event', async ({
|
||||
comfyPage,
|
||||
ws
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('execution/parallel_async_nodes')
|
||||
|
||||
// Track both events
|
||||
const eventsFired = await comfyPage.page.evaluate(() => {
|
||||
const events: string[] = []
|
||||
const api = window['app'].api
|
||||
|
||||
api.addEventListener('b_preview', () => {
|
||||
events.push('b_preview')
|
||||
})
|
||||
|
||||
api.addEventListener('b_preview_with_metadata', () => {
|
||||
events.push('b_preview_with_metadata')
|
||||
})
|
||||
|
||||
window['__eventsFired'] = events
|
||||
return events
|
||||
})
|
||||
|
||||
// Trigger b_preview_with_metadata
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const api = window['app'].api
|
||||
const blob = new Blob(['test image'], { type: 'image/png' })
|
||||
|
||||
// Simulate the API behavior
|
||||
api.dispatchCustomEvent('b_preview_with_metadata', {
|
||||
blob,
|
||||
nodeId: '2',
|
||||
displayNodeId: '2',
|
||||
parentNodeId: '2',
|
||||
realNodeId: '2',
|
||||
promptId: 'test-prompt-id'
|
||||
})
|
||||
|
||||
// Also dispatch legacy event as the API would
|
||||
api.dispatchCustomEvent('b_preview', blob)
|
||||
})
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Check that both events were fired
|
||||
const finalEvents = await comfyPage.page.evaluate(
|
||||
() => window['__eventsFired']
|
||||
)
|
||||
expect(finalEvents).toContain('b_preview_with_metadata')
|
||||
expect(finalEvents).toContain('b_preview')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user