From 4ef1fd984b302147045edae77e23fc18163f3564 Mon Sep 17 00:00:00 2001 From: DrJKL Date: Mon, 12 Jan 2026 11:16:21 -0800 Subject: [PATCH] fix(browser_tests): remove all @ts-expect-error and type assertions --- .../fixtures/utils/litegraphUtils.ts | 2 +- browser_tests/globalSetup.ts | 2 +- browser_tests/globalTeardown.ts | 2 +- browser_tests/helpers/actionbar.ts | 16 +- browser_tests/helpers/templates.ts | 8 +- browser_tests/tests/actionbar.spec.ts | 22 ++- browser_tests/tests/browserTabTitle.spec.ts | 21 ++- browser_tests/tests/changeTracker.spec.ts | 12 +- browser_tests/tests/colorPalette.spec.ts | 18 +- browser_tests/tests/commands.spec.ts | 18 +- browser_tests/tests/dialog.spec.ts | 8 +- browser_tests/tests/extensionAPI.spec.ts | 163 +++++++++++++----- browser_tests/tests/featureFlags.spec.ts | 163 ++++++++++++------ browser_tests/tests/graph.spec.ts | 4 +- browser_tests/tests/graphCanvasMenu.spec.ts | 4 +- browser_tests/tests/groupNode.spec.ts | 56 ++++-- browser_tests/tests/keybindings.spec.ts | 30 ++-- browser_tests/tests/lodThreshold.spec.ts | 46 +++-- browser_tests/tests/menu.spec.ts | 8 +- browser_tests/tests/nodeBadge.spec.ts | 6 + browser_tests/tests/nodeDisplay.spec.ts | 20 ++- browser_tests/tests/nodeHelp.spec.ts | 8 +- browser_tests/tests/nodeSearchBox.spec.ts | 6 +- browser_tests/tests/remoteWidgets.spec.ts | 27 ++- .../tests/sidebar/nodeLibrary.spec.ts | 10 +- browser_tests/tests/sidebar/workflows.spec.ts | 13 +- .../tests/subgraph-rename-dialog.spec.ts | 51 ++++-- browser_tests/tests/subgraph.spec.ts | 150 ++++++++++++---- browser_tests/tests/useSettingSearch.spec.ts | 9 +- .../vueNodes/widgets/widgetReactivity.spec.ts | 22 ++- browser_tests/tests/widget.spec.ts | 79 +++++++-- 31 files changed, 727 insertions(+), 277 deletions(-) diff --git a/browser_tests/fixtures/utils/litegraphUtils.ts b/browser_tests/fixtures/utils/litegraphUtils.ts index 72f4cc165..a44d61da5 100644 --- a/browser_tests/fixtures/utils/litegraphUtils.ts +++ b/browser_tests/fixtures/utils/litegraphUtils.ts @@ -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}:`, { diff --git a/browser_tests/globalSetup.ts b/browser_tests/globalSetup.ts index 881ef11c4..b43b77c6a 100644 --- a/browser_tests/globalSetup.ts +++ b/browser_tests/globalSetup.ts @@ -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']) diff --git a/browser_tests/globalTeardown.ts b/browser_tests/globalTeardown.ts index aeed77294..c69f563df 100644 --- a/browser_tests/globalTeardown.ts +++ b/browser_tests/globalTeardown.ts @@ -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']) diff --git a/browser_tests/helpers/actionbar.ts b/browser_tests/helpers/actionbar.ts index 6c368c4d6..f48646ae3 100644 --- a/browser_tests/helpers/actionbar.ts +++ b/browser_tests/helpers/actionbar.ts @@ -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 }) } } diff --git a/browser_tests/helpers/templates.ts b/browser_tests/helpers/templates.ts index ca74096ad..9fe72223a 100644 --- a/browser_tests/helpers/templates.ts +++ b/browser_tests/helpers/templates.ts @@ -32,9 +32,11 @@ export class ComfyTemplates { } async getAllTemplates(): Promise { - 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) } diff --git a/browser_tests/tests/actionbar.spec.ts b/browser_tests/tests/actionbar.spec.ts index bd086b461..8c4b77ca3 100644 --- a/browser_tests/tests/actionbar.spec.ts +++ b/browser_tests/tests/actionbar.spec.ts @@ -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) } diff --git a/browser_tests/tests/browserTabTitle.spec.ts b/browser_tests/tests/browserTabTitle.spec.ts index fd855ea4a..9faa22f2d 100644 --- a/browser_tests/tests/browserTabTitle.spec.ts +++ b/browser_tests/tests/browserTabTitle.spec.ts @@ -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 } } + } + return extMgr.workflow?.activeWorkflow?.delete?.() }) }) }) diff --git a/browser_tests/tests/changeTracker.spec.ts b/browser_tests/tests/changeTracker.spec.ts index 8e39154f1..0ab11c343 100644 --- a/browser_tests/tests/changeTracker.spec.ts +++ b/browser_tests/tests/changeTracker.spec.ts @@ -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() diff --git a/browser_tests/tests/colorPalette.spec.ts b/browser_tests/tests/colorPalette.spec.ts index ce372ddfa..11d5c3178 100644 --- a/browser_tests/tests/colorPalette.spec.ts +++ b/browser_tests/tests/colorPalette.spec.ts @@ -7,7 +7,15 @@ test.beforeEach(async ({ comfyPage }) => { await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled') }) -const customColorPalettes: Record = { +type TestPalette = Omit & { + colors: { + node_slot: Record + litegraph_base: Partial + comfy_base: Partial + } +} + +const customColorPalettes: Record = { 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') }) diff --git a/browser_tests/tests/commands.spec.ts b/browser_tests/tests/commands.spec.ts index e271f2e15..0a355cf54 100644 --- a/browser_tests/tests/commands.spec.ts +++ b/browser_tests/tests/commands.spec.ts @@ -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)['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)['foo'] + ) + ).toBe(true) }) test('Should execute async command', async ({ comfyPage }) => { await comfyPage.registerCommand('TestCommand', async () => { await new Promise((resolve) => setTimeout(() => { - window['foo'] = true + ;(window as unknown as Record)['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)['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((resolve, reject) => + await new Promise((_resolve, reject) => setTimeout(() => { reject(new Error('Test error')) }, 5) diff --git a/browser_tests/tests/dialog.spec.ts b/browser_tests/tests/dialog.spec.ts index 081f47275..8d4ba7981 100644 --- a/browser_tests/tests/dialog.spec.ts +++ b/browser_tests/tests/dialog.spec.ts @@ -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') diff --git a/browser_tests/tests/extensionAPI.spec.ts b/browser_tests/tests/extensionAPI.spec.ts index 38f4a6c1d..da878ae65 100644 --- a/browser_tests/tests/extensionAPI.spec.ts +++ b/browser_tests/tests/extensionAPI.spec.ts @@ -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)['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)['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)['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)['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 + 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)['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)['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 + 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)['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)['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)['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)['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)['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)['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)['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)['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)['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)[ + '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)[ + 'selectionCommandExecuted' + ] + ) ).toBe(true) }) }) diff --git a/browser_tests/tests/featureFlags.spec.ts b/browser_tests/tests/featureFlags.spec.ts index 707976829..c3ee28fdc 100644 --- a/browser_tests/tests/featureFlags.spec.ts +++ b/browser_tests/tests/featureFlags.spec.ts @@ -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 } } + } + 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 } + serverFeatureFlags: Record + } + } + 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 } }) diff --git a/browser_tests/tests/graph.spec.ts b/browser_tests/tests/graph.spec.ts index 0c021b5f2..ebabf2892 100644 --- a/browser_tests/tests/graph.spec.ts +++ b/browser_tests/tests/graph.spec.ts @@ -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) }) diff --git a/browser_tests/tests/graphCanvasMenu.spec.ts b/browser_tests/tests/graphCanvasMenu.spec.ts index fc8385e49..84c9477d6 100644 --- a/browser_tests/tests/graphCanvasMenu.spec.ts +++ b/browser_tests/tests/graphCanvasMenu.spec.ts @@ -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 diff --git a/browser_tests/tests/groupNode.spec.ts b/browser_tests/tests/groupNode.spec.ts index 9d67a0951..25f22a525 100644 --- a/browser_tests/tests/groupNode.spec.ts +++ b/browser_tests/tests/groupNode.spec.ts @@ -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 }> + | 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[0] + ) + }, currentGraphState) await comfyPage.nextFrame() await verifyNodeLoaded(comfyPage, 1) }) diff --git a/browser_tests/tests/keybindings.spec.ts b/browser_tests/tests/keybindings.spec.ts index f4244ae66..81e6e19f4 100644 --- a/browser_tests/tests/keybindings.spec.ts +++ b/browser_tests/tests/keybindings.spec.ts @@ -11,23 +11,25 @@ test.describe('Keybindings', () => { comfyPage }) => { await comfyPage.registerKeybinding({ key: 'k' }, () => { - window['TestCommand'] = true + ;(window as unknown as Record)['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)['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)['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)['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)['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)['TestCommand'] + ) + ).toBe(undefined) }) }) diff --git a/browser_tests/tests/lodThreshold.spec.ts b/browser_tests/tests/lodThreshold.spec.ts index 154ac3c16..2c3778f0e 100644 --- a/browser_tests/tests/lodThreshold.spec.ts +++ b/browser_tests/tests/lodThreshold.spec.ts @@ -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 diff --git a/browser_tests/tests/menu.spec.ts b/browser_tests/tests/menu.spec.ts index 2c3ffb3d4..2086960fa 100644 --- a/browser_tests/tests/menu.spec.ts +++ b/browser_tests/tests/menu.spec.ts @@ -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: [ { diff --git a/browser_tests/tests/nodeBadge.spec.ts b/browser_tests/tests/nodeBadge.spec.ts index 111efe29c..76d489640 100644 --- a/browser_tests/tests/nodeBadge.spec.ts +++ b/browser_tests/tests/nodeBadge.spec.ts @@ -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 diff --git a/browser_tests/tests/nodeDisplay.spec.ts b/browser_tests/tests/nodeDisplay.spec.ts index fdaae14bc..dec3a5c6b 100644 --- a/browser_tests/tests/nodeDisplay.spec.ts +++ b/browser_tests/tests/nodeDisplay.spec.ts @@ -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('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('inputs') + const renamedInput = inputs.find( + (w: INodeInputSlot) => w.name === 'breadth' + ) expect(renamedInput).toBeUndefined() }) test('slider', async ({ comfyPage }) => { diff --git a/browser_tests/tests/nodeHelp.spec.ts b/browser_tests/tests/nodeHelp.spec.ts index cfb04bc46..370f08f0e 100644 --- a/browser_tests/tests/nodeHelp.spec.ts +++ b/browser_tests/tests/nodeHelp.spec.ts @@ -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]) diff --git a/browser_tests/tests/nodeSearchBox.spec.ts b/browser_tests/tests/nodeSearchBox.spec.ts index 2eea22329..4395636db 100644 --- a/browser_tests/tests/nodeSearchBox.spec.ts +++ b/browser_tests/tests/nodeSearchBox.spec.ts @@ -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 diff --git a/browser_tests/tests/remoteWidgets.spec.ts b/browser_tests/tests/remoteWidgets.spec.ts index 18cc77f44..6da21501a 100644 --- a/browser_tests/tests/remoteWidgets.spec.ts +++ b/browser_tests/tests/remoteWidgets.spec.ts @@ -25,24 +25,33 @@ test.describe('Remote COMBO Widget', () => { comfyPage: ComfyPage, nodeName: string ): Promise => { - 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 diff --git a/browser_tests/tests/sidebar/nodeLibrary.spec.ts b/browser_tests/tests/sidebar/nodeLibrary.spec.ts index 4f476376a..62f1a46c6 100644 --- a/browser_tests/tests/sidebar/nodeLibrary.spec.ts +++ b/browser_tests/tests/sidebar/nodeLibrary.spec.ts @@ -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 | 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 }) => { diff --git a/browser_tests/tests/sidebar/workflows.spec.ts b/browser_tests/tests/sidebar/workflows.spec.ts index 86f37b23d..d5bb16d5c 100644 --- a/browser_tests/tests/sidebar/workflows.spec.ts +++ b/browser_tests/tests/sidebar/workflows.spec.ts @@ -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) }) diff --git a/browser_tests/tests/subgraph-rename-dialog.spec.ts b/browser_tests/tests/subgraph-rename-dialog.spec.ts index 0bdb9766d..5d566c077 100644 --- a/browser_tests/tests/subgraph-rename-dialog.spec.ts +++ b/browser_tests/tests/subgraph-rename-dialog.spec.ts @@ -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, { diff --git a/browser_tests/tests/subgraph.spec.ts b/browser_tests/tests/subgraph.spec.ts index 635915621..419a19b86 100644 --- a/browser_tests/tests/subgraph.spec.ts +++ b/browser_tests/tests/subgraph.spec.ts @@ -26,8 +26,14 @@ test.describe('Subgraph Operations', () => { comfyPage: typeof test.prototype.comfyPage, type: 'inputs' | 'outputs' ): Promise { - 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)[slotType]?.length || 0 + ) }, type) } @@ -36,7 +42,11 @@ test.describe('Subgraph Operations', () => { comfyPage: typeof test.prototype.comfyPage ): Promise { 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 { 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) diff --git a/browser_tests/tests/useSettingSearch.spec.ts b/browser_tests/tests/useSettingSearch.spec.ts index f022bb4bd..5baeec69f 100644 --- a/browser_tests/tests/useSettingSearch.spec.ts +++ b/browser_tests/tests/useSettingSearch.spec.ts @@ -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 ] }) }) diff --git a/browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts b/browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts index 6f3701c12..c0c1655ec 100644 --- a/browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts +++ b/browser_tests/tests/vueNodes/widgets/widgetReactivity.spec.ts @@ -3,6 +3,10 @@ import { comfyPageFixture as test } from '../../../fixtures/ComfyPage' +interface GraphWithNodes { + _nodes_by_id: Record +} + 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) diff --git a/browser_tests/tests/widget.spec.ts b/browser_tests/tests/widget.spec.ts index c8448f36c..23c804f73 100644 --- a/browser_tests/tests/widget.spec.ts +++ b/browser_tests/tests/widget.spec.ts @@ -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)['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)['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)['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)['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]