mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 05:49:54 +00:00
Add additional wait after closing the dialog to ensure all async operations complete before continuing with the test. This prevents race conditions where the dialog might not be fully closed when the test proceeds. The test was failing intermittently because closeDialog() waits for the dialog to be hidden, but there may be additional async state updates that need to complete after the dialog closes. Fixes flaky test in dialog.spec.ts:33
369 lines
12 KiB
TypeScript
369 lines
12 KiB
TypeScript
import { Locator, expect } from '@playwright/test'
|
|
|
|
import type { Keybinding } from '../../src/schemas/keyBindingSchema'
|
|
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
|
|
|
test.describe('Load workflow warning', () => {
|
|
test('Should display a warning when loading a workflow with missing nodes', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.loadWorkflow('missing/missing_nodes')
|
|
|
|
// Wait for the element with the .comfy-missing-nodes selector to be visible
|
|
const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes')
|
|
await expect(missingNodesWarning).toBeVisible()
|
|
})
|
|
|
|
test('Should display a warning when loading a workflow with missing nodes in subgraphs', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.loadWorkflow('missing/missing_nodes_in_subgraph')
|
|
|
|
// Wait for the element with the .comfy-missing-nodes selector to be visible
|
|
const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes')
|
|
await expect(missingNodesWarning).toBeVisible()
|
|
|
|
// Verify the missing node text includes subgraph context
|
|
const warningText = await missingNodesWarning.textContent()
|
|
expect(warningText).toContain('MISSING_NODE_TYPE_IN_SUBGRAPH')
|
|
expect(warningText).toContain('in subgraph')
|
|
})
|
|
})
|
|
|
|
test('Does not report warning on undo/redo', async ({ comfyPage }) => {
|
|
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
|
|
|
await comfyPage.loadWorkflow('missing/missing_nodes')
|
|
await comfyPage.closeDialog()
|
|
|
|
// Wait for any async operations to complete after dialog closes
|
|
await comfyPage.nextFrame()
|
|
await comfyPage.page.waitForTimeout(100)
|
|
|
|
// Make a change to the graph
|
|
await comfyPage.doubleClickCanvas()
|
|
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
|
|
|
// Undo and redo the change
|
|
await comfyPage.ctrlZ()
|
|
await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
|
|
await comfyPage.ctrlY()
|
|
await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
|
|
})
|
|
|
|
test.describe('Execution error', () => {
|
|
test('Should display an error message when an execution error occurs', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.loadWorkflow('nodes/execution_error')
|
|
await comfyPage.queueButton.click()
|
|
await comfyPage.nextFrame()
|
|
|
|
// Wait for the element with the .comfy-execution-error selector to be visible
|
|
const executionError = comfyPage.page.locator('.comfy-error-report')
|
|
await expect(executionError).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test.describe('Missing models warning', () => {
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.setSetting('Comfy.Workflow.ShowMissingModelsWarning', true)
|
|
await comfyPage.page.evaluate((url: string) => {
|
|
return fetch(`${url}/api/devtools/cleanup_fake_model`)
|
|
}, comfyPage.url)
|
|
})
|
|
|
|
test('Should display a warning when missing models are found', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.loadWorkflow('missing/missing_models')
|
|
|
|
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
|
|
await expect(missingModelsWarning).toBeVisible()
|
|
|
|
const downloadButton = missingModelsWarning.getByLabel('Download')
|
|
await expect(downloadButton).toBeVisible()
|
|
})
|
|
|
|
test('Should display a warning when missing models are found in node properties', async ({
|
|
comfyPage
|
|
}) => {
|
|
// Load workflow that has a node with models metadata at the node level
|
|
await comfyPage.loadWorkflow('missing/missing_models_from_node_properties')
|
|
|
|
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
|
|
await expect(missingModelsWarning).toBeVisible()
|
|
|
|
const downloadButton = missingModelsWarning.getByLabel('Download')
|
|
await expect(downloadButton).toBeVisible()
|
|
})
|
|
|
|
test('Should not display a warning when no missing models are found', async ({
|
|
comfyPage
|
|
}) => {
|
|
const modelFoldersRes = {
|
|
status: 200,
|
|
body: JSON.stringify([
|
|
{
|
|
name: 'text_encoders',
|
|
folders: ['ComfyUI/models/text_encoders']
|
|
}
|
|
])
|
|
}
|
|
await comfyPage.page.route(
|
|
'**/api/experiment/models',
|
|
(route) => route.fulfill(modelFoldersRes),
|
|
{ times: 1 }
|
|
)
|
|
|
|
// Reload page to trigger indexing of model folders
|
|
await comfyPage.setup()
|
|
|
|
const clipModelsRes = {
|
|
status: 200,
|
|
body: JSON.stringify([
|
|
{
|
|
name: 'fake_model.safetensors',
|
|
pathIndex: 0
|
|
}
|
|
])
|
|
}
|
|
await comfyPage.page.route(
|
|
'**/api/experiment/models/text_encoders',
|
|
(route) => route.fulfill(clipModelsRes),
|
|
{ times: 1 }
|
|
)
|
|
|
|
await comfyPage.loadWorkflow('missing/missing_models')
|
|
|
|
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
|
|
await expect(missingModelsWarning).not.toBeVisible()
|
|
})
|
|
|
|
test('Should not display warning when model metadata exists but widget values have changed', async ({
|
|
comfyPage
|
|
}) => {
|
|
// This tests the scenario where outdated model metadata exists in the workflow
|
|
// but the actual selected models (widget values) have changed
|
|
await comfyPage.loadWorkflow('missing/model_metadata_widget_mismatch')
|
|
|
|
// The missing models warning should NOT appear
|
|
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
|
|
await expect(missingModelsWarning).not.toBeVisible()
|
|
})
|
|
|
|
// Flaky test after parallelization
|
|
// https://github.com/Comfy-Org/ComfyUI_frontend/pull/1400
|
|
test.skip('Should download missing model when clicking download button', async ({
|
|
comfyPage
|
|
}) => {
|
|
// The fake_model.safetensors is served by
|
|
// https://github.com/Comfy-Org/ComfyUI_devtools/blob/main/__init__.py
|
|
await comfyPage.loadWorkflow('missing/missing_models')
|
|
|
|
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
|
|
await expect(missingModelsWarning).toBeVisible()
|
|
|
|
const downloadButton = comfyPage.page.getByLabel('Download')
|
|
await expect(downloadButton).toBeVisible()
|
|
const downloadPromise = comfyPage.page.waitForEvent('download')
|
|
await downloadButton.click()
|
|
|
|
const download = await downloadPromise
|
|
expect(download.suggestedFilename()).toBe('fake_model.safetensors')
|
|
})
|
|
|
|
test.describe('Do not show again checkbox', () => {
|
|
let checkbox: Locator
|
|
let closeButton: Locator
|
|
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.setSetting(
|
|
'Comfy.Workflow.ShowMissingModelsWarning',
|
|
true
|
|
)
|
|
await comfyPage.loadWorkflow('missing/missing_models')
|
|
|
|
checkbox = comfyPage.page.getByLabel("Don't show this again")
|
|
closeButton = comfyPage.page.getByLabel('Close')
|
|
})
|
|
|
|
test('Should disable warning dialog when checkbox is checked', async ({
|
|
comfyPage
|
|
}) => {
|
|
await checkbox.click()
|
|
const changeSettingPromise = comfyPage.page.waitForRequest(
|
|
'**/api/settings/Comfy.Workflow.ShowMissingModelsWarning'
|
|
)
|
|
await closeButton.click()
|
|
await changeSettingPromise
|
|
|
|
const settingValue = await comfyPage.getSetting(
|
|
'Comfy.Workflow.ShowMissingModelsWarning'
|
|
)
|
|
expect(settingValue).toBe(false)
|
|
})
|
|
|
|
test('Should keep warning dialog enabled when checkbox is unchecked', async ({
|
|
comfyPage
|
|
}) => {
|
|
await closeButton.click()
|
|
|
|
const settingValue = await comfyPage.getSetting(
|
|
'Comfy.Workflow.ShowMissingModelsWarning'
|
|
)
|
|
expect(settingValue).toBe(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
test.describe('Settings', () => {
|
|
test('@mobile Should be visible on mobile', async ({ comfyPage }) => {
|
|
await comfyPage.page.keyboard.press('Control+,')
|
|
const settingsContent = comfyPage.page.locator('.settings-content')
|
|
await expect(settingsContent).toBeVisible()
|
|
const isUsableHeight = await settingsContent.evaluate(
|
|
(el) => el.clientHeight > 30
|
|
)
|
|
expect(isUsableHeight).toBeTruthy()
|
|
})
|
|
|
|
test('Can open settings with hotkey', async ({ comfyPage }) => {
|
|
await comfyPage.page.keyboard.down('ControlOrMeta')
|
|
await comfyPage.page.keyboard.press(',')
|
|
await comfyPage.page.keyboard.up('ControlOrMeta')
|
|
const settingsLocator = comfyPage.page.locator('.settings-container')
|
|
await expect(settingsLocator).toBeVisible()
|
|
await comfyPage.page.keyboard.press('Escape')
|
|
await expect(settingsLocator).not.toBeVisible()
|
|
})
|
|
|
|
test('Can change canvas zoom speed setting', async ({ comfyPage }) => {
|
|
const maxSpeed = 2.5
|
|
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', maxSpeed)
|
|
await test.step('Setting should persist', async () => {
|
|
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
|
|
})
|
|
})
|
|
|
|
test('Should persist keybinding setting', async ({ comfyPage }) => {
|
|
// Open the settings dialog
|
|
await comfyPage.page.keyboard.press('Control+,')
|
|
await comfyPage.page.waitForSelector('.settings-container')
|
|
|
|
// Open the keybinding tab
|
|
await comfyPage.page.getByLabel('Keybinding').click()
|
|
await comfyPage.page.waitForSelector(
|
|
'[placeholder="Search Keybindings..."]'
|
|
)
|
|
|
|
// Focus the 'New Blank Workflow' row
|
|
const newBlankWorkflowRow = comfyPage.page.locator('tr', {
|
|
has: comfyPage.page.getByRole('cell', { name: 'New Blank Workflow' })
|
|
})
|
|
await newBlankWorkflowRow.click()
|
|
|
|
// Click edit button
|
|
const editKeybindingButton = newBlankWorkflowRow.locator('.pi-pencil')
|
|
await editKeybindingButton.click()
|
|
|
|
// Set new keybinding
|
|
const input = comfyPage.page.getByPlaceholder('Press keys for new binding')
|
|
await input.press('Alt+n')
|
|
|
|
const requestPromise = comfyPage.page.waitForRequest(
|
|
'**/api/settings/Comfy.Keybinding.NewBindings'
|
|
)
|
|
|
|
// Save keybinding
|
|
const saveButton = comfyPage.page
|
|
.getByLabel('New Blank Workflow')
|
|
.getByLabel('Save')
|
|
await saveButton.click()
|
|
|
|
const request = await requestPromise
|
|
const expectedSetting: Keybinding = {
|
|
commandId: 'Comfy.NewBlankWorkflow',
|
|
combo: {
|
|
key: 'n',
|
|
ctrl: false,
|
|
alt: true,
|
|
shift: false
|
|
}
|
|
}
|
|
expect(request.postData()).toContain(JSON.stringify(expectedSetting))
|
|
})
|
|
})
|
|
|
|
test.describe('Support', () => {
|
|
test('Should open external zendesk link', async ({ comfyPage }) => {
|
|
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
|
const pagePromise = comfyPage.page.context().waitForEvent('page')
|
|
await comfyPage.menu.topbar.triggerTopbarCommand(['Help', 'Support'])
|
|
const newPage = await pagePromise
|
|
|
|
await newPage.waitForLoadState('networkidle')
|
|
await expect(newPage).toHaveURL(/.*support\.comfy\.org.*/)
|
|
await newPage.close()
|
|
})
|
|
})
|
|
|
|
test.describe('Error dialog', () => {
|
|
test('Should display an error dialog when graph configure fails', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.page.evaluate(() => {
|
|
const graph = window['graph']
|
|
graph.configure = () => {
|
|
throw new Error('Error on configure!')
|
|
}
|
|
})
|
|
|
|
await comfyPage.loadWorkflow('default')
|
|
|
|
const errorDialog = comfyPage.page.locator('.comfy-error-report')
|
|
await expect(errorDialog).toBeVisible()
|
|
})
|
|
|
|
test('Should display an error dialog when prompt execution fails', async ({
|
|
comfyPage
|
|
}) => {
|
|
await comfyPage.page.evaluate(async () => {
|
|
const app = window['app']
|
|
app.api.queuePrompt = () => {
|
|
throw new Error('Error on queuePrompt!')
|
|
}
|
|
await app.queuePrompt(0)
|
|
})
|
|
const errorDialog = comfyPage.page.locator('.comfy-error-report')
|
|
await expect(errorDialog).toBeVisible()
|
|
})
|
|
})
|
|
|
|
test.describe('Signin dialog', () => {
|
|
test('Paste content to signin dialog should not paste node on canvas', async ({
|
|
comfyPage
|
|
}) => {
|
|
const nodeNum = (await comfyPage.getNodes()).length
|
|
await comfyPage.clickEmptyLatentNode()
|
|
await comfyPage.ctrlC()
|
|
|
|
const textBox = comfyPage.widgetTextBox
|
|
await textBox.click()
|
|
await textBox.fill('test_password')
|
|
await textBox.press('Control+a')
|
|
await textBox.press('Control+c')
|
|
|
|
await comfyPage.page.evaluate(() => {
|
|
void window['app'].extensionManager.dialog.showSignInDialog()
|
|
})
|
|
|
|
const input = comfyPage.page.locator('#comfy-org-sign-in-password')
|
|
await input.waitFor({ state: 'visible' })
|
|
await input.press('Control+v')
|
|
await expect(input).toHaveValue('test_password')
|
|
|
|
expect(await comfyPage.getNodes()).toHaveLength(nodeNum)
|
|
})
|
|
})
|