Compare commits

...

5 Commits

Author SHA1 Message Date
bymyself
7766c46bf7 test: extract preview text assertion into named helper
Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/11415#discussion_r3107682014
2026-05-03 01:15:29 -07:00
Glary-Bot
42385269b8 fix: remove flawed unit test, fix E2E test per AustinMroz review
- Remove executionUtil.test.ts: the serializeValue hypothesis was wrong.
  DynamicCombo does NOT use serializeValue — the combo serializes as
  just its value and sub-widgets serialize independently.
- Rewrite E2E test to use DOM textbox locator instead of stale
  getWidget() refs that fail inside subgraphs.
- The page snapshot shows the preview text IS present — the original
  test was just incorrectly resolving the node reference.
2026-04-20 18:50:20 +00:00
Glary-Bot
addad967cb fix: address review feedback — node sizes, stale refs, runButton
- Increase node sizes to 400x200 minimum per AGENTS.md guidelines
- Re-query PreviewAny inside expect.poll to avoid stale node refs
- Use comfyPage.runButton.click() instead of executeCommand
2026-04-19 23:50:47 +00:00
Glary-Bot
7cdab5a212 fix: use runButton.click() instead of executeCommand for user action
Address review feedback from @christian-byrne: use user-action
pattern (clicking the run button) instead of invoking commands
directly.
2026-04-19 23:33:48 +00:00
Glary-Bot
e9ac55af59 test: add failing tests for DynamicCombo text preview inside subgraphs
Adds regression tests demonstrating that V3 DynamicCombo widget
serialization breaks when nodes are packed into subgraphs. The
serializeValue callback is lost during subgraph conversion, causing
sampling_mode to serialize as the raw string 'on' instead of the
expected {sampling_mode: 'on', temperature: 0.7, top_k: 64} object.

This reproduces the bug where TextGenerateLTX2Prompt -> PreviewAny
text preview fails inside subgraphs.

Tests:
- Unit: graphToPrompt DynamicCombo serialization at top level (pass)
  and inside subgraph (intentional fail)
- E2E: DynamicCombo text preview at top level and inside subgraph
- Devtools: Mock V3 DynamicCombo node for CI testing (no model needed)
2026-04-19 09:17:13 +00:00
4 changed files with 165 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
{
"id": "ef2b657e-8af3-42dd-9674-dde8a95adf22",
"revision": 0,
"last_node_id": 2,
"last_link_id": 1,
"nodes": [
{
"id": 1,
"type": "DynamicComboStringOutput",
"pos": [576, 441],
"size": [400, 200],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "output_text",
"type": "STRING",
"links": [1]
}
],
"properties": {
"Node name for S&R": "DynamicComboStringOutput"
},
"widgets_values": ["test input", "on", 0.7, 64]
},
{
"id": 2,
"type": "PreviewAny",
"pos": [972, 458],
"size": [400, 200],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "source",
"type": "*",
"link": 1
}
],
"outputs": [],
"properties": {
"Node name for S&R": "PreviewAny"
},
"widgets_values": []
}
],
"links": [[1, 1, 0, 2, 0, "*"]],
"groups": [],
"config": {},
"extra": {
"frontendVersion": "1.19.1",
"ds": {
"offset": [0, 0],
"scale": 1
}
},
"version": 0.4
}

View File

@@ -0,0 +1,48 @@
import { expect } from '@playwright/test'
import type { Page } from '@playwright/test'
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
const PREVIEW_TEXT_TIMEOUT_MS = 15_000
const expectPreviewTextContains = async (page: Page, expected: string) => {
const previewTextbox = page.getByRole('textbox', { name: 'preview_text' })
await expect
.poll(async () => previewTextbox.inputValue(), {
timeout: PREVIEW_TEXT_TIMEOUT_MS
})
.toContain(expected)
}
test.describe(
'DynamicCombo text preview',
{ tag: ['@workflow', '@subgraph'] },
() => {
test('shows text preview when DynamicCombo node is at top level', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow('execution/dynamic_combo_preview')
await comfyPage.runButton.click()
await expectPreviewTextContains(comfyPage.page, 'DynamicCombo output')
})
test('shows text preview when DynamicCombo node is inside subgraph', async ({
comfyPage
}) => {
await comfyPage.workflow.loadWorkflow('execution/dynamic_combo_preview')
const sourceNode = await comfyPage.nodeOps.getNodeRefById('1')
await comfyPage.canvas.click()
await comfyPage.page.keyboard.press('Control+a')
const subgraphNode = await sourceNode.convertToSubgraph()
await comfyPage.runButton.click()
await subgraphNode.navigateIntoSubgraph()
await expectPreviewTextContains(comfyPage.page, 'DynamicCombo output')
})
}
)

View File

@@ -8,6 +8,11 @@ from .errors import (
NODE_CLASS_MAPPINGS as errors_class_mappings,
NODE_DISPLAY_NAME_MAPPINGS as errors_display_name_mappings,
)
from .dynamic_combo import (
DynamicComboStringOutput,
NODE_CLASS_MAPPINGS as dynamic_combo_class_mappings,
NODE_DISPLAY_NAME_MAPPINGS as dynamic_combo_display_name_mappings,
)
from .inputs import (
LongComboDropdown,
NodeWithBooleanInput,
@@ -47,6 +52,7 @@ from .remote import (
NODE_CLASS_MAPPINGS = {
**errors_class_mappings,
**dynamic_combo_class_mappings,
**inputs_class_mappings,
**remote_class_mappings,
**models_class_mappings,
@@ -54,6 +60,7 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = {
**errors_display_name_mappings,
**dynamic_combo_display_name_mappings,
**inputs_display_name_mappings,
**remote_display_name_mappings,
**models_display_name_mappings,
@@ -61,6 +68,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
__all__ = [
"DeprecatedNode",
"DynamicComboStringOutput",
"DummyPatch",
"ErrorRaiseNode",
"ErrorRaiseNodeWithMessage",

View File

@@ -0,0 +1,48 @@
from __future__ import annotations
from comfy_api.latest import io # pyright: ignore[reportMissingImports]
class DynamicComboStringOutput(io.ComfyNode):
@classmethod
def define_schema(cls):
sampling_options = [
io.DynamicCombo.Option(
key="on",
inputs=[
io.Float.Input("temperature", default=0.7, min=0.01, max=2.0),
io.Int.Input("top_k", default=64, min=0, max=1000),
],
),
io.DynamicCombo.Option(key="off", inputs=[]),
]
return io.Schema(
node_id="DynamicComboStringOutput",
category="DevTools",
inputs=[
io.String.Input("prompt", multiline=True, default="test input"),
io.DynamicCombo.Input("sampling_mode", options=sampling_options),
],
outputs=[io.String.Output(display_name="output_text")],
)
@classmethod
def execute(cls, prompt, sampling_mode) -> io.NodeOutput:
mode = sampling_mode.get("sampling_mode", "unknown")
return io.NodeOutput(f"DynamicCombo output ({mode}): {prompt}")
NODE_CLASS_MAPPINGS = {
"DynamicComboStringOutput": DynamicComboStringOutput,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"DynamicComboStringOutput": "Dynamic Combo String Output",
}
__all__ = [
"DynamicComboStringOutput",
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
]