Files
ltdrdata-ComfyUI-Impact-Pack/tests/e2e_animatediff_tutorials.py
T

87 lines
4.3 KiB
Python

"""Check Impact widget restoration in the four original AnimateDiff tutorial PNGs.
Set IMPACT_TUTORIALS_DIR to ComfyUI-extension-tutorials. This checks native
PNG import and JSON export/reload in both restoration modes, without inference.
Missing external nodes are recorded; the Impact nodes under test must be real.
"""
import hashlib
import json
import os
from pathlib import Path
import tempfile
from PIL import Image
from e2e_ui import browser_session, options
from test_e2e_widget_catalog import WIDGETS, export_workflow, full_zoom, setting, show_node, widget_state
WORKFLOWS = ["cinemagraph", "detailer-for-animatediff", "MakeTileSEGS_upscale_AnimateDiff",
"MakeTileSEGS_upscale_AnimateDiff_ControlNet"]
TYPES = {"SEGSDetailerForAnimateDiff", "DetailerForEachPipeForAnimateDiff", "SEGSPaste",
"MaskToSEGS", "ImpactSimpleDetectorSEGS_for_AD", "ImpactMakeTileSEGS",
"ImpactControlNetApplySEGS", "PreviewDetailerHookProvider"}
def check_widgets(ui, expected):
full_zoom(ui)
for node in expected:
show_node(ui, node["id"])
actual = widget_state(ui, node["id"])
assert json.dumps(actual, sort_keys=True) == json.dumps(node, sort_keys=True), (actual, node)
def main():
args = options(__doc__)
tutorials = Path(os.environ.get("IMPACT_TUTORIALS_DIR",
Path(__file__).resolve().parents[4] / "ComfyUI-extension-tutorials"))
catalog = {node["type"]: node["widgets"] for node in json.loads(WIDGETS.read_text())["nodes"]}
artifacts = args.artifacts or Path(tempfile.mkdtemp(prefix="impact-animatediff-tutorials-"))
for name in WORKFLOWS:
path = tutorials / "ComfyUI-Impact-Pack/workflow" / f"{name}.png"
with Image.open(path) as image:
workflow = json.loads(image.info["workflow"])
expected = []
for node in workflow["nodes"]:
if node["type"] not in TYPES:
continue
# Names/order and defaults come from the unchanged trusted catalog;
# saved values come from the original PNG, never the current runtime.
widgets = [dict(widget) for widget in catalog[node["type"]]]
saved = node["widgets_values"]
assert len(saved) <= len(widgets)
for widget, value in zip(widgets, saved):
widget["value"] = value
expected.append(dict(id=str(node["id"]), type=node["type"], widgets=widgets))
assert any(node["type"] in {"SEGSDetailerForAnimateDiff", "DetailerForEachPipeForAnimateDiff"} for node in expected)
for restore_by_name in (False, True):
label = f"{name}-{'named' if restore_by_name else 'positional'}"
args.artifacts = artifacts / label
result_path = args.artifacts / "result.json"
result_path.unlink(missing_ok=True)
with browser_session(args) as ui:
ui.page.keyboard.press("Escape")
setting(ui, "Restore widget values", "Comfy.Workflow.NamedValuesRestore", restore_by_name)
ui.new_workflow()
ui.load_workflow(path, len(workflow["nodes"]), check_node_errors=False)
check_widgets(ui, expected)
missing = ui.app.evaluate("""app => app.graph._nodes.filter(node =>
!LiteGraph.registered_node_types[node.type]).map(node => node.type)""")
assert not TYPES.intersection(missing)
# Export through the native UI; no hand-built workflow JSON.
exported = args.artifacts / "exported.json"
export_workflow(ui, exported)
ui.new_workflow()
ui.load_workflow(exported, len(workflow["nodes"]), check_node_errors=False)
check_widgets(ui, expected)
report = dict(source=str(path), sha256=hashlib.sha256(path.read_bytes()).hexdigest(),
restore_by_name=restore_by_name, widgets=expected, missing_external_nodes=missing,
png_import=True, native_export_reload=True, inference=False)
# Session exit also checks browser errors and trace/close failures.
result_path.write_text(json.dumps(report, indent=2) + "\n")
print(f"PASS: {label}, {len(expected)} Impact nodes, original PNG and native JSON reload", flush=True)
if __name__ == "__main__":
main()