最新のComfyUIにおける実行エンジンのリスト自動アンラップ廃止に伴い、リスト型ノードの出力データが下流ノードで二重にラップされて正常に結合できなくなる不具合を解消するため、およびE2Eテスト環境の不整合を修正 - StringToolsRandomChoiceList、StringToolsBalancedChoiceList、StringToolsConcatList の process 戻り値から不要なリストブラケットを削除 - test_string_tools_complex_interconnection において seed と text_list の接続ポートが逆になっていた不具合を修正 - テスト内モックノード StringToolsMockStringList をテスト実行環境(COMFYUI_TEST_MODE または pytest 稼働中)のみ動的に登録するように制御 - モックノード誤露出時のフェイルセーフとして NODE_DISPLAY_NAME_MAPPINGS にテスト用である旨の表示名を設定 - conftest.py の起動オプションにホワイトリストを追加しテスト実行速度を高速化
176 lines
6.2 KiB
Python
176 lines
6.2 KiB
Python
import pytest
|
|
import time
|
|
import json
|
|
import urllib.request
|
|
from playwright.sync_api import Page, expect
|
|
|
|
def get_latest_history(base_url: str):
|
|
"""ComfyUIのAPIから最新の実行履歴を取得する"""
|
|
req = urllib.request.Request(f"{base_url}/history", method="GET")
|
|
try:
|
|
with urllib.request.urlopen(req) as response:
|
|
data = json.loads(response.read().decode('utf-8'))
|
|
if not data:
|
|
return None
|
|
latest_id = list(data.keys())[-1]
|
|
return data[latest_id]
|
|
except Exception as e:
|
|
print(f"Failed to fetch history: {e}")
|
|
return None
|
|
|
|
def run_prompt_and_wait(page: Page, comfyui_server: str, timeout=30):
|
|
"""
|
|
app.graphToPrompt() を取得し、requests で POST して実行完了を待機する。
|
|
"""
|
|
import requests
|
|
# graphToPrompt を取得
|
|
prompt_data = page.evaluate('''async () => {
|
|
const p = await app.graphToPrompt();
|
|
return { output: p.output, workflow: p.workflow };
|
|
}''')
|
|
|
|
# POST
|
|
resp = requests.post(
|
|
f"{comfyui_server}/prompt",
|
|
json={
|
|
"client_id": "test_client",
|
|
"prompt": prompt_data["output"],
|
|
"extra_data": {"extra_pnginfo": {"workflow": prompt_data["workflow"]}}
|
|
},
|
|
timeout=10
|
|
)
|
|
assert resp.status_code == 200, f"Prompt failed: {resp.text}"
|
|
|
|
prompt_id = resp.json().get("prompt_id")
|
|
assert prompt_id, "No prompt_id returned"
|
|
|
|
# 完了待機
|
|
start_time = time.time()
|
|
while time.time() - start_time < timeout:
|
|
try:
|
|
resp = requests.get(f"{comfyui_server}/history", timeout=5).json()
|
|
if prompt_id in resp:
|
|
return resp[prompt_id]
|
|
except: pass
|
|
time.sleep(0.5)
|
|
|
|
raise TimeoutError("Prompt execution timed out")
|
|
|
|
def test_concat_no_separator_success(page: Page, comfyui_server: str, wait_for_comfyui):
|
|
"""
|
|
StringToolsConcat ノードの separator が未接続の場合、エラーにならずに実行でき、空文字で結合されることを確認。
|
|
"""
|
|
page.goto(comfyui_server)
|
|
wait_for_comfyui(page)
|
|
|
|
page.evaluate('''() => {
|
|
app.graph.clear();
|
|
const strA = LiteGraph.createNode("StringToolsString");
|
|
app.graph.add(strA);
|
|
strA.widgets[0].value = "Hello";
|
|
|
|
const strB = LiteGraph.createNode("StringToolsString");
|
|
app.graph.add(strB);
|
|
strB.widgets[0].value = "World";
|
|
|
|
const concat = LiteGraph.createNode("StringToolsConcat");
|
|
app.graph.add(concat);
|
|
|
|
// separator(Slot 0) は接続しない
|
|
strA.connect(0, concat, 1);
|
|
if (concat.onConnectionsChange) {
|
|
concat.onConnectionsChange(1, 1, true, null, concat.inputs[1]);
|
|
}
|
|
strB.connect(0, concat, 2);
|
|
if (concat.onConnectionsChange) {
|
|
concat.onConnectionsChange(1, 2, true, null, concat.inputs[2]);
|
|
}
|
|
|
|
let preview = LiteGraph.createNode("PreviewAny");
|
|
app.graph.add(preview);
|
|
concat.connect(0, preview, 0);
|
|
|
|
window.concatNodeId = concat.id;
|
|
window.tgtNodeId = preview.id;
|
|
}''')
|
|
|
|
history = run_prompt_and_wait(page, comfyui_server)
|
|
assert history is not None
|
|
|
|
tgt_id = page.evaluate("window.tgtNodeId")
|
|
outputs = history.get("outputs", {})
|
|
assert str(tgt_id) in outputs
|
|
|
|
output = outputs[str(tgt_id)]
|
|
# PreviewAny の出力形式に合わせて text を取得
|
|
out_str = str(output.get("text", output[0] if isinstance(output, list) else output))
|
|
print(f"Concat No Separator Output: {out_str}")
|
|
|
|
# デフォルトは空文字なので HelloWorld になるはず
|
|
assert "HelloWorld" in out_str
|
|
|
|
def test_concat_list_no_separator_success(page: Page, comfyui_server: str, wait_for_comfyui):
|
|
"""
|
|
StringToolsConcatList ノードの separator が未接続の場合、エラーにならずに実行でき、改行で結合されることを確認。
|
|
"""
|
|
page.goto(comfyui_server)
|
|
wait_for_comfyui(page)
|
|
|
|
page.evaluate('''() => {
|
|
if (!LiteGraph.getNodeType("StringToolsMockStringList")) {
|
|
class MockStringList extends LiteGraph.LGraphNode {
|
|
constructor() {
|
|
super();
|
|
this.addInput("str1", "STRING");
|
|
this.addInput("str2", "STRING");
|
|
this.addOutput("list", "STRING");
|
|
this.comfyClass = "StringToolsMockStringList";
|
|
}
|
|
}
|
|
MockStringList.title = "MockStringList";
|
|
MockStringList.comfyClass = "StringToolsMockStringList";
|
|
LiteGraph.registerNodeType("StringToolsMockStringList", MockStringList);
|
|
}
|
|
app.graph.clear();
|
|
|
|
const s1 = LiteGraph.createNode("StringToolsString");
|
|
app.graph.add(s1);
|
|
s1.widgets[0].value = "ABC";
|
|
const s2 = LiteGraph.createNode("StringToolsString");
|
|
app.graph.add(s2);
|
|
s2.widgets[0].value = "DEF";
|
|
|
|
const mock = LiteGraph.createNode("StringToolsMockStringList");
|
|
app.graph.add(mock);
|
|
s1.connect(0, mock, 0);
|
|
s2.connect(0, mock, 1);
|
|
|
|
const concatList = LiteGraph.createNode("StringToolsConcatList");
|
|
app.graph.add(concatList);
|
|
|
|
mock.connect(0, concatList, 0); // text_list
|
|
// separator(Slot 1) は接続しない
|
|
|
|
const preview = LiteGraph.createNode("PreviewAny");
|
|
app.graph.add(preview);
|
|
concatList.connect(0, preview, 0);
|
|
|
|
window.tgtNodeId = preview.id;
|
|
}''')
|
|
|
|
history = run_prompt_and_wait(page, comfyui_server)
|
|
assert history is not None
|
|
|
|
tgt_id = page.evaluate("window.tgtNodeId")
|
|
outputs = history.get("outputs", {})
|
|
assert str(tgt_id) in outputs
|
|
|
|
output = outputs[str(tgt_id)]
|
|
out_str = str(output.get("text", output[0] if isinstance(output, list) else output))
|
|
print(f"ConcatList No Separator Output: {out_str}")
|
|
|
|
# デフォルトは改行なので ABC\nDEF になるはず
|
|
assert "ABC" in out_str and "DEF" in out_str
|
|
# ABCDEF ではなく改行を挟んでいるか
|
|
assert "ABC" in out_str and "DEF" in out_str and "ABCDEF" not in out_str
|