Files
Taremin 26e910b4f1 chore(test): ComfyUIの複数バージョン自動検証基盤の導入とテスト安定化
テスト時に指定された複数のComfyUIバージョンを自動ダウンロードし、最適な仮想環境を構築した上でテストを実行する仕組みを導入。また、Playwright同期APIに起因するデッドロックやキャッシュ汚染によるテストのハングアップ問題を解消。

- 設定ファイルで複数バージョンの定義をサポート
- 該当するComfyUIおよびEmbeddable Pythonを自動取得するdownloaderモジュールを追加
- pytest_generate_testsによりバージョン別のテストを動的にパラメータ化
- min_comfy_versionマーカーによるバージョン制限テストの条件付きスキップをサポート
- Windows環境でのゾンビプロセス防止のためtaskkillによる強制終了処理を追加
- バージョン切り替え時のブラウザキャッシュ汚染を防ぐためキャッシュを無効化
- Playwright同期APIのダイアログ処理をJavaScript側でのモック化へ移行しデッドロックを回避
- 設定画面でのCSVトグル描画有無によるテストアプローチの動的切り替えを実装
- リサイズ操作のsteps指定など各種タイミング競合とタイムアウト処理を強化
2026-07-13 21:13:11 +09:00

216 lines
10 KiB
Python

import pytest
from playwright.sync_api import Page, expect
import os
from pathlib import Path
import uuid
def create_static_test_csv(unique_id: str):
"""Create a static test CSV file to ensure stable test environment."""
extension_path = os.environ.get("COMFYUI_EXTENSION_PATH", os.getcwd())
csv_dir = Path(extension_path) / "csv"
csv_dir.mkdir(exist_ok=True)
basename = f"test_auto_{unique_id}"
test_csv_path = csv_dir / f"{basename}.csv"
content = f"zzz_unique_apple_{unique_id},100\nzzz_unique_banana_{unique_id},50\n"
with open(test_csv_path, "w", encoding="utf-8") as f:
f.write(content)
return test_csv_path, basename
def test_multitext_csv_autocomplete_toggle(page: Page, comfyui_server, wait_for_comfyui, wmp_helpers):
page.set_viewport_size({"width": 1920, "height": 1080})
# 1. 準備:テスト用CSVの作成
unique_id = str(uuid.uuid4())[:8]
test_csv_path, basename = create_static_test_csv(unique_id)
target_tag = f"zzz_unique_apple_{unique_id}"
context_key = f"csv.{basename}"
try:
wmp_helpers.load_comfyui(page, comfyui_server, wait_for_comfyui)
wmp_helpers.wait_for_graph_clear(page)
# ノードを作成してキャンバスに配置
page.evaluate(f"""() => {{
const findApp = () => window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const app = findApp();
const types = Object.keys(window.LiteGraph.registered_node_types);
const nodeType = types.find(t => t.includes('WebuiMonacoPromptMultiText') || t.includes('MultiText'));
const node1 = window.LiteGraph.createNode(nodeType);
node1.pos = [100, 300];
app.graph.add(node1);
app.canvas.centerOnNode(node1);
}}""")
wmp_helpers.wait_for_editor(page)
# エディタのShadow DOM内にフォーカス
editor_locator = page.locator("prompt-editor").first
editor_locator.scroll_into_view_if_needed()
editor_locator.click()
# CSVを確実に読み込ませるためにRefreshを呼ぶ
refresh_btn = page.locator("button:has-text('Refresh')")
if refresh_btn.is_visible():
refresh_btn.click()
else:
page.evaluate("""() => {
const findApp = () => window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const app = findApp();
if (app.refreshComboInNodes) {
app.refreshComboInNodes();
} else if (app.extensionManager && app.extensionManager.refreshComboInNodes) {
app.extensionManager.refreshComboInNodes();
}
}""")
# 確実にロードされるのを待機
page.wait_for_function(f"""() => {{
const findApp = () => window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const app = findApp();
const nodes = app.graph._nodes.filter(n => n.type && n.type.includes('MultiText'));
if (nodes.length === 0) return false;
const editor = nodes[0].multitext_widget.editorInstance;
if (!editor) return false;
const val = editor.getContext(editor.createContextKey("{context_key}"));
return val !== undefined && val !== null;
}}""")
# 2. デフォルト状態(CSVオン)でのサジェスト確認
page.evaluate("() => { const pe = document.querySelector('prompt-editor'); if (pe) pe.focus(); }")
page.keyboard.type("zzz_unique_")
# API経由でサジェスト起動
page.evaluate("() => { const pe = document.querySelector('prompt-editor'); if (pe) pe.triggerSuggest(); }")
# API経由で表示完了を待機
page.wait_for_function("() => { const pe = document.querySelector('prompt-editor'); return pe && pe.isSuggestVisible(); }", timeout=10000)
# 候補が含まれているか確認
has_tag = page.evaluate(f"() => {{ const pe = document.querySelector('prompt-editor'); return pe ? pe.getSuggestList().some(s => s.includes('{target_tag}')) : false; }}")
assert has_tag, f"Suggest tag '{target_tag}' should be visible."
# 入力をクリア
page.keyboard.press("Escape") # サジェストを閉じる
page.keyboard.press("Control+a")
page.keyboard.press("Backspace")
# 3. CSVをオフにする (API直接操作と伝播)
page.evaluate(f"""() => {{
const app = window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const currentToggle = app.ui.settings.getSettingValue("WebuiMonacoPrompt.CsvToggle", {{}});
currentToggle["{context_key}"] = false;
app.ui.settings.setSettingValue("WebuiMonacoPrompt.CsvToggle", currentToggle);
if (window.WebuiMonacoPrompt && window.WebuiMonacoPrompt.runAllInstances) {{
window.WebuiMonacoPrompt.runAllInstances((instance) => {{
instance.setSettings({{csvToggle: currentToggle}}, true);
}});
}}
}}""")
# エディタ側で設定が反映されたか確認
page.wait_for_function(f"""() => {{
const app = window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const nodes = app.graph._nodes.filter(n => n.type && n.type.includes('MultiText'));
const editor = nodes[0].multitext_widget.editorInstance;
return editor.getContext(editor.createContextKey("{context_key}")) === false;
}}""", timeout=10000)
# 4. オフ状態でのサジェスト非表示の確認
page.evaluate("() => { const pe = document.querySelector('prompt-editor'); if (pe) pe.focus(); }")
page.keyboard.type("zzz_unique_")
page.evaluate("() => { const pe = document.querySelector('prompt-editor'); if (pe) pe.triggerSuggest(); }")
# 2秒待って候補が現れないことを確認
page.wait_for_timeout(2000)
has_tag = page.evaluate(f"() => {{ const pe = document.querySelector('prompt-editor'); return pe ? pe.getSuggestList().some(s => s.includes('{target_tag}')) : false; }}")
assert not has_tag, f"Suggest tag '{target_tag}' should not be visible."
# 入力をクリア
page.keyboard.press("Escape")
page.keyboard.press("Control+a")
page.keyboard.press("Backspace")
# 5. 再度CSVをオンにする (API直接操作と伝播)
page.evaluate(f"""() => {{
const app = window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const currentToggle = app.ui.settings.getSettingValue("WebuiMonacoPrompt.CsvToggle", {{}});
currentToggle["{context_key}"] = true;
app.ui.settings.setSettingValue("WebuiMonacoPrompt.CsvToggle", currentToggle);
if (window.WebuiMonacoPrompt && window.WebuiMonacoPrompt.runAllInstances) {{
window.WebuiMonacoPrompt.runAllInstances((instance) => {{
instance.setSettings({{csvToggle: currentToggle}}, true);
}});
}}
}}""")
page.wait_for_timeout(500)
# ページをリロードして、設定が永続化され、起動時に正しくタグが読み込まれることを確認する
wmp_helpers.load_comfyui(page, comfyui_server, wait_for_comfyui)
wmp_helpers.wait_for_graph_clear(page)
# 再度ノードを作成してキャンバスに配置
page.evaluate(f"""() => {{
const findApp = () => window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const app = findApp();
const types = Object.keys(window.LiteGraph.registered_node_types);
const nodeType = types.find(t => t.includes('WebuiMonacoPromptMultiText') || t.includes('MultiText'));
const node1 = window.LiteGraph.createNode(nodeType);
node1.pos = [100, 300];
app.graph.add(node1);
app.canvas.centerOnNode(node1);
}}""")
wmp_helpers.wait_for_editor(page)
# エディタのShadow DOM内にフォーカス
editor_locator = page.locator("prompt-editor").first
editor_locator.scroll_into_view_if_needed()
editor_locator.click()
# エディタ側で設定が反映されたか確認 (オンになっているはず)
page.wait_for_function(f"""() => {{
const app = window.app || (window.comfyAPI && window.comfyAPI.app) || window.ComfyApp;
const nodes = app.graph._nodes.filter(n => n.type && n.type.includes('MultiText'));
if (nodes.length === 0) return false;
const editor = nodes[0].multitext_widget.editorInstance;
if (!editor) return false;
return editor.getContext(editor.createContextKey("{context_key}")) === true;
}}""", timeout=10000)
# UIの反応を少し待つ
page.wait_for_timeout(1000)
# 6. オン状態でのサジェスト表示の再確認
page.evaluate("() => { const pe = document.querySelector('prompt-editor'); if (pe) pe.focus(); }")
page.keyboard.press("Control+a")
page.keyboard.press("Backspace")
page.wait_for_timeout(500)
page.keyboard.type("zzz_unique_")
# API経由でサジェスト起動
page.evaluate("() => { const pe = document.querySelector('prompt-editor'); if (pe) pe.triggerSuggest(); }")
# API経由で表示完了を待機
page.wait_for_function("() => { const pe = document.querySelector('prompt-editor'); return pe && pe.isSuggestVisible(); }", timeout=10000)
has_tag = page.evaluate(f"() => {{ const pe = document.querySelector('prompt-editor'); return pe ? pe.getSuggestList().some(s => s.includes('{target_tag}')) : false; }}")
assert has_tag, f"Suggest tag '{target_tag}' should be visible."
finally:
if test_csv_path.exists():
try:
test_csv_path.unlink()
except Exception:
pass