Fix lazy Switch inputs, Inversed Switch fan-out, and valid ANY connections. Use target input slots for output connection guards, preserve mask batches and one-pixel dimensions during resizing, and restore label-filter defaults. Keep existing widget names and order and remove noisy selection logging. Add Chrome regression coverage for routing, execution results, and tutorial workflows. Add a UI-created catalog of 197 registered nodes and 712 ordered widget values to detect native workflow loading regressions. Require Full Cache mode and trusted-baseline checks before catalog additions. Verify candidate workflows and browser cleanup before publishing fixtures. Document the widget compatibility, test setup, and catalog-extension rules.
95 lines
4.6 KiB
Python
95 lines
4.6 KiB
Python
"""Run the original Switch tutorial PNGs through Chrome and compare their routes.
|
|
|
|
Set IMPACT_TUTORIALS_DIR to the ComfyUI-extension-tutorials checkout.
|
|
Requires its referenced input images and ControlNet Auxiliary's OpenPose node.
|
|
"""
|
|
import copy
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from urllib.parse import urlsplit
|
|
|
|
from PIL import Image
|
|
|
|
from e2e_ui import browser_session, options
|
|
|
|
|
|
def output_pixels(ui, entry):
|
|
images = {}
|
|
for node_id, output in ui.images(entry).items():
|
|
assert len(output) == 1
|
|
images[node_id] = (output[0].size, output[0].tobytes())
|
|
return images
|
|
|
|
|
|
def main():
|
|
args = options(__doc__)
|
|
tutorials = Path(os.environ.get("IMPACT_TUTORIALS_DIR",
|
|
Path(__file__).resolve().parents[4] / "ComfyUI-extension-tutorials"))
|
|
directory = tutorials / "ComfyUI-Impact-Pack" / "tutorial"
|
|
artifacts = args.artifacts
|
|
for name in ("switch-simple", "inversed-switch-simple", "select_on_execution", "select_on_prompt"):
|
|
args.artifacts = artifacts / name if artifacts else None
|
|
path = directory / f"{name}.png"
|
|
with Image.open(path) as image:
|
|
workflow = json.loads(image.info["workflow"])
|
|
with browser_session(args) as ui:
|
|
ui.page.keyboard.press("Escape")
|
|
ui.new_workflow()
|
|
ui.load_workflow(path, len(workflow["nodes"]))
|
|
with ui.page.expect_request(lambda request: urlsplit(request.url).path in ("/prompt", "/api/prompt")
|
|
and request.method == "POST") as submitted:
|
|
initial = output_pixels(ui, ui.run())
|
|
prompt = submitted.value.post_data_json["prompt"]
|
|
|
|
if name == "switch-simple":
|
|
sources = ["1", "10", "11"]
|
|
reference = {key: copy.deepcopy(prompt[key]) for key in sources}
|
|
selector, widget = "ImpactSwitch", "select"
|
|
expected_nodes = {value: {"15"} for value in (1, 2, 3)}
|
|
elif name == "inversed-switch-simple":
|
|
sources = ["3", "5", "8"]
|
|
reference = {key: copy.deepcopy(prompt[key]) for key in ["1"] + sources}
|
|
for key in sources:
|
|
reference[key]["inputs"]["image"] = ["1", 0]
|
|
selector, widget = "ImpactInversedSwitch", "select"
|
|
expected_nodes = {1: {"6"}, 2: {"7"}, 3: {"9"}}
|
|
else:
|
|
sources = ["13", "15"]
|
|
reference = {key: copy.deepcopy(prompt[key]) for key in ["10"] + sources}
|
|
for key in sources:
|
|
reference[key]["inputs"]["image"] = ["10", 0]
|
|
selector, widget = "PrimitiveNode", "value"
|
|
expected_nodes = ({1: {"33"}, 2: {"33"}} if name == "select_on_execution"
|
|
else {1: {"25", "33"}, 2: {"26", "33"}})
|
|
for index, node_id in enumerate(sources, 1):
|
|
reference[f"expected{index}"] = {"class_type": "PreviewImage", "inputs": {"images": [node_id, 0]}}
|
|
|
|
ui.new_workflow()
|
|
ui.load_workflow({"name": "switch-reference.json", "mimeType": "application/json",
|
|
"buffer": json.dumps(reference).encode()}, len(reference))
|
|
expected = output_pixels(ui, ui.run())
|
|
assert len(set(expected.values())) == len(sources), "Reference images must differ"
|
|
assert initial.keys() == expected_nodes[2], f"{name}: initial output nodes differ"
|
|
assert all(pixels == expected["expected2"] for pixels in initial.values())
|
|
|
|
ui.new_workflow()
|
|
ui.load_workflow(path, len(workflow["nodes"]))
|
|
for value in [1, 2] + ([3] if len(sources) == 3 else []) + [2, None]:
|
|
if value is None:
|
|
saved = ui.app.evaluate("app => app.graph.serialize()")
|
|
ui.new_workflow()
|
|
ui.load_workflow({"name": f"{name}-saved.json", "mimeType": "application/json",
|
|
"buffer": json.dumps(saved).encode()}, len(workflow["nodes"]))
|
|
value = 2
|
|
else:
|
|
ui.set_number(selector, widget, value)
|
|
actual = output_pixels(ui, ui.run())
|
|
assert actual.keys() == expected_nodes[value], f"{name}: select={value} output nodes differ"
|
|
assert all(pixels == expected[f"expected{value}"] for pixels in actual.values()), f"{name}: output pixels differ"
|
|
print(f"PASS: {name}, original PNG, all routes, core-node pixel parity, saved JSON import")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|