mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 08:44:06 +00:00
fix(browser_tests): remove all @ts-expect-error and type assertions
This commit is contained in:
@@ -160,7 +160,7 @@ class NodeSlotReference {
|
||||
const convertedPos = app.canvas.ds.convertOffsetToCanvas(rawPos)
|
||||
|
||||
// Debug logging - convert Float64Arrays to regular arrays for visibility
|
||||
|
||||
|
||||
console.log(
|
||||
`NodeSlotReference debug for ${type} slot ${index} on node ${id}:`,
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ import { backupPath } from './utils/backupUtils'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export default function globalSetup(config: FullConfig) {
|
||||
export default function globalSetup(_config: FullConfig) {
|
||||
if (!process.env.CI) {
|
||||
if (process.env.TEST_COMFYUI_DIR) {
|
||||
backupPath([process.env.TEST_COMFYUI_DIR, 'user'])
|
||||
|
||||
@@ -5,7 +5,7 @@ import { restorePath } from './utils/backupUtils'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export default function globalTeardown(config: FullConfig) {
|
||||
export default function globalTeardown(_config: FullConfig) {
|
||||
if (!process.env.CI && process.env.TEST_COMFYUI_DIR) {
|
||||
restorePath([process.env.TEST_COMFYUI_DIR, 'user'])
|
||||
restorePath([process.env.TEST_COMFYUI_DIR, 'models'])
|
||||
|
||||
@@ -42,13 +42,25 @@ class ComfyQueueButtonOptions {
|
||||
|
||||
public async setMode(mode: AutoQueueMode) {
|
||||
await this.page.evaluate((mode) => {
|
||||
window['app'].extensionManager.queueSettings.mode = mode
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const extMgr = app.extensionManager as {
|
||||
queueSettings?: { mode: string }
|
||||
}
|
||||
if (extMgr.queueSettings) {
|
||||
extMgr.queueSettings.mode = mode
|
||||
}
|
||||
}, mode)
|
||||
}
|
||||
|
||||
public async getMode() {
|
||||
return await this.page.evaluate(() => {
|
||||
return window['app'].extensionManager.queueSettings.mode
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const extMgr = app.extensionManager as {
|
||||
queueSettings?: { mode: string }
|
||||
}
|
||||
return extMgr.queueSettings?.mode
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,11 @@ export class ComfyTemplates {
|
||||
}
|
||||
|
||||
async getAllTemplates(): Promise<TemplateInfo[]> {
|
||||
const templates: WorkflowTemplates[] = await this.page.evaluate(() =>
|
||||
window['app'].api.getCoreWorkflowTemplates()
|
||||
)
|
||||
const templates: WorkflowTemplates[] = await this.page.evaluate(() => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.getCoreWorkflowTemplates()
|
||||
})
|
||||
return templates.flatMap((t) => t.templates)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Response } from '@playwright/test'
|
||||
import { expect, mergeTests } from '@playwright/test'
|
||||
|
||||
import type { StatusWsMessage } from '../../src/schemas/apiSchema.ts'
|
||||
import { comfyPageFixture } from '../fixtures/ComfyPage.ts'
|
||||
import { webSocketFixture } from '../fixtures/ws.ts'
|
||||
import type { StatusWsMessage } from '../../src/schemas/apiSchema'
|
||||
import { comfyPageFixture } from '../fixtures/ComfyPage'
|
||||
import { webSocketFixture } from '../fixtures/ws'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
@@ -49,13 +49,19 @@ test.describe('Actionbar', () => {
|
||||
// Find and set the width on the latent node
|
||||
const triggerChange = async (value: number) => {
|
||||
return await comfyPage.page.evaluate((value) => {
|
||||
const node = window['app'].graph._nodes.find(
|
||||
(n) => n.type === 'EmptyLatentImage'
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const node = app.graph._nodes.find(
|
||||
(n: { type: string }) => n.type === 'EmptyLatentImage'
|
||||
)
|
||||
if (!node?.widgets?.[0]) throw new Error('Node or widget not found')
|
||||
node.widgets[0].value = value
|
||||
window[
|
||||
'app'
|
||||
].extensionManager.workflow.activeWorkflow.changeTracker.checkState()
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: {
|
||||
activeWorkflow?: { changeTracker?: { checkState?: () => void } }
|
||||
}
|
||||
}
|
||||
extMgr.workflow?.activeWorkflow?.changeTracker?.checkState?.()
|
||||
}, value)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,12 @@ test.describe('Browser tab title', () => {
|
||||
|
||||
test('Can display workflow name', async ({ comfyPage }) => {
|
||||
const workflowName = await comfyPage.page.evaluate(async () => {
|
||||
return window['app'].extensionManager.workflow.activeWorkflow.filename
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: { activeWorkflow?: { filename?: string } }
|
||||
}
|
||||
return extMgr.workflow?.activeWorkflow?.filename
|
||||
})
|
||||
expect(await comfyPage.page.title()).toBe(`*${workflowName} - ComfyUI`)
|
||||
})
|
||||
@@ -21,7 +26,12 @@ test.describe('Browser tab title', () => {
|
||||
comfyPage
|
||||
}) => {
|
||||
const workflowName = await comfyPage.page.evaluate(async () => {
|
||||
return window['app'].extensionManager.workflow.activeWorkflow.filename
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: { activeWorkflow?: { filename?: string } }
|
||||
}
|
||||
return extMgr.workflow?.activeWorkflow?.filename
|
||||
})
|
||||
expect(await comfyPage.page.title()).toBe(`${workflowName} - ComfyUI`)
|
||||
|
||||
@@ -35,7 +45,12 @@ test.describe('Browser tab title', () => {
|
||||
|
||||
// Delete the saved workflow for cleanup.
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
return window['app'].extensionManager.workflow.activeWorkflow.delete()
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: { activeWorkflow?: { delete?: () => Promise<void> } }
|
||||
}
|
||||
return extMgr.workflow?.activeWorkflow?.delete?.()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,12 +6,16 @@ import {
|
||||
|
||||
async function beforeChange(comfyPage: ComfyPage) {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].canvas.emitBeforeChange()
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.canvas.emitBeforeChange()
|
||||
})
|
||||
}
|
||||
async function afterChange(comfyPage: ComfyPage) {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].canvas.emitAfterChange()
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.canvas.emitAfterChange()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,7 +160,9 @@ test.describe('Change Tracker', () => {
|
||||
test('Can detect changes in workflow.extra', async ({ comfyPage }) => {
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].graph.extra.foo = 'bar'
|
||||
const app = window['app']
|
||||
if (!app?.graph?.extra) throw new Error('App graph not initialized')
|
||||
app.graph.extra.foo = 'bar'
|
||||
})
|
||||
// Click empty space to trigger a change detection.
|
||||
await comfyPage.clickEmptySpace()
|
||||
|
||||
@@ -7,7 +7,15 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
const customColorPalettes: Record<string, Palette> = {
|
||||
type TestPalette = Omit<Palette, 'colors'> & {
|
||||
colors: {
|
||||
node_slot: Record<string, string>
|
||||
litegraph_base: Partial<Palette['colors']['litegraph_base']>
|
||||
comfy_base: Partial<Palette['colors']['comfy_base']>
|
||||
}
|
||||
}
|
||||
|
||||
const customColorPalettes: Record<string, TestPalette> = {
|
||||
obsidian: {
|
||||
version: 102,
|
||||
id: 'obsidian',
|
||||
@@ -176,7 +184,12 @@ test.describe('Color Palette', () => {
|
||||
|
||||
test('Can add custom color palette', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate((p) => {
|
||||
window['app'].extensionManager.colorPalette.addCustomColorPalette(p)
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const extMgr = app.extensionManager as {
|
||||
colorPalette?: { addCustomColorPalette?: (p: unknown) => void }
|
||||
}
|
||||
extMgr.colorPalette?.addCustomColorPalette?.(p)
|
||||
}, customColorPalettes.obsidian_dark)
|
||||
expect(await comfyPage.getToastErrorCount()).toBe(0)
|
||||
|
||||
@@ -232,7 +245,6 @@ test.describe('Node Color Adjustments', () => {
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
const saveWorkflowInterval = 1000
|
||||
const workflow = await comfyPage.page.evaluate(() => {
|
||||
return localStorage.getItem('workflow')
|
||||
})
|
||||
|
||||
@@ -9,25 +9,33 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
test.describe('Keybindings', () => {
|
||||
test('Should execute command', async ({ comfyPage }) => {
|
||||
await comfyPage.registerCommand('TestCommand', () => {
|
||||
window['foo'] = true
|
||||
;(window as unknown as Record<string, unknown>)['foo'] = true
|
||||
})
|
||||
|
||||
await comfyPage.executeCommand('TestCommand')
|
||||
expect(await comfyPage.page.evaluate(() => window['foo'])).toBe(true)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['foo']
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('Should execute async command', async ({ comfyPage }) => {
|
||||
await comfyPage.registerCommand('TestCommand', async () => {
|
||||
await new Promise<void>((resolve) =>
|
||||
setTimeout(() => {
|
||||
window['foo'] = true
|
||||
;(window as unknown as Record<string, unknown>)['foo'] = true
|
||||
resolve()
|
||||
}, 5)
|
||||
)
|
||||
})
|
||||
|
||||
await comfyPage.executeCommand('TestCommand')
|
||||
expect(await comfyPage.page.evaluate(() => window['foo'])).toBe(true)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['foo']
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('Should handle command errors', async ({ comfyPage }) => {
|
||||
@@ -41,7 +49,7 @@ test.describe('Keybindings', () => {
|
||||
|
||||
test('Should handle async command errors', async ({ comfyPage }) => {
|
||||
await comfyPage.registerCommand('TestCommand', async () => {
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
await new Promise<void>((_resolve, reject) =>
|
||||
setTimeout(() => {
|
||||
reject(new Error('Test error'))
|
||||
}, 5)
|
||||
|
||||
@@ -331,7 +331,8 @@ test.describe('Error dialog', () => {
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const graph = window['graph']
|
||||
const graph = window['graph'] as { configure?: () => void } | undefined
|
||||
if (!graph) throw new Error('Graph not initialized')
|
||||
graph.configure = () => {
|
||||
throw new Error('Error on configure!')
|
||||
}
|
||||
@@ -348,6 +349,7 @@ test.describe('Error dialog', () => {
|
||||
}) => {
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.api.queuePrompt = () => {
|
||||
throw new Error('Error on queuePrompt!')
|
||||
}
|
||||
@@ -373,7 +375,9 @@ test.describe('Signin dialog', () => {
|
||||
await textBox.press('Control+c')
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
void window['app'].extensionManager.dialog.showSignInDialog()
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
void app.extensionManager.dialog.showSignInDialog()
|
||||
})
|
||||
|
||||
const input = comfyPage.page.locator('#comfy-org-sign-in-password')
|
||||
|
||||
@@ -10,14 +10,16 @@ test.describe('Topbar commands', () => {
|
||||
|
||||
test('Should allow registering topbar commands', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
commands: [
|
||||
{
|
||||
id: 'foo',
|
||||
label: 'foo-command',
|
||||
function: () => {
|
||||
window['foo'] = true
|
||||
;(window as unknown as Record<string, unknown>)['foo'] = true
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -31,7 +33,11 @@ test.describe('Topbar commands', () => {
|
||||
})
|
||||
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['ext', 'foo-command'])
|
||||
expect(await comfyPage.page.evaluate(() => window['foo'])).toBe(true)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['foo']
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('Should not allow register command defined in other extension', async ({
|
||||
@@ -39,7 +45,9 @@ test.describe('Topbar commands', () => {
|
||||
}) => {
|
||||
await comfyPage.registerCommand('foo', () => alert(1))
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
menuCommands: [
|
||||
{
|
||||
@@ -57,13 +65,15 @@ test.describe('Topbar commands', () => {
|
||||
test('Should allow registering keybindings', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
commands: [
|
||||
{
|
||||
id: 'TestCommand',
|
||||
function: () => {
|
||||
window['TestCommand'] = true
|
||||
;(window as unknown as Record<string, unknown>)['TestCommand'] =
|
||||
true
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -77,15 +87,19 @@ test.describe('Topbar commands', () => {
|
||||
})
|
||||
|
||||
await comfyPage.page.keyboard.press('k')
|
||||
expect(await comfyPage.page.evaluate(() => window['TestCommand'])).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['TestCommand']
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test.describe('Settings', () => {
|
||||
test('Should allow adding settings', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
settings: [
|
||||
{
|
||||
@@ -94,24 +108,39 @@ test.describe('Topbar commands', () => {
|
||||
type: 'text',
|
||||
defaultValue: 'Hello, world!',
|
||||
onChange: () => {
|
||||
window['changeCount'] = (window['changeCount'] ?? 0) + 1
|
||||
const win = window as unknown as Record<string, unknown>
|
||||
win['changeCount'] = ((win['changeCount'] as number) ?? 0) + 1
|
||||
}
|
||||
}
|
||||
} as unknown as SettingParams
|
||||
]
|
||||
})
|
||||
})
|
||||
// onChange is called when the setting is first added
|
||||
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(1)
|
||||
expect(await comfyPage.getSetting('TestSetting')).toBe('Hello, world!')
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['changeCount']
|
||||
)
|
||||
).toBe(1)
|
||||
expect(await comfyPage.getSetting('TestSetting' as string)).toBe(
|
||||
'Hello, world!'
|
||||
)
|
||||
|
||||
await comfyPage.setSetting('TestSetting', 'Hello, universe!')
|
||||
expect(await comfyPage.getSetting('TestSetting')).toBe('Hello, universe!')
|
||||
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(2)
|
||||
await comfyPage.setSetting('TestSetting' as string, 'Hello, universe!')
|
||||
expect(await comfyPage.getSetting('TestSetting' as string)).toBe(
|
||||
'Hello, universe!'
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['changeCount']
|
||||
)
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
test('Should allow setting boolean settings', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
settings: [
|
||||
{
|
||||
@@ -120,20 +149,35 @@ test.describe('Topbar commands', () => {
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
onChange: () => {
|
||||
window['changeCount'] = (window['changeCount'] ?? 0) + 1
|
||||
const win = window as unknown as Record<string, unknown>
|
||||
win['changeCount'] = ((win['changeCount'] as number) ?? 0) + 1
|
||||
}
|
||||
}
|
||||
} as unknown as SettingParams
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
expect(await comfyPage.getSetting('Comfy.TestSetting')).toBe(false)
|
||||
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(1)
|
||||
expect(await comfyPage.getSetting('Comfy.TestSetting' as string)).toBe(
|
||||
false
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['changeCount']
|
||||
)
|
||||
).toBe(1)
|
||||
|
||||
await comfyPage.settingDialog.open()
|
||||
await comfyPage.settingDialog.toggleBooleanSetting('Comfy.TestSetting')
|
||||
expect(await comfyPage.getSetting('Comfy.TestSetting')).toBe(true)
|
||||
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(2)
|
||||
await comfyPage.settingDialog.toggleBooleanSetting(
|
||||
'Comfy.TestSetting' as string
|
||||
)
|
||||
expect(await comfyPage.getSetting('Comfy.TestSetting' as string)).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['changeCount']
|
||||
)
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
test.describe('Passing through attrs to setting components', () => {
|
||||
@@ -191,7 +235,9 @@ test.describe('Topbar commands', () => {
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.evaluate((config) => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
settings: [
|
||||
{
|
||||
@@ -200,7 +246,7 @@ test.describe('Topbar commands', () => {
|
||||
// The `disabled` attr is common to all settings components
|
||||
attrs: { disabled: true },
|
||||
...config
|
||||
}
|
||||
} as SettingParams
|
||||
]
|
||||
})
|
||||
}, config)
|
||||
@@ -224,7 +270,9 @@ test.describe('Topbar commands', () => {
|
||||
test.describe('About panel', () => {
|
||||
test('Should allow adding badges', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
aboutPageBadges: [
|
||||
{
|
||||
@@ -247,55 +295,71 @@ test.describe('Topbar commands', () => {
|
||||
test.describe('Dialog', () => {
|
||||
test('Should allow showing a prompt dialog', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
void window['app'].extensionManager.dialog
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
void app.extensionManager.dialog
|
||||
.prompt({
|
||||
title: 'Test Prompt',
|
||||
message: 'Test Prompt Message'
|
||||
})
|
||||
.then((value: string) => {
|
||||
window['value'] = value
|
||||
.then((value: string | null) => {
|
||||
;(window as unknown as Record<string, unknown>)['value'] = value
|
||||
})
|
||||
})
|
||||
|
||||
await comfyPage.fillPromptDialog('Hello, world!')
|
||||
expect(await comfyPage.page.evaluate(() => window['value'])).toBe(
|
||||
'Hello, world!'
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['value']
|
||||
)
|
||||
).toBe('Hello, world!')
|
||||
})
|
||||
|
||||
test('Should allow showing a confirmation dialog', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
void window['app'].extensionManager.dialog
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
void app.extensionManager.dialog
|
||||
.confirm({
|
||||
title: 'Test Confirm',
|
||||
message: 'Test Confirm Message'
|
||||
})
|
||||
.then((value: boolean) => {
|
||||
window['value'] = value
|
||||
.then((value: boolean | null) => {
|
||||
;(window as unknown as Record<string, unknown>)['value'] = value
|
||||
})
|
||||
})
|
||||
|
||||
await comfyPage.confirmDialog.click('confirm')
|
||||
expect(await comfyPage.page.evaluate(() => window['value'])).toBe(true)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['value']
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('Should allow dismissing a dialog', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['value'] = 'foo'
|
||||
void window['app'].extensionManager.dialog
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
;(window as unknown as Record<string, unknown>)['value'] = 'foo'
|
||||
void app.extensionManager.dialog
|
||||
.confirm({
|
||||
title: 'Test Confirm',
|
||||
message: 'Test Confirm Message'
|
||||
})
|
||||
.then((value: boolean) => {
|
||||
window['value'] = value
|
||||
.then((value: boolean | null) => {
|
||||
;(window as unknown as Record<string, unknown>)['value'] = value
|
||||
})
|
||||
})
|
||||
|
||||
await comfyPage.confirmDialog.click('reject')
|
||||
expect(await comfyPage.page.evaluate(() => window['value'])).toBeNull()
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['value']
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -309,7 +373,9 @@ test.describe('Topbar commands', () => {
|
||||
}) => {
|
||||
// Register an extension with a selection toolbox command
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
commands: [
|
||||
{
|
||||
@@ -317,7 +383,9 @@ test.describe('Topbar commands', () => {
|
||||
label: 'Test Command',
|
||||
icon: 'pi pi-star',
|
||||
function: () => {
|
||||
window['selectionCommandExecuted'] = true
|
||||
;(window as unknown as Record<string, unknown>)[
|
||||
'selectionCommandExecuted'
|
||||
] = true
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -335,7 +403,12 @@ test.describe('Topbar commands', () => {
|
||||
|
||||
// Verify the command was executed
|
||||
expect(
|
||||
await comfyPage.page.evaluate(() => window['selectionCommandExecuted'])
|
||||
await comfyPage.page.evaluate(
|
||||
() =>
|
||||
(window as unknown as Record<string, unknown>)[
|
||||
'selectionCommandExecuted'
|
||||
]
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,19 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
declare const window: Window &
|
||||
typeof globalThis & {
|
||||
__capturedMessages?: {
|
||||
clientFeatureFlags: unknown
|
||||
serverFeatureFlags: unknown
|
||||
}
|
||||
__appReadiness?: {
|
||||
featureFlagsReceived: boolean
|
||||
apiInitialized: boolean
|
||||
appInitialized: boolean
|
||||
}
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
@@ -15,8 +28,17 @@ test.describe('Feature Flags', () => {
|
||||
|
||||
// Set up monitoring before navigation
|
||||
await newPage.addInitScript(() => {
|
||||
type WindowWithMessages = Window &
|
||||
typeof globalThis & {
|
||||
__capturedMessages: {
|
||||
clientFeatureFlags: unknown
|
||||
serverFeatureFlags: unknown
|
||||
}
|
||||
app?: { api?: { serverFeatureFlags?: Record<string, unknown> } }
|
||||
}
|
||||
const win = window as WindowWithMessages
|
||||
// This runs before any page scripts
|
||||
window.__capturedMessages = {
|
||||
win.__capturedMessages = {
|
||||
clientFeatureFlags: null,
|
||||
serverFeatureFlags: null
|
||||
}
|
||||
@@ -25,11 +47,13 @@ test.describe('Feature Flags', () => {
|
||||
const originalSend = WebSocket.prototype.send
|
||||
WebSocket.prototype.send = function (data) {
|
||||
try {
|
||||
const parsed = JSON.parse(data)
|
||||
if (parsed.type === 'feature_flags') {
|
||||
window.__capturedMessages.clientFeatureFlags = parsed
|
||||
if (typeof data === 'string') {
|
||||
const parsed = JSON.parse(data)
|
||||
if (parsed.type === 'feature_flags') {
|
||||
win.__capturedMessages.clientFeatureFlags = parsed
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Not JSON, ignore
|
||||
}
|
||||
return originalSend.call(this, data)
|
||||
@@ -37,12 +61,9 @@ test.describe('Feature Flags', () => {
|
||||
|
||||
// Monitor for server feature flags
|
||||
const checkInterval = setInterval(() => {
|
||||
if (
|
||||
window['app']?.api?.serverFeatureFlags &&
|
||||
Object.keys(window['app'].api.serverFeatureFlags).length > 0
|
||||
) {
|
||||
window.__capturedMessages.serverFeatureFlags =
|
||||
window['app'].api.serverFeatureFlags
|
||||
const serverFlags = win.app?.api?.serverFeatureFlags
|
||||
if (serverFlags && Object.keys(serverFlags).length > 0) {
|
||||
win.__capturedMessages.serverFeatureFlags = serverFlags
|
||||
clearInterval(checkInterval)
|
||||
}
|
||||
}, 100)
|
||||
@@ -56,37 +77,58 @@ test.describe('Feature Flags', () => {
|
||||
|
||||
// Wait for both client and server feature flags
|
||||
await newPage.waitForFunction(
|
||||
() =>
|
||||
window.__capturedMessages.clientFeatureFlags !== null &&
|
||||
window.__capturedMessages.serverFeatureFlags !== null,
|
||||
() => {
|
||||
type WindowWithMessages = Window &
|
||||
typeof globalThis & {
|
||||
__capturedMessages?: {
|
||||
clientFeatureFlags: unknown
|
||||
serverFeatureFlags: unknown
|
||||
}
|
||||
}
|
||||
const win = window as WindowWithMessages
|
||||
return (
|
||||
win.__capturedMessages?.clientFeatureFlags !== null &&
|
||||
win.__capturedMessages?.serverFeatureFlags !== null
|
||||
)
|
||||
},
|
||||
{ timeout: 10000 }
|
||||
)
|
||||
|
||||
// Get the captured messages
|
||||
const messages = await newPage.evaluate(() => window.__capturedMessages)
|
||||
const messages = await newPage.evaluate(() => {
|
||||
type WindowWithMessages = Window &
|
||||
typeof globalThis & {
|
||||
__capturedMessages?: {
|
||||
clientFeatureFlags: { type: string; data: Record<string, unknown> }
|
||||
serverFeatureFlags: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
return (window as WindowWithMessages).__capturedMessages
|
||||
})
|
||||
|
||||
// Verify client sent feature flags
|
||||
expect(messages.clientFeatureFlags).toBeTruthy()
|
||||
expect(messages.clientFeatureFlags).toHaveProperty('type', 'feature_flags')
|
||||
expect(messages.clientFeatureFlags).toHaveProperty('data')
|
||||
expect(messages.clientFeatureFlags.data).toHaveProperty(
|
||||
expect(messages).toBeTruthy()
|
||||
expect(messages!.clientFeatureFlags).toBeTruthy()
|
||||
expect(messages!.clientFeatureFlags).toHaveProperty('type', 'feature_flags')
|
||||
expect(messages!.clientFeatureFlags).toHaveProperty('data')
|
||||
expect(messages!.clientFeatureFlags.data).toHaveProperty(
|
||||
'supports_preview_metadata'
|
||||
)
|
||||
expect(
|
||||
typeof messages.clientFeatureFlags.data.supports_preview_metadata
|
||||
typeof messages!.clientFeatureFlags.data.supports_preview_metadata
|
||||
).toBe('boolean')
|
||||
|
||||
// Verify server sent feature flags back
|
||||
expect(messages.serverFeatureFlags).toBeTruthy()
|
||||
expect(messages.serverFeatureFlags).toHaveProperty(
|
||||
expect(messages!.serverFeatureFlags).toBeTruthy()
|
||||
expect(messages!.serverFeatureFlags).toHaveProperty(
|
||||
'supports_preview_metadata'
|
||||
)
|
||||
expect(typeof messages.serverFeatureFlags.supports_preview_metadata).toBe(
|
||||
expect(typeof messages!.serverFeatureFlags.supports_preview_metadata).toBe(
|
||||
'boolean'
|
||||
)
|
||||
expect(messages.serverFeatureFlags).toHaveProperty('max_upload_size')
|
||||
expect(typeof messages.serverFeatureFlags.max_upload_size).toBe('number')
|
||||
expect(Object.keys(messages.serverFeatureFlags).length).toBeGreaterThan(0)
|
||||
expect(messages!.serverFeatureFlags).toHaveProperty('max_upload_size')
|
||||
expect(typeof messages!.serverFeatureFlags.max_upload_size).toBe('number')
|
||||
expect(Object.keys(messages!.serverFeatureFlags).length).toBeGreaterThan(0)
|
||||
|
||||
await newPage.close()
|
||||
})
|
||||
@@ -96,7 +138,9 @@ test.describe('Feature Flags', () => {
|
||||
}) => {
|
||||
// Get the actual server feature flags from the backend
|
||||
const serverFlags = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].api.serverFeatureFlags
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.serverFeatureFlags
|
||||
})
|
||||
|
||||
// Verify we received real feature flags from the backend
|
||||
@@ -115,24 +159,28 @@ test.describe('Feature Flags', () => {
|
||||
}) => {
|
||||
// Test serverSupportsFeature with real backend flags
|
||||
const supportsPreviewMetadata = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].api.serverSupportsFeature(
|
||||
'supports_preview_metadata'
|
||||
)
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.serverSupportsFeature('supports_preview_metadata')
|
||||
})
|
||||
// The method should return a boolean based on the backend's value
|
||||
expect(typeof supportsPreviewMetadata).toBe('boolean')
|
||||
|
||||
// Test non-existent feature - should always return false
|
||||
const supportsNonExistent = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].api.serverSupportsFeature('non_existent_feature_xyz')
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.serverSupportsFeature('non_existent_feature_xyz')
|
||||
})
|
||||
expect(supportsNonExistent).toBe(false)
|
||||
|
||||
// Test that the method only returns true for boolean true values
|
||||
const testResults = await comfyPage.page.evaluate(() => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
// Temporarily modify serverFeatureFlags to test behavior
|
||||
const original = window['app'].api.serverFeatureFlags
|
||||
window['app'].api.serverFeatureFlags = {
|
||||
const original = app.api.serverFeatureFlags
|
||||
app.api.serverFeatureFlags = {
|
||||
bool_true: true,
|
||||
bool_false: false,
|
||||
string_value: 'yes',
|
||||
@@ -141,15 +189,15 @@ test.describe('Feature Flags', () => {
|
||||
}
|
||||
|
||||
const results = {
|
||||
bool_true: window['app'].api.serverSupportsFeature('bool_true'),
|
||||
bool_false: window['app'].api.serverSupportsFeature('bool_false'),
|
||||
string_value: window['app'].api.serverSupportsFeature('string_value'),
|
||||
number_value: window['app'].api.serverSupportsFeature('number_value'),
|
||||
null_value: window['app'].api.serverSupportsFeature('null_value')
|
||||
bool_true: app.api.serverSupportsFeature('bool_true'),
|
||||
bool_false: app.api.serverSupportsFeature('bool_false'),
|
||||
string_value: app.api.serverSupportsFeature('string_value'),
|
||||
number_value: app.api.serverSupportsFeature('number_value'),
|
||||
null_value: app.api.serverSupportsFeature('null_value')
|
||||
}
|
||||
|
||||
// Restore original
|
||||
window['app'].api.serverFeatureFlags = original
|
||||
app.api.serverFeatureFlags = original
|
||||
return results
|
||||
})
|
||||
|
||||
@@ -166,23 +214,26 @@ test.describe('Feature Flags', () => {
|
||||
}) => {
|
||||
// Test getServerFeature method
|
||||
const previewMetadataValue = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].api.getServerFeature('supports_preview_metadata')
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.getServerFeature('supports_preview_metadata')
|
||||
})
|
||||
expect(typeof previewMetadataValue).toBe('boolean')
|
||||
|
||||
// Test getting max_upload_size
|
||||
const maxUploadSize = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].api.getServerFeature('max_upload_size')
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.getServerFeature('max_upload_size')
|
||||
})
|
||||
expect(typeof maxUploadSize).toBe('number')
|
||||
expect(maxUploadSize).toBeGreaterThan(0)
|
||||
|
||||
// Test getServerFeature with default value for non-existent feature
|
||||
const defaultValue = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].api.getServerFeature(
|
||||
'non_existent_feature_xyz',
|
||||
'default'
|
||||
)
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.getServerFeature('non_existent_feature_xyz', 'default')
|
||||
})
|
||||
expect(defaultValue).toBe('default')
|
||||
})
|
||||
@@ -192,7 +243,9 @@ test.describe('Feature Flags', () => {
|
||||
}) => {
|
||||
// Test getServerFeatures returns all flags
|
||||
const allFeatures = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].api.getServerFeatures()
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.api.getServerFeatures()
|
||||
})
|
||||
|
||||
expect(allFeatures).toBeTruthy()
|
||||
@@ -205,14 +258,16 @@ test.describe('Feature Flags', () => {
|
||||
test('Client feature flags are immutable', async ({ comfyPage }) => {
|
||||
// Test that getClientFeatureFlags returns a copy
|
||||
const immutabilityTest = await comfyPage.page.evaluate(() => {
|
||||
const flags1 = window['app'].api.getClientFeatureFlags()
|
||||
const flags2 = window['app'].api.getClientFeatureFlags()
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const flags1 = app.api.getClientFeatureFlags()
|
||||
const flags2 = app.api.getClientFeatureFlags()
|
||||
|
||||
// Modify the first object
|
||||
flags1.test_modification = true
|
||||
|
||||
// Get flags again to check if original was modified
|
||||
const flags3 = window['app'].api.getClientFeatureFlags()
|
||||
const flags3 = app.api.getClientFeatureFlags()
|
||||
|
||||
return {
|
||||
areEqual: flags1 === flags2,
|
||||
@@ -237,15 +292,17 @@ test.describe('Feature Flags', () => {
|
||||
comfyPage
|
||||
}) => {
|
||||
const immutabilityTest = await comfyPage.page.evaluate(() => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
// Get a copy of server features
|
||||
const features1 = window['app'].api.getServerFeatures()
|
||||
const features1 = app.api.getServerFeatures()
|
||||
|
||||
// Try to modify it
|
||||
features1.supports_preview_metadata = false
|
||||
features1.new_feature = 'added'
|
||||
|
||||
// Get another copy
|
||||
const features2 = window['app'].api.getServerFeatures()
|
||||
const features2 = app.api.getServerFeatures()
|
||||
|
||||
return {
|
||||
modifiedValue: features1.supports_preview_metadata,
|
||||
@@ -330,9 +387,11 @@ test.describe('Feature Flags', () => {
|
||||
|
||||
// Get readiness state
|
||||
const readiness = await newPage.evaluate(() => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return {
|
||||
...(window as any).__appReadiness,
|
||||
currentFlags: window['app'].api.serverFeatureFlags
|
||||
currentFlags: app.api.serverFeatureFlags
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ test.describe('Graph', () => {
|
||||
await comfyPage.loadWorkflow('inputs/input_order_swap')
|
||||
expect(
|
||||
await comfyPage.page.evaluate(() => {
|
||||
return window['app'].graph.links.get(1)?.target_slot
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
return app.graph.links.get(1)?.target_slot
|
||||
})
|
||||
).toBe(1)
|
||||
})
|
||||
|
||||
@@ -23,7 +23,9 @@ test.describe('Graph Canvas Menu', () => {
|
||||
'canvas-with-hidden-links.png'
|
||||
)
|
||||
const hiddenLinkRenderMode = await comfyPage.page.evaluate(() => {
|
||||
return window['LiteGraph'].HIDDEN_LINK
|
||||
const LiteGraph = window['LiteGraph']
|
||||
if (!LiteGraph) throw new Error('LiteGraph not initialized')
|
||||
return LiteGraph.HIDDEN_LINK
|
||||
})
|
||||
expect(await comfyPage.getSetting('Comfy.LinkRenderMode')).toBe(
|
||||
hiddenLinkRenderMode
|
||||
|
||||
@@ -2,6 +2,7 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import type { NodeLibrarySidebarTab } from '../fixtures/components/SidebarTab'
|
||||
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
@@ -13,7 +14,7 @@ test.describe('Group Node', () => {
|
||||
const groupNodeName = 'DefautWorkflowGroupNode'
|
||||
const groupNodeCategory = 'group nodes>workflow'
|
||||
const groupNodeBookmarkName = `workflow>${groupNodeName}`
|
||||
let libraryTab
|
||||
let libraryTab: NodeLibrarySidebarTab
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
@@ -22,7 +23,9 @@ test.describe('Group Node', () => {
|
||||
await libraryTab.open()
|
||||
})
|
||||
|
||||
test('Is added to node library sidebar', async ({ comfyPage }) => {
|
||||
test('Is added to node library sidebar', async ({
|
||||
comfyPage: _comfyPage
|
||||
}) => {
|
||||
expect(await libraryTab.getFolder('group nodes').count()).toBe(1)
|
||||
})
|
||||
|
||||
@@ -110,7 +113,7 @@ test.describe('Group Node', () => {
|
||||
test('Manage group opens with the correct group selected', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const makeGroup = async (name, type1, type2) => {
|
||||
const makeGroup = async (name: string, type1: string, type2: string) => {
|
||||
const node1 = (await comfyPage.getNodeRefsByType(type1))[0]
|
||||
const node2 = (await comfyPage.getNodeRefsByType(type2))[0]
|
||||
await node1.click('title')
|
||||
@@ -149,17 +152,27 @@ test.describe('Group Node', () => {
|
||||
const groupNodeName = 'two_VAE_decode'
|
||||
|
||||
const totalInputCount = await comfyPage.page.evaluate((nodeName) => {
|
||||
const {
|
||||
extra: { groupNodes }
|
||||
} = window['app'].graph
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.graph
|
||||
if (!graph?.extra) throw new Error('Graph extra not initialized')
|
||||
const groupNodes = graph.extra.groupNodes as
|
||||
| Record<string, { nodes: Array<{ inputs?: unknown[] }> }>
|
||||
| undefined
|
||||
if (!groupNodes?.[nodeName]) throw new Error('Group node not found')
|
||||
const { nodes } = groupNodes[nodeName]
|
||||
return nodes.reduce((acc: number, node) => {
|
||||
return acc + node.inputs.length
|
||||
}, 0)
|
||||
return nodes.reduce(
|
||||
(acc: number, node: { inputs?: unknown[] }) =>
|
||||
acc + (node.inputs?.length ?? 0),
|
||||
0
|
||||
)
|
||||
}, groupNodeName)
|
||||
|
||||
const visibleInputCount = await comfyPage.page.evaluate((id) => {
|
||||
const node = window['app'].graph.getNodeById(id)
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const node = app.graph?.getNodeById(id)
|
||||
if (!node) throw new Error('Node not found')
|
||||
return node.inputs.length
|
||||
}, groupNodeId)
|
||||
|
||||
@@ -226,7 +239,9 @@ test.describe('Group Node', () => {
|
||||
|
||||
const isRegisteredLitegraph = async (comfyPage: ComfyPage) => {
|
||||
return await comfyPage.page.evaluate((nodeType: string) => {
|
||||
return !!window['LiteGraph'].registered_node_types[nodeType]
|
||||
const lg = window['LiteGraph']
|
||||
if (!lg) throw new Error('LiteGraph not initialized')
|
||||
return !!lg.registered_node_types[nodeType]
|
||||
}, GROUP_NODE_TYPE)
|
||||
}
|
||||
|
||||
@@ -299,15 +314,20 @@ test.describe('Group Node', () => {
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['New'])
|
||||
await comfyPage.ctrlV()
|
||||
const currentGraphState = await comfyPage.page.evaluate(() =>
|
||||
window['app'].graph.serialize()
|
||||
)
|
||||
const currentGraphState = await comfyPage.page.evaluate(() => {
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('App or graph not initialized')
|
||||
return app.graph.serialize()
|
||||
})
|
||||
|
||||
await test.step('Load workflow containing a group node pasted from a different workflow', async () => {
|
||||
await comfyPage.page.evaluate(
|
||||
(workflow) => window['app'].loadGraphData(workflow),
|
||||
currentGraphState
|
||||
)
|
||||
await comfyPage.page.evaluate((workflow) => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.loadGraphData(
|
||||
workflow as Parameters<typeof app.loadGraphData>[0]
|
||||
)
|
||||
}, currentGraphState)
|
||||
await comfyPage.nextFrame()
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
})
|
||||
|
||||
@@ -11,23 +11,25 @@ test.describe('Keybindings', () => {
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.registerKeybinding({ key: 'k' }, () => {
|
||||
window['TestCommand'] = true
|
||||
;(window as unknown as Record<string, unknown>)['TestCommand'] = true
|
||||
})
|
||||
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.fill('k')
|
||||
await expect(textBox).toHaveValue('k')
|
||||
expect(await comfyPage.page.evaluate(() => window['TestCommand'])).toBe(
|
||||
undefined
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['TestCommand']
|
||||
)
|
||||
).toBe(undefined)
|
||||
})
|
||||
|
||||
test('Should not trigger modifier keybinding when typing in input fields', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.registerKeybinding({ key: 'k', ctrl: true }, () => {
|
||||
window['TestCommand'] = true
|
||||
;(window as unknown as Record<string, unknown>)['TestCommand'] = true
|
||||
})
|
||||
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
@@ -35,24 +37,28 @@ test.describe('Keybindings', () => {
|
||||
await textBox.fill('q')
|
||||
await textBox.press('Control+k')
|
||||
await expect(textBox).toHaveValue('q')
|
||||
expect(await comfyPage.page.evaluate(() => window['TestCommand'])).toBe(
|
||||
true
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['TestCommand']
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
test('Should not trigger keybinding reserved by text input when typing in input fields', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.registerKeybinding({ key: 'Ctrl+v' }, () => {
|
||||
window['TestCommand'] = true
|
||||
;(window as unknown as Record<string, unknown>)['TestCommand'] = true
|
||||
})
|
||||
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.press('Control+v')
|
||||
await expect(textBox).toBeFocused()
|
||||
expect(await comfyPage.page.evaluate(() => window['TestCommand'])).toBe(
|
||||
undefined
|
||||
)
|
||||
expect(
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['TestCommand']
|
||||
)
|
||||
).toBe(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,7 +15,9 @@ test.describe('LOD Threshold', () => {
|
||||
|
||||
// Get initial LOD state and settings
|
||||
const initialState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale,
|
||||
@@ -36,7 +38,9 @@ test.describe('LOD Threshold', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const aboveThresholdState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
@@ -54,7 +58,9 @@ test.describe('LOD Threshold', () => {
|
||||
|
||||
// Check that LOD is now active
|
||||
const zoomedOutState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
@@ -70,7 +76,9 @@ test.describe('LOD Threshold', () => {
|
||||
|
||||
// Check that LOD is now inactive
|
||||
const zoomedInState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
@@ -91,7 +99,9 @@ test.describe('LOD Threshold', () => {
|
||||
|
||||
// Check that font size updated
|
||||
const newState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
minFontSize: canvas.min_font_size_for_lod
|
||||
}
|
||||
@@ -102,7 +112,9 @@ test.describe('LOD Threshold', () => {
|
||||
|
||||
// At default zoom, LOD should still be inactive (scale is exactly 1.0, not less than)
|
||||
const lodState = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].canvas.low_quality
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
return app.canvas.low_quality
|
||||
})
|
||||
expect(lodState).toBe(false)
|
||||
|
||||
@@ -111,7 +123,9 @@ test.describe('LOD Threshold', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const afterZoom = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
@@ -136,7 +150,9 @@ test.describe('LOD Threshold', () => {
|
||||
|
||||
// LOD should remain disabled even at very low zoom
|
||||
const state = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale,
|
||||
@@ -160,8 +176,10 @@ test.describe('LOD Threshold', () => {
|
||||
|
||||
// Zoom to target level
|
||||
await comfyPage.page.evaluate((zoom) => {
|
||||
window['app'].canvas.ds.scale = zoom
|
||||
window['app'].canvas.setDirty(true, true)
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.canvas.ds.scale = zoom
|
||||
app.canvas.setDirty(true, true)
|
||||
}, targetZoom)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
@@ -171,7 +189,9 @@ test.describe('LOD Threshold', () => {
|
||||
)
|
||||
|
||||
const lowQualityState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
@@ -189,7 +209,9 @@ test.describe('LOD Threshold', () => {
|
||||
)
|
||||
|
||||
const highQualityState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
|
||||
@@ -11,7 +11,9 @@ test.describe('Menu', () => {
|
||||
const initialChildrenCount = await comfyPage.menu.buttons.count()
|
||||
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
window['app'].extensionManager.registerSidebarTab({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.extensionManager.registerSidebarTab({
|
||||
id: 'search',
|
||||
icon: 'pi pi-search',
|
||||
title: 'search',
|
||||
@@ -152,7 +154,9 @@ test.describe('Menu', () => {
|
||||
|
||||
test('Can catch error when executing command', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
app.registerExtension({
|
||||
name: 'TestExtension1',
|
||||
commands: [
|
||||
{
|
||||
|
||||
@@ -12,7 +12,9 @@ test.describe('Node Badge', () => {
|
||||
test('Can add badge', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
if (!LGraphBadge) throw new Error('LGraphBadge not initialized')
|
||||
const app = window['app'] as ComfyApp
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
@@ -29,7 +31,9 @@ test.describe('Node Badge', () => {
|
||||
test('Can add multiple badges', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
if (!LGraphBadge) throw new Error('LGraphBadge not initialized')
|
||||
const app = window['app'] as ComfyApp
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
@@ -49,7 +53,9 @@ test.describe('Node Badge', () => {
|
||||
test('Can add badge left-side', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
if (!LGraphBadge) throw new Error('LGraphBadge not initialized')
|
||||
const app = window['app'] as ComfyApp
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
@@ -49,20 +51,24 @@ test.describe('Optional input', () => {
|
||||
test('Old workflow with converted input', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/old_workflow_converted_input')
|
||||
const node = await comfyPage.getNodeRefById('1')
|
||||
const inputs = await node.getProperty('inputs')
|
||||
const vaeInput = inputs.find((w) => w.name === 'vae')
|
||||
const convertedInput = inputs.find((w) => w.name === 'strength')
|
||||
const inputs = await node.getProperty<INodeInputSlot[]>('inputs')
|
||||
const vaeInput = inputs.find((w: INodeInputSlot) => w.name === 'vae')
|
||||
const convertedInput = inputs.find(
|
||||
(w: INodeInputSlot) => w.name === 'strength'
|
||||
)
|
||||
|
||||
expect(vaeInput).toBeDefined()
|
||||
expect(convertedInput).toBeDefined()
|
||||
expect(vaeInput.link).toBeNull()
|
||||
expect(convertedInput.link).not.toBeNull()
|
||||
expect(vaeInput!.link).toBeNull()
|
||||
expect(convertedInput!.link).not.toBeNull()
|
||||
})
|
||||
test('Renamed converted input', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/renamed_converted_widget')
|
||||
const node = await comfyPage.getNodeRefById('3')
|
||||
const inputs = await node.getProperty('inputs')
|
||||
const renamedInput = inputs.find((w) => w.name === 'breadth')
|
||||
const inputs = await node.getProperty<INodeInputSlot[]>('inputs')
|
||||
const renamedInput = inputs.find(
|
||||
(w: INodeInputSlot) => w.name === 'breadth'
|
||||
)
|
||||
expect(renamedInput).toBeUndefined()
|
||||
})
|
||||
test('slider', async ({ comfyPage }) => {
|
||||
|
||||
@@ -9,8 +9,9 @@ import { fitToViewInstant } from '../helpers/fitToView'
|
||||
async function selectNodeWithPan(comfyPage: any, nodeRef: any) {
|
||||
const nodePos = await nodeRef.getPosition()
|
||||
|
||||
await comfyPage.page.evaluate((pos) => {
|
||||
await comfyPage.page.evaluate((pos: { x: number; y: number }) => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const canvas = app.canvas
|
||||
canvas.ds.offset[0] = -pos.x + canvas.canvas.width / 2
|
||||
canvas.ds.offset[1] = -pos.y + canvas.canvas.height / 2 + 100
|
||||
@@ -345,7 +346,10 @@ This is documentation for a custom node.
|
||||
|
||||
// Find and select a custom/group node
|
||||
const nodeRefs = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].graph.nodes.map((n: any) => n.id)
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
if (!app.graph) throw new Error('Graph not initialized')
|
||||
return app.graph.nodes.map((n: any) => n.id)
|
||||
})
|
||||
if (nodeRefs.length > 0) {
|
||||
const firstNode = await comfyPage.getNodeRefById(nodeRefs[0])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
@@ -126,7 +127,10 @@ test.describe('Node search box', () => {
|
||||
})
|
||||
|
||||
test.describe('Filtering', () => {
|
||||
const expectFilterChips = async (comfyPage, expectedTexts: string[]) => {
|
||||
const expectFilterChips = async (
|
||||
comfyPage: ComfyPage,
|
||||
expectedTexts: string[]
|
||||
) => {
|
||||
const chips = comfyPage.searchBox.filterChips
|
||||
|
||||
// Check that the number of chips matches the expected count
|
||||
|
||||
@@ -25,24 +25,33 @@ test.describe('Remote COMBO Widget', () => {
|
||||
comfyPage: ComfyPage,
|
||||
nodeName: string
|
||||
): Promise<string[] | undefined> => {
|
||||
return await comfyPage.page.evaluate((name) => {
|
||||
const node = window['app'].graph.nodes.find((node) => node.title === name)
|
||||
return node.widgets[0].options.values
|
||||
return await comfyPage.page.evaluate((name): string[] | undefined => {
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const node = app.graph.nodes.find((node) => node.title === name)
|
||||
if (!node?.widgets) throw new Error('Node or widgets not found')
|
||||
return node.widgets[0].options.values as string[] | undefined
|
||||
}, nodeName)
|
||||
}
|
||||
|
||||
const getWidgetValue = async (comfyPage: ComfyPage, nodeName: string) => {
|
||||
return await comfyPage.page.evaluate((name) => {
|
||||
const node = window['app'].graph.nodes.find((node) => node.title === name)
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const node = app.graph.nodes.find((node) => node.title === name)
|
||||
if (!node?.widgets) throw new Error('Node or widgets not found')
|
||||
return node.widgets[0].value
|
||||
}, nodeName)
|
||||
}
|
||||
|
||||
const clickRefreshButton = (comfyPage: ComfyPage, nodeName: string) => {
|
||||
return comfyPage.page.evaluate((name) => {
|
||||
const node = window['app'].graph.nodes.find((node) => node.title === name)
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const node = app.graph.nodes.find((node) => node.title === name)
|
||||
if (!node?.widgets) throw new Error('Node or widgets not found')
|
||||
const buttonWidget = node.widgets.find((w) => w.name === 'refresh')
|
||||
return buttonWidget?.callback()
|
||||
buttonWidget?.callback?.(buttonWidget.value, undefined, node)
|
||||
}, nodeName)
|
||||
}
|
||||
|
||||
@@ -92,7 +101,9 @@ test.describe('Remote COMBO Widget', () => {
|
||||
await comfyPage.loadWorkflow('inputs/remote_widget')
|
||||
|
||||
const node = await comfyPage.page.evaluate((name) => {
|
||||
return window['app'].graph.nodes.find((node) => node.title === name)
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
return app.graph.nodes.find((node) => node.title === name)
|
||||
}, nodeName)
|
||||
expect(node).toBeDefined()
|
||||
|
||||
@@ -196,7 +207,7 @@ test.describe('Remote COMBO Widget', () => {
|
||||
// Fulfill each request with a unique timestamp
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route, request) => {
|
||||
async (route, _request) => {
|
||||
await route.fulfill({
|
||||
body: JSON.stringify([Date.now()]),
|
||||
status: 200
|
||||
|
||||
@@ -265,13 +265,13 @@ test.describe('Node library sidebar', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Verify the color selection is saved
|
||||
const setting = await comfyPage.getSetting(
|
||||
const setting = (await comfyPage.getSetting(
|
||||
'Comfy.NodeLibrary.BookmarksCustomization'
|
||||
)
|
||||
)) as Record<string, { icon?: string; color?: string }> | undefined
|
||||
await expect(setting).toHaveProperty(['foo/', 'color'])
|
||||
await expect(setting['foo/'].color).not.toBeNull()
|
||||
await expect(setting['foo/'].color).not.toBeUndefined()
|
||||
await expect(setting['foo/'].color).not.toBe('')
|
||||
await expect(setting?.['foo/'].color).not.toBeNull()
|
||||
await expect(setting?.['foo/'].color).not.toBeUndefined()
|
||||
await expect(setting?.['foo/'].color).not.toBe('')
|
||||
})
|
||||
|
||||
test('Can rename customized bookmark folder', async ({ comfyPage }) => {
|
||||
|
||||
@@ -139,12 +139,15 @@ test.describe('Workflows sidebar', () => {
|
||||
api: false
|
||||
})
|
||||
expect(exportedWorkflow).toBeDefined()
|
||||
for (const node of exportedWorkflow.nodes) {
|
||||
for (const slot of node.inputs) {
|
||||
if (!exportedWorkflow) return
|
||||
const nodes = exportedWorkflow.nodes
|
||||
if (!Array.isArray(nodes)) return
|
||||
for (const node of nodes) {
|
||||
for (const slot of node.inputs ?? []) {
|
||||
expect(slot.localized_name).toBeUndefined()
|
||||
expect(slot.label).toBeUndefined()
|
||||
}
|
||||
for (const slot of node.outputs) {
|
||||
for (const slot of node.outputs ?? []) {
|
||||
expect(slot.localized_name).toBeUndefined()
|
||||
expect(slot.label).toBeUndefined()
|
||||
}
|
||||
@@ -177,9 +180,11 @@ test.describe('Workflows sidebar', () => {
|
||||
})
|
||||
|
||||
// Compare the exported workflow with the original
|
||||
expect(downloadedContent).toBeDefined()
|
||||
expect(downloadedContentZh).toBeDefined()
|
||||
if (!downloadedContent || !downloadedContentZh) return
|
||||
delete downloadedContent.id
|
||||
delete downloadedContentZh.id
|
||||
expect(downloadedContent).toBeDefined()
|
||||
expect(downloadedContent).toEqual(downloadedContentZh)
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
// Constants
|
||||
const INITIAL_NAME = 'initial_slot_name'
|
||||
const RENAMED_NAME = 'renamed_slot_name'
|
||||
const SECOND_RENAMED_NAME = 'second_renamed_name'
|
||||
|
||||
@@ -27,12 +26,18 @@ test.describe('Subgraph Slot Rename Dialog', () => {
|
||||
|
||||
// Get initial slot label
|
||||
const initialInputLabel = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || graph.inputs?.[0]?.name || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not available')
|
||||
const canvas = app.canvas
|
||||
if (!canvas) throw new Error('Canvas not available')
|
||||
const graph = canvas.graph
|
||||
if (!graph || !('inputs' in graph)) throw new Error('Not in subgraph')
|
||||
const inputs = graph.inputs as { label?: string; name?: string }[]
|
||||
return inputs?.[0]?.label || inputs?.[0]?.name || null
|
||||
})
|
||||
|
||||
// First rename
|
||||
await comfyPage.rightClickSubgraphInputSlot(initialInputLabel)
|
||||
await comfyPage.rightClickSubgraphInputSlot(initialInputLabel ?? undefined)
|
||||
await comfyPage.clickLitegraphContextMenuItem('Rename Slot')
|
||||
|
||||
await comfyPage.page.waitForSelector(SELECTORS.promptDialog, {
|
||||
@@ -55,8 +60,18 @@ test.describe('Subgraph Slot Rename Dialog', () => {
|
||||
|
||||
// Verify the rename worked
|
||||
const afterFirstRename = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
const slot = graph.inputs?.[0]
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not available')
|
||||
const canvas = app.canvas
|
||||
if (!canvas) throw new Error('Canvas not available')
|
||||
const graph = canvas.graph
|
||||
if (!graph || !('inputs' in graph)) throw new Error('Not in subgraph')
|
||||
const inputs = graph.inputs as {
|
||||
label?: string
|
||||
name?: string
|
||||
displayName?: string
|
||||
}[]
|
||||
const slot = inputs?.[0]
|
||||
return {
|
||||
label: slot?.label || null,
|
||||
name: slot?.name || null,
|
||||
@@ -97,8 +112,14 @@ test.describe('Subgraph Slot Rename Dialog', () => {
|
||||
|
||||
// Verify the second rename worked
|
||||
const afterSecondRename = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not available')
|
||||
const canvas = app.canvas
|
||||
if (!canvas) throw new Error('Canvas not available')
|
||||
const graph = canvas.graph
|
||||
if (!graph || !('inputs' in graph)) throw new Error('Not in subgraph')
|
||||
const inputs = graph.inputs as { label?: string }[]
|
||||
return inputs?.[0]?.label || null
|
||||
})
|
||||
expect(afterSecondRename).toBe(SECOND_RENAMED_NAME)
|
||||
})
|
||||
@@ -113,12 +134,20 @@ test.describe('Subgraph Slot Rename Dialog', () => {
|
||||
|
||||
// Get initial output slot label
|
||||
const initialOutputLabel = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.outputs?.[0]?.label || graph.outputs?.[0]?.name || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not available')
|
||||
const canvas = app.canvas
|
||||
if (!canvas) throw new Error('Canvas not available')
|
||||
const graph = canvas.graph
|
||||
if (!graph || !('outputs' in graph)) throw new Error('Not in subgraph')
|
||||
const outputs = graph.outputs as { label?: string; name?: string }[]
|
||||
return outputs?.[0]?.label || outputs?.[0]?.name || null
|
||||
})
|
||||
|
||||
// First rename
|
||||
await comfyPage.rightClickSubgraphOutputSlot(initialOutputLabel)
|
||||
await comfyPage.rightClickSubgraphOutputSlot(
|
||||
initialOutputLabel ?? undefined
|
||||
)
|
||||
await comfyPage.clickLitegraphContextMenuItem('Rename Slot')
|
||||
|
||||
await comfyPage.page.waitForSelector(SELECTORS.promptDialog, {
|
||||
|
||||
@@ -26,8 +26,14 @@ test.describe('Subgraph Operations', () => {
|
||||
comfyPage: typeof test.prototype.comfyPage,
|
||||
type: 'inputs' | 'outputs'
|
||||
): Promise<number> {
|
||||
return await comfyPage.page.evaluate((slotType) => {
|
||||
return window['app'].canvas.graph[slotType]?.length || 0
|
||||
return await comfyPage.page.evaluate((slotType: 'inputs' | 'outputs') => {
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !(slotType in graph)) return 0
|
||||
return (
|
||||
(graph as unknown as Record<string, unknown[]>)[slotType]?.length || 0
|
||||
)
|
||||
}, type)
|
||||
}
|
||||
|
||||
@@ -36,7 +42,11 @@ test.describe('Subgraph Operations', () => {
|
||||
comfyPage: typeof test.prototype.comfyPage
|
||||
): Promise<number> {
|
||||
return await comfyPage.page.evaluate(() => {
|
||||
return window['app'].canvas.graph.nodes?.length || 0
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph) return 0
|
||||
return graph.nodes?.length || 0
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,7 +55,9 @@ test.describe('Subgraph Operations', () => {
|
||||
comfyPage: typeof test.prototype.comfyPage
|
||||
): Promise<boolean> {
|
||||
return await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
return graph?.constructor?.name === 'Subgraph'
|
||||
})
|
||||
}
|
||||
@@ -130,11 +142,16 @@ test.describe('Subgraph Operations', () => {
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
const initialInputLabel = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
await comfyPage.rightClickSubgraphInputSlot(initialInputLabel)
|
||||
await comfyPage.rightClickSubgraphInputSlot(
|
||||
initialInputLabel ?? undefined
|
||||
)
|
||||
await comfyPage.clickLitegraphContextMenuItem('Rename Slot')
|
||||
|
||||
await comfyPage.page.waitForSelector(SELECTORS.promptDialog, {
|
||||
@@ -148,8 +165,11 @@ test.describe('Subgraph Operations', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newInputName = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
expect(newInputName).toBe(RENAMED_INPUT_NAME)
|
||||
@@ -163,11 +183,16 @@ test.describe('Subgraph Operations', () => {
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
const initialInputLabel = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
await comfyPage.doubleClickSubgraphInputSlot(initialInputLabel)
|
||||
await comfyPage.doubleClickSubgraphInputSlot(
|
||||
initialInputLabel ?? undefined
|
||||
)
|
||||
|
||||
await comfyPage.page.waitForSelector(SELECTORS.promptDialog, {
|
||||
state: 'visible'
|
||||
@@ -180,8 +205,11 @@ test.describe('Subgraph Operations', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newInputName = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
expect(newInputName).toBe(RENAMED_INPUT_NAME)
|
||||
@@ -195,11 +223,16 @@ test.describe('Subgraph Operations', () => {
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
const initialOutputLabel = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.outputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('outputs' in graph)) return null
|
||||
return (graph.outputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
await comfyPage.doubleClickSubgraphOutputSlot(initialOutputLabel)
|
||||
await comfyPage.doubleClickSubgraphOutputSlot(
|
||||
initialOutputLabel ?? undefined
|
||||
)
|
||||
|
||||
await comfyPage.page.waitForSelector(SELECTORS.promptDialog, {
|
||||
state: 'visible'
|
||||
@@ -213,8 +246,11 @@ test.describe('Subgraph Operations', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newOutputName = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.outputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('outputs' in graph)) return null
|
||||
return (graph.outputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
expect(newOutputName).toBe(renamedOutputName)
|
||||
@@ -230,12 +266,17 @@ test.describe('Subgraph Operations', () => {
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
const initialInputLabel = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
// Test that right-click still works for renaming
|
||||
await comfyPage.rightClickSubgraphInputSlot(initialInputLabel)
|
||||
await comfyPage.rightClickSubgraphInputSlot(
|
||||
initialInputLabel ?? undefined
|
||||
)
|
||||
await comfyPage.clickLitegraphContextMenuItem('Rename Slot')
|
||||
|
||||
await comfyPage.page.waitForSelector(SELECTORS.promptDialog, {
|
||||
@@ -250,8 +291,11 @@ test.describe('Subgraph Operations', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newInputName = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
expect(newInputName).toBe(rightClickRenamedName)
|
||||
@@ -267,14 +311,30 @@ test.describe('Subgraph Operations', () => {
|
||||
await subgraphNode.navigateIntoSubgraph()
|
||||
|
||||
const initialInputLabel = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
// Use direct pointer event approach to double-click on label
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const app = window['app']
|
||||
const graph = app.canvas.graph
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph as {
|
||||
inputs?: { label?: string; labelPos?: [number, number] }[]
|
||||
inputNode?: {
|
||||
onPointerDown?: (
|
||||
e: unknown,
|
||||
pointer: unknown,
|
||||
linkConnector: unknown
|
||||
) => void
|
||||
}
|
||||
} | null
|
||||
if (!graph || !('inputs' in graph)) {
|
||||
throw new Error('Not in a subgraph')
|
||||
}
|
||||
const input = graph.inputs?.[0]
|
||||
|
||||
if (!input?.labelPos) {
|
||||
@@ -302,8 +362,11 @@ test.describe('Subgraph Operations', () => {
|
||||
)
|
||||
|
||||
// Trigger double-click if pointer has the handler
|
||||
if (app.canvas.pointer.onDoubleClick) {
|
||||
app.canvas.pointer.onDoubleClick(leftClickEvent)
|
||||
const pointer = app.canvas.pointer as {
|
||||
onDoubleClick?: (e: unknown) => void
|
||||
}
|
||||
if (pointer.onDoubleClick) {
|
||||
pointer.onDoubleClick(leftClickEvent)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -322,8 +385,11 @@ test.describe('Subgraph Operations', () => {
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newInputName = await comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
return graph.inputs?.[0]?.label || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !('inputs' in graph)) return null
|
||||
return (graph.inputs as { label?: string }[])?.[0]?.label ?? null
|
||||
})
|
||||
|
||||
expect(newInputName).toBe(labelClickRenamedName)
|
||||
@@ -334,7 +400,11 @@ test.describe('Subgraph Operations', () => {
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('subgraphs/subgraph-compressed-target-slot')
|
||||
const step = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].graph.nodes[0].widgets[0].options.step
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.graph
|
||||
if (!graph?.nodes?.[0]) return undefined
|
||||
return graph.nodes[0].widgets?.[0]?.options?.step
|
||||
})
|
||||
expect(step).toBe(10)
|
||||
})
|
||||
@@ -344,8 +414,6 @@ test.describe('Subgraph Operations', () => {
|
||||
test('Can create subgraph from selected nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
|
||||
const initialNodeCount = await getGraphNodeCount(comfyPage)
|
||||
|
||||
await comfyPage.ctrlA()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
@@ -453,8 +521,12 @@ test.describe('Subgraph Operations', () => {
|
||||
const initialNodeCount = await getGraphNodeCount(comfyPage)
|
||||
|
||||
const nodesInSubgraph = await comfyPage.page.evaluate(() => {
|
||||
const nodes = window['app'].canvas.graph.nodes
|
||||
return nodes?.[0]?.id || null
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph) return null
|
||||
const nodes = graph.nodes
|
||||
return nodes?.[0]?.id ?? null
|
||||
})
|
||||
|
||||
expect(nodesInSubgraph).not.toBeNull()
|
||||
@@ -682,7 +754,11 @@ test.describe('Subgraph Operations', () => {
|
||||
|
||||
// Check that the subgraph node has no widgets after removing the text slot
|
||||
const widgetCount = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].canvas.graph.nodes[0].widgets?.length || 0
|
||||
const app = window['app']
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
if (!graph || !graph.nodes?.[0]) return 0
|
||||
return graph.nodes[0].widgets?.length || 0
|
||||
})
|
||||
|
||||
expect(widgetCount).toBe(0)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { SettingParams } from '../../src/platform/settings/types'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
@@ -10,7 +11,7 @@ test.describe('Settings Search functionality', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// Register test settings to verify hidden/deprecated filtering
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].registerExtension({
|
||||
window['app']?.registerExtension({
|
||||
name: 'TestSettingsExtension',
|
||||
settings: [
|
||||
{
|
||||
@@ -19,7 +20,7 @@ test.describe('Settings Search functionality', () => {
|
||||
type: 'hidden',
|
||||
defaultValue: 'hidden_value',
|
||||
category: ['Test', 'Hidden']
|
||||
},
|
||||
} as unknown as SettingParams,
|
||||
{
|
||||
id: 'TestDeprecatedSetting',
|
||||
name: 'Test Deprecated Setting',
|
||||
@@ -27,14 +28,14 @@ test.describe('Settings Search functionality', () => {
|
||||
defaultValue: 'deprecated_value',
|
||||
deprecated: true,
|
||||
category: ['Test', 'Deprecated']
|
||||
},
|
||||
} as unknown as SettingParams,
|
||||
{
|
||||
id: 'TestVisibleSetting',
|
||||
name: 'Test Visible Setting',
|
||||
type: 'text',
|
||||
defaultValue: 'visible_value',
|
||||
category: ['Test', 'Visible']
|
||||
}
|
||||
} as unknown as SettingParams
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,6 +3,10 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '../../../fixtures/ComfyPage'
|
||||
|
||||
interface GraphWithNodes {
|
||||
_nodes_by_id: Record<string, { widgets: unknown[] }>
|
||||
}
|
||||
|
||||
test.describe('Vue Widget Reactivity', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
@@ -13,17 +17,20 @@ test.describe('Vue Widget Reactivity', () => {
|
||||
'css=[data-testid="node-body-4"] > .lg-node-widgets > div'
|
||||
)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window['graph']._nodes_by_id['4']
|
||||
const graph = window['graph'] as GraphWithNodes
|
||||
const node = graph._nodes_by_id['4']
|
||||
node.widgets.push(node.widgets[0])
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(2)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window['graph']._nodes_by_id['4']
|
||||
const graph = window['graph'] as GraphWithNodes
|
||||
const node = graph._nodes_by_id['4']
|
||||
node.widgets[2] = node.widgets[0]
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(3)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window['graph']._nodes_by_id['4']
|
||||
const graph = window['graph'] as GraphWithNodes
|
||||
const node = graph._nodes_by_id['4']
|
||||
node.widgets.splice(0, 0, node.widgets[0])
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(4)
|
||||
@@ -33,17 +40,20 @@ test.describe('Vue Widget Reactivity', () => {
|
||||
'css=[data-testid="node-body-3"] > .lg-node-widgets > div'
|
||||
)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window['graph']._nodes_by_id['3']
|
||||
const graph = window['graph'] as GraphWithNodes
|
||||
const node = graph._nodes_by_id['3']
|
||||
node.widgets.pop()
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(5)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window['graph']._nodes_by_id['3']
|
||||
const graph = window['graph'] as GraphWithNodes
|
||||
const node = graph._nodes_by_id['3']
|
||||
node.widgets.length--
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(4)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const node = window['graph']._nodes_by_id['3']
|
||||
const graph = window['graph'] as GraphWithNodes
|
||||
const node = graph._nodes_by_id['3']
|
||||
node.widgets.splice(0, 1)
|
||||
})
|
||||
await expect(loadCheckpointNode).toHaveCount(3)
|
||||
|
||||
@@ -36,10 +36,15 @@ test.describe('Combo text widget', () => {
|
||||
}) => {
|
||||
const getComboValues = async () =>
|
||||
comfyPage.page.evaluate(() => {
|
||||
return window['app'].graph.nodes
|
||||
.find((node) => node.title === 'Node With Optional Combo Input')
|
||||
.widgets.find((widget) => widget.name === 'optional_combo_input')
|
||||
.options.values
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('app or graph not found')
|
||||
const node = app.graph.nodes.find(
|
||||
(n: { title: string }) => n.title === 'Node With Optional Combo Input'
|
||||
) as { widgets: Array<{ name: string; options: { values: unknown } }> }
|
||||
const widget = node?.widgets?.find(
|
||||
(w) => w.name === 'optional_combo_input'
|
||||
)
|
||||
return widget?.options?.values
|
||||
})
|
||||
|
||||
await comfyPage.loadWorkflow('inputs/optional_combo_input')
|
||||
@@ -71,9 +76,13 @@ test.describe('Combo text widget', () => {
|
||||
await comfyPage.nextFrame()
|
||||
// get the combo widget's values
|
||||
const comboValues = await comfyPage.page.evaluate(() => {
|
||||
return window['app'].graph.nodes
|
||||
.find((node) => node.title === 'Node With V2 Combo Input')
|
||||
.widgets.find((widget) => widget.name === 'combo_input').options.values
|
||||
const app = window['app']
|
||||
if (!app?.graph) throw new Error('app or graph not found')
|
||||
const node = app.graph.nodes.find(
|
||||
(n: { title: string }) => n.title === 'Node With V2 Combo Input'
|
||||
) as { widgets: Array<{ name: string; options: { values: unknown } }> }
|
||||
const widget = node?.widgets?.find((w) => w.name === 'combo_input')
|
||||
return widget?.options?.values
|
||||
})
|
||||
expect(comboValues).toEqual(['A', 'B'])
|
||||
})
|
||||
@@ -99,16 +108,20 @@ test.describe('Slider widget', () => {
|
||||
const widget = await node.getWidget(0)
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const widget = window['app'].graph.nodes[0].widgets[0]
|
||||
const app = window['app']
|
||||
if (!app?.graph?.nodes?.[0]?.widgets?.[0]) return
|
||||
const widget = app.graph.nodes[0].widgets[0]
|
||||
widget.callback = (value: number) => {
|
||||
window['widgetValue'] = value
|
||||
;(window as unknown as Record<string, unknown>)['widgetValue'] = value
|
||||
}
|
||||
})
|
||||
await widget.dragHorizontal(50)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('slider_widget_dragged.png')
|
||||
|
||||
expect(
|
||||
await comfyPage.page.evaluate(() => window['widgetValue'])
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['widgetValue']
|
||||
)
|
||||
).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -120,16 +133,20 @@ test.describe('Number widget', () => {
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
const widget = await node.getWidget(0)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const widget = window['app'].graph.nodes[0].widgets[0]
|
||||
const app = window['app']
|
||||
if (!app?.graph?.nodes?.[0]?.widgets?.[0]) return
|
||||
const widget = app.graph.nodes[0].widgets[0]
|
||||
widget.callback = (value: number) => {
|
||||
window['widgetValue'] = value
|
||||
;(window as unknown as Record<string, unknown>)['widgetValue'] = value
|
||||
}
|
||||
})
|
||||
await widget.dragHorizontal(50)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('seed_widget_dragged.png')
|
||||
|
||||
expect(
|
||||
await comfyPage.page.evaluate(() => window['widgetValue'])
|
||||
await comfyPage.page.evaluate(
|
||||
() => (window as unknown as Record<string, unknown>)['widgetValue']
|
||||
)
|
||||
).toBeDefined()
|
||||
})
|
||||
})
|
||||
@@ -141,8 +158,16 @@ test.describe('Dynamic widget manipulation', () => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['graph'].nodes[0].addWidget('number', 'new_widget', 10)
|
||||
window['graph'].setDirtyCanvas(true, true)
|
||||
type GraphWithNodes = {
|
||||
nodes: Array<{
|
||||
addWidget: (type: string, name: string, value: number) => void
|
||||
}>
|
||||
setDirtyCanvas: (fg: boolean, bg: boolean) => void
|
||||
}
|
||||
const graph = window['graph'] as GraphWithNodes | undefined
|
||||
if (!graph?.nodes?.[0]) return
|
||||
graph.nodes[0].addWidget('number', 'new_widget', 10)
|
||||
graph.setDirtyCanvas(true, true)
|
||||
})
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('ksampler_widget_added.png')
|
||||
@@ -209,6 +234,23 @@ test.describe('Image widget', () => {
|
||||
comfyPage
|
||||
}) => {
|
||||
const [x, y] = await comfyPage.page.evaluate(() => {
|
||||
type TestNode = {
|
||||
pos: [number, number]
|
||||
size: [number, number]
|
||||
widgets: Array<{ last_y: number }>
|
||||
imgs?: HTMLImageElement[]
|
||||
imageIndex?: number
|
||||
}
|
||||
type TestGraph = { nodes: TestNode[] }
|
||||
type TestApp = {
|
||||
canvas: { setDirty: (dirty: boolean) => void }
|
||||
canvasPosToClientPos: (pos: [number, number]) => [number, number]
|
||||
}
|
||||
const graph = window['graph'] as TestGraph | undefined
|
||||
const app = window['app'] as TestApp | undefined
|
||||
if (!graph?.nodes?.[6] || !app?.canvas) {
|
||||
throw new Error('graph or app not found')
|
||||
}
|
||||
const src =
|
||||
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='768' height='512' viewBox='0 0 1 1'%3E%3Crect width='1' height='1' stroke='black'/%3E%3C/svg%3E"
|
||||
const image1 = new Image()
|
||||
@@ -220,8 +262,9 @@ test.describe('Image widget', () => {
|
||||
targetNode.imageIndex = 1
|
||||
app.canvas.setDirty(true)
|
||||
|
||||
const lastWidget = targetNode.widgets.at(-1)
|
||||
const x = targetNode.pos[0] + targetNode.size[0] - 41
|
||||
const y = targetNode.pos[1] + targetNode.widgets.at(-1).last_y + 30
|
||||
const y = targetNode.pos[1] + (lastWidget?.last_y ?? 0) + 30
|
||||
return app.canvasPosToClientPos([x, y])
|
||||
})
|
||||
|
||||
@@ -313,8 +356,10 @@ test.describe('Animated image widget', () => {
|
||||
// Simulate the graph executing
|
||||
await comfyPage.page.evaluate(
|
||||
([loadId, saveId]) => {
|
||||
const app = window['app']
|
||||
if (!app?.nodeOutputs || !app?.canvas) return
|
||||
// Set the output of the SaveAnimatedWEBP node to equal the loader node's image
|
||||
window['app'].nodeOutputs[saveId] = window['app'].nodeOutputs[loadId]
|
||||
app.nodeOutputs[saveId] = app.nodeOutputs[loadId]
|
||||
app.canvas.setDirty(true)
|
||||
},
|
||||
[loadAnimatedWebpNode.id, saveAnimatedWebpNode.id]
|
||||
|
||||
Reference in New Issue
Block a user