Compare commits
53 Commits
core/1.8
...
preview-sc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84c5f7bf16 | ||
|
|
a914456827 | ||
|
|
340513e27f | ||
|
|
117c8be3a0 | ||
|
|
68f6d51ad2 | ||
|
|
2da23fd373 | ||
|
|
7ddcac88d7 | ||
|
|
40a817bb0f | ||
|
|
774ed4178f | ||
|
|
78e4161c51 | ||
|
|
dda9a72966 | ||
|
|
d7673af8f5 | ||
|
|
2b18949615 | ||
|
|
629ac63f06 | ||
|
|
1061620783 | ||
|
|
af7a6601e0 | ||
|
|
0e0c4b1302 | ||
|
|
fb170c9ee9 | ||
|
|
7ef304b381 | ||
|
|
50833341bb | ||
|
|
f5c5a95bdc | ||
|
|
b700cc1824 | ||
|
|
a6031ec2be | ||
|
|
a4d99d9d28 | ||
|
|
5a7465a907 | ||
|
|
6525ae7cf4 | ||
|
|
c6ef107111 | ||
|
|
3fbccd20ff | ||
|
|
fa0682d66e | ||
|
|
f7b613c6cb | ||
|
|
292af3fe3f | ||
|
|
2c12df12ab | ||
|
|
d0e99beaa7 | ||
|
|
6b64b74f6c | ||
|
|
0af4768dd2 | ||
|
|
5f850ddaa4 | ||
|
|
9fb3235df4 | ||
|
|
628facaa75 | ||
|
|
eb5a4b65ab | ||
|
|
98c197e8b1 | ||
|
|
90914a40ba | ||
|
|
821816955f | ||
|
|
b4121008cd | ||
|
|
3730c2b36f | ||
|
|
77be5ac514 | ||
|
|
83759b9a4a | ||
|
|
b8f187713e | ||
|
|
b8088ad782 | ||
|
|
a37671b154 | ||
|
|
57bc7ad312 | ||
|
|
5f59fbdead | ||
|
|
4eed9c7e53 | ||
|
|
ee6197785a |
@@ -15,7 +15,7 @@ const folderStructure = `
|
||||
src/
|
||||
components/
|
||||
constants/
|
||||
hooks/
|
||||
composables/
|
||||
views/
|
||||
stores/
|
||||
services/
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
{ "name": "VAE", "type": "VAE", "links": [8], "slot_index": 2 }
|
||||
],
|
||||
"properties": {},
|
||||
"widgets_values": ["v1-5-pruned-emaonly.ckpt"]
|
||||
"widgets_values": ["v1-5-pruned-emaonly-fp16.safetensors"]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"last_node_id": 19,
|
||||
"last_link_id": 14,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 19,
|
||||
"type": "workflow>two_VAE_decode",
|
||||
"pos": [
|
||||
1368.800048828125,
|
||||
768.7999877929688
|
||||
],
|
||||
"size": [
|
||||
418.1999816894531,
|
||||
86
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null
|
||||
},
|
||||
{
|
||||
"name": "VAEDecode IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {}
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"node_versions": {},
|
||||
"ue_links": [],
|
||||
"groupNodes": {
|
||||
"two_VAE_decode": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": -1,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1368.800048828125,
|
||||
768.7999877929688
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null,
|
||||
"localized_name": "samples"
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null,
|
||||
"localized_name": "vae"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"localized_name": "IMAGE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"id": -1,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1368.800048828125,
|
||||
873.7999877929688
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": null,
|
||||
"localized_name": "samples"
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": null,
|
||||
"localized_name": "vae"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"localized_name": "IMAGE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"index": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"external": [],
|
||||
"config": {
|
||||
"1": {
|
||||
"input": {
|
||||
"samples": {
|
||||
"visible": false
|
||||
},
|
||||
"vae": {
|
||||
"visible": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
48
browser_tests/assets/remote_widget.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"last_node_id": 15,
|
||||
"last_link_id": 10,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 15,
|
||||
"type": "DevToolsRemoteWidgetNode",
|
||||
"pos": [
|
||||
495,
|
||||
735
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DevToolsRemoteWidgetNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"v1-5-pruned-emaonly-fp16.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8008869919566275,
|
||||
"offset": [
|
||||
538.9801226576359,
|
||||
-55.24554581806672
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
35
browser_tests/assets/widgets/boolean_widget.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 11,
|
||||
"type": "DevToolsNodeWithBooleanInput",
|
||||
"pos": [
|
||||
0,
|
||||
30
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "DevToolsNodeWithBooleanInput"
|
||||
},
|
||||
"widgets_values": [
|
||||
false
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 102 KiB |
@@ -253,7 +253,7 @@ test.describe('Settings', () => {
|
||||
|
||||
// Save keybinding
|
||||
const saveButton = comfyPage.page
|
||||
.getByLabel('Comfy.NewBlankWorkflow')
|
||||
.getByLabel('New Blank Workflow')
|
||||
.getByLabel('Save')
|
||||
await saveButton.click()
|
||||
|
||||
|
||||
@@ -83,6 +83,12 @@ export class NodeWidgetReference {
|
||||
y: pos[1]
|
||||
}
|
||||
}
|
||||
|
||||
async click() {
|
||||
await this.node.comfyPage.canvas.click({
|
||||
position: await this.getPosition()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeReference {
|
||||
|
||||
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 100 KiB |
@@ -134,6 +134,37 @@ test.describe('Group Node', () => {
|
||||
expect(await manage2.getSelectedNodeType()).toBe('g2')
|
||||
})
|
||||
|
||||
test('Preserves hidden input configuration when containing duplicate node types', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('group_node_identical_nodes_hidden_inputs')
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const groupNodeId = 19
|
||||
const groupNodeName = 'two_VAE_decode'
|
||||
|
||||
const totalInputCount = await comfyPage.page.evaluate((nodeName) => {
|
||||
const {
|
||||
extra: { groupNodes }
|
||||
} = window['app'].graph
|
||||
const { nodes } = groupNodes[nodeName]
|
||||
return nodes.reduce((acc: number, node) => {
|
||||
return acc + node.inputs.length
|
||||
}, 0)
|
||||
}, groupNodeName)
|
||||
|
||||
const visibleInputCount = await comfyPage.page.evaluate((id) => {
|
||||
const node = window['app'].graph.getNodeById(id)
|
||||
return node.inputs.length
|
||||
}, groupNodeId)
|
||||
|
||||
// Verify there are 4 total inputs (2 VAE decode nodes with 2 inputs each)
|
||||
expect(totalInputCount).toBe(4)
|
||||
|
||||
// Verify there are 2 visible inputs (2 have been hidden in config)
|
||||
expect(visibleInputCount).toBe(2)
|
||||
})
|
||||
|
||||
test('Reconnects inputs after configuration changed via manage dialog save', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 96 KiB |
@@ -707,6 +707,36 @@ test.describe('Menu', () => {
|
||||
'*Unsaved Workflow.json'
|
||||
])
|
||||
})
|
||||
|
||||
test('Can drop workflow from workflows sidebar', async ({ comfyPage }) => {
|
||||
await comfyPage.setupWorkflowsDirectory({
|
||||
'workflow1.json': 'default.json'
|
||||
})
|
||||
await comfyPage.setup()
|
||||
await comfyPage.menu.workflowsTab.open()
|
||||
|
||||
const nodeCount = await comfyPage.getGraphNodesCount()
|
||||
|
||||
// Get the bounding box of the canvas element
|
||||
const canvasBoundingBox = (await comfyPage.page
|
||||
.locator('#graph-canvas')
|
||||
.boundingBox())!
|
||||
|
||||
// Calculate the center position of the canvas
|
||||
const targetPosition = {
|
||||
x: canvasBoundingBox.x + canvasBoundingBox.width / 2,
|
||||
y: canvasBoundingBox.y + canvasBoundingBox.height / 2
|
||||
}
|
||||
|
||||
await comfyPage.page.dragAndDrop(
|
||||
'.comfyui-workflows-browse .node-label:has-text("workflow1.json")',
|
||||
'#graph-canvas',
|
||||
{ targetPosition }
|
||||
)
|
||||
// Wait for the workflow to be inserted
|
||||
await comfyPage.page.waitForTimeout(200)
|
||||
expect(await comfyPage.getGraphNodesCount()).toBe(nodeCount * 2)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Workflows topbar tabs', () => {
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 101 KiB |
258
browser_tests/remoteWidgets.spec.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { ComfyPage, comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Remote COMBO Widget', () => {
|
||||
const mockOptions = ['d', 'c', 'b', 'a']
|
||||
|
||||
const addRemoteWidgetNode = async (
|
||||
comfyPage: ComfyPage,
|
||||
nodeName: string,
|
||||
count: number = 1
|
||||
) => {
|
||||
const tab = comfyPage.menu.nodeLibraryTab
|
||||
await tab.open()
|
||||
await tab.getFolder('DevTools').click()
|
||||
const nodeEntry = tab.getNode(nodeName).first()
|
||||
for (let i = 0; i < count; i++) {
|
||||
await nodeEntry.click()
|
||||
await comfyPage.nextFrame()
|
||||
}
|
||||
}
|
||||
|
||||
const getWidgetOptions = async (
|
||||
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
|
||||
}, nodeName)
|
||||
}
|
||||
|
||||
const waitForWidgetUpdate = async (comfyPage: ComfyPage) => {
|
||||
// Force re-render to trigger first access of widget's options
|
||||
await comfyPage.page.mouse.click(100, 100)
|
||||
await comfyPage.page.waitForTimeout(256)
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
test.describe('Loading options', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route, request) => {
|
||||
const params = new URL(request.url()).searchParams
|
||||
const sort = params.get('sort')
|
||||
await route.fulfill({
|
||||
body: JSON.stringify(sort ? [...mockOptions].sort() : mockOptions),
|
||||
status: 200
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ comfyPage }) => {
|
||||
await comfyPage.page.unroute('**/api/models/checkpoints**')
|
||||
})
|
||||
|
||||
test('lazy loads options when widget is added from node library', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const nodeName = 'Remote Widget Node'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
const widgetOptions = await getWidgetOptions(comfyPage, nodeName)
|
||||
expect(widgetOptions).toEqual(mockOptions)
|
||||
})
|
||||
|
||||
test('lazy loads options when widget is added via workflow load', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const nodeName = 'Remote Widget Node'
|
||||
await comfyPage.loadWorkflow('remote_widget')
|
||||
await comfyPage.page.waitForTimeout(512)
|
||||
|
||||
const node = await comfyPage.page.evaluate((name) => {
|
||||
return window['app'].graph.nodes.find((node) => node.title === name)
|
||||
}, nodeName)
|
||||
expect(node).toBeDefined()
|
||||
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
const widgetOptions = await getWidgetOptions(comfyPage, nodeName)
|
||||
expect(widgetOptions).toEqual(mockOptions)
|
||||
})
|
||||
|
||||
test('applies query parameters from input spec', async ({ comfyPage }) => {
|
||||
const nodeName = 'Remote Widget Node With Sort Query Param'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
const widgetOptions = await getWidgetOptions(comfyPage, nodeName)
|
||||
expect(widgetOptions).not.toEqual(mockOptions)
|
||||
expect(widgetOptions).toEqual([...mockOptions].sort())
|
||||
})
|
||||
|
||||
test('handles empty list of options', async ({ comfyPage }) => {
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route) => {
|
||||
await route.fulfill({ body: JSON.stringify([]), status: 200 })
|
||||
}
|
||||
)
|
||||
|
||||
const nodeName = 'Remote Widget Node'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
const widgetOptions = await getWidgetOptions(comfyPage, nodeName)
|
||||
expect(widgetOptions).toEqual([])
|
||||
})
|
||||
|
||||
test('falls back to default value when non-200 response', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route) => {
|
||||
await route.fulfill({ status: 500 })
|
||||
}
|
||||
)
|
||||
|
||||
const nodeName = 'Remote Widget Node'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
const widgetOptions = await getWidgetOptions(comfyPage, nodeName)
|
||||
|
||||
const defaultValue = 'Loading...'
|
||||
expect(widgetOptions).toEqual(defaultValue)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Lazy Loading Behavior', () => {
|
||||
test('does not fetch options before widget is added to graph', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
let requestWasMade = false
|
||||
|
||||
comfyPage.page.on('request', (request) => {
|
||||
if (request.url().includes('/api/models/checkpoints')) {
|
||||
requestWasMade = true
|
||||
}
|
||||
})
|
||||
|
||||
// Wait a reasonable time to ensure no request is made
|
||||
await comfyPage.page.waitForTimeout(512)
|
||||
expect(requestWasMade).toBe(false)
|
||||
})
|
||||
|
||||
test('fetches options immediately after widget is added to graph', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const requestPromise = comfyPage.page.waitForRequest((request) =>
|
||||
request.url().includes('/api/models/checkpoints')
|
||||
)
|
||||
await addRemoteWidgetNode(comfyPage, 'Remote Widget Node')
|
||||
const request = await requestPromise
|
||||
expect(request.url()).toContain('/api/models/checkpoints')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Refresh Behavior', () => {
|
||||
test('refreshes options when TTL expires', async ({ comfyPage }) => {
|
||||
// Fulfill each request with a unique timestamp
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route, request) => {
|
||||
await route.fulfill({
|
||||
body: JSON.stringify([Date.now()]),
|
||||
status: 200
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const nodeName = 'Remote Widget Node With 300ms Refresh'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
const initialOptions = await getWidgetOptions(comfyPage, nodeName)
|
||||
|
||||
// Wait for the refresh (TTL) to expire
|
||||
await comfyPage.page.waitForTimeout(302)
|
||||
await comfyPage.page.mouse.click(100, 100)
|
||||
|
||||
const refreshedOptions = await getWidgetOptions(comfyPage, nodeName)
|
||||
expect(refreshedOptions).not.toEqual(initialOptions)
|
||||
})
|
||||
|
||||
test('does not refresh when TTL is not set', async ({ comfyPage }) => {
|
||||
let requestCount = 0
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route) => {
|
||||
requestCount++
|
||||
await route.fulfill({ body: JSON.stringify(['test']), status: 200 })
|
||||
}
|
||||
)
|
||||
|
||||
const nodeName = 'Remote Widget Node'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
|
||||
// Force multiple re-renders
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await comfyPage.page.mouse.click(100, 100)
|
||||
await comfyPage.nextFrame()
|
||||
}
|
||||
|
||||
expect(requestCount).toBe(1) // Should only make initial request
|
||||
})
|
||||
|
||||
test('retries failed requests with backoff', async ({ comfyPage }) => {
|
||||
const timestamps: number[] = []
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route) => {
|
||||
timestamps.push(Date.now())
|
||||
await route.fulfill({ status: 500 })
|
||||
}
|
||||
)
|
||||
|
||||
const nodeName = 'Remote Widget Node'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName)
|
||||
|
||||
// Wait for a few retries
|
||||
await comfyPage.page.waitForTimeout(1024)
|
||||
|
||||
// Verify exponential backoff between retries
|
||||
const intervals = timestamps.slice(1).map((t, i) => t - timestamps[i])
|
||||
expect(intervals[1]).toBeGreaterThan(intervals[0])
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Cache Behavior', () => {
|
||||
test('reuses cached data between widgets with same params', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
let requestCount = 0
|
||||
await comfyPage.page.route(
|
||||
'**/api/models/checkpoints**',
|
||||
async (route) => {
|
||||
requestCount++
|
||||
await route.fulfill({
|
||||
body: JSON.stringify(mockOptions),
|
||||
status: 200
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Add two widgets with same config
|
||||
const nodeName = 'Remote Widget Node'
|
||||
await addRemoteWidgetNode(comfyPage, nodeName, 2)
|
||||
await waitForWidgetUpdate(comfyPage)
|
||||
|
||||
expect(requestCount).toBe(1) // Should reuse cached data
|
||||
})
|
||||
})
|
||||
})
|
||||