Files
Taremin-webui-monaco-prompt/tests/e2e/multitext/test_multitext_selection_mode.py
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

153 lines
7.1 KiB
Python

import pytest
import os
import json
import re
from playwright.sync_api import Page, expect
def test_multitext_selection_mode(page: Page, comfyui_server, wait_for_comfyui, wmp_helpers):
"""
Selection Modeのテスト
1. ツールバーのボタンクリックでチェックボックスが現れること
2. チェックボックスを切り替え、状態が変化すること
3. 保存してリロードした際にも状態が維持されること
"""
page.set_viewport_size({"width": 1280, "height": 720})
screenshot_dir = os.path.join(os.getcwd(), "tests", "screenshots_features")
os.makedirs(screenshot_dir, exist_ok=True)
page.on("console", lambda msg: print(f"BROWSER: {msg.text}"))
wmp_helpers.load_comfyui(page, comfyui_server, wait_for_comfyui)
wmp_helpers.wait_for_graph_clear(page)
# MultiTextノードを作成
wmp_helpers.create_node(page, "WebuiMonacoPromptMultiText", [100, 100])
wmp_helpers.wait_for_ui_stabilize(page, 1000)
# ツールバーとボタンの取得
selection_btn = page.locator("button[title='Toggle Selection Mode']").first
add_file_btn = page.locator("button[title='New File']").first
add_folder_btn = page.locator("button[title='New Folder']").first
# フォルダ1つ、ファイル2つを作成する (default.txtが既に存在している前提)
# フォルダ "SubFolder" を作成 (Playwright同期APIスレッドのデッドロックを防ぐため、ブラウザ側で prompt を直接モック化)
page.evaluate("window.prompt = () => 'SubFolder';")
add_folder_btn.click(force=True, timeout=5000)
wmp_helpers.wait_for_ui_stabilize(page, 500)
# SubFolder の ID を取得し、その中に "sub_file.txt" を作成する
page.evaluate("""() => {
const node = app.graph._nodes.find(n => n.type === 'WebuiMonacoPromptMultiText');
const tree = node.multitext_widget.data.tree;
const subFolder = tree.find(i => i.name === 'SubFolder');
if (subFolder) {
node.multitext_widget.addItemWithName('file', 'sub_file.txt', subFolder.id);
}
}""")
wmp_helpers.wait_for_ui_stabilize(page, 500)
# 1. Selection Mode を有効化し、チェックボックスが表示されることを確認
selection_btn.click()
wmp_helpers.wait_for_ui_stabilize(page, 500)
expect(selection_btn).to_have_class(re.compile(r"active"))
# wait_for_ui_stabilize でDOMの更新を待機
wmp_helpers.wait_for_ui_stabilize(page, 1000)
# 2. UI操作でのチェックボックスのトグル
default_txt_item = page.locator("div").filter(has_text=re.compile(r"^default.txt$")).first
default_checkbox = default_txt_item.locator("xpath=..").locator("input[type='checkbox']").first
default_checkbox.click(force=True)
expect(default_checkbox).not_to_be_checked()
# SubFolder のチェックボックスと sub_file.txt のチェックボックスを取得
sub_folder_item = page.locator("div").filter(has_text=re.compile(r"^SubFolder$")).first
sub_folder_checkbox = sub_folder_item.locator("xpath=..").locator("input[type='checkbox']").first
sub_file_item = page.locator("div").filter(has_text=re.compile(r"^sub_file.txt$")).first
sub_file_checkbox = sub_file_item.locator("xpath=..").locator("input[type='checkbox']").first
# (A) 親(SubFolder)をOFFにすると、子(sub_file.txt)もOFFになることの確認
sub_folder_checkbox.click(force=True)
expect(sub_folder_checkbox).not_to_be_checked()
expect(sub_file_checkbox).not_to_be_checked()
# (B) 子をONにすると、親もON(出力ルート確保のため)になることの確認
sub_file_checkbox.click(force=True)
expect(sub_file_checkbox).to_be_checked()
expect(sub_folder_checkbox).to_be_checked() # 子が全てONなので親も完全なONになる
# (C) 再度子をOFFにすると、子要素がすべてOFFになるため親もOFFになることの確認
sub_file_checkbox.click(force=True)
expect(sub_file_checkbox).not_to_be_checked()
expect(sub_folder_checkbox).not_to_be_checked()
# データへの反映を確実にする
page.evaluate("""
() => {
const node = app.graph._nodes.find(n => n.type === 'WebuiMonacoPromptMultiText');
node.multitext_widget.commitData();
}
""")
# 4. 保存とリロードによる状態維持 (Persistence)
page.evaluate("""() => {
if (window.comfyAPI && window.comfyAPI.api && window.comfyAPI.api.api) {
window.comfyAPI.api.api.dispatchEvent(new CustomEvent('graphChanged'));
} else if (window.api) {
window.api.dispatchEvent(new CustomEvent('graphChanged'));
} else {
app.graph.change();
}
}""")
wmp_helpers.wait_for_ui_stabilize(page, 2000)
# リロードしてグラフを復元
# localstorageに保存されていることを明示的に保証するため少し待機
page.wait_for_timeout(1000)
page.reload()
wait_for_comfyui(page)
# グラフがロードされるまで待機
page.evaluate("""() => {
return new Promise(resolve => {
if (app.graph && app.graph._nodes && app.graph._nodes.length > 0) {
resolve();
} else {
const interval = setInterval(() => {
if (app.graph && app.graph._nodes && app.graph._nodes.length > 0) {
clearInterval(interval);
resolve();
}
}, 100);
}
});
}""")
wmp_helpers.wait_for_ui_stabilize(page, 3000) # DOMが構築されるのを待機
# 復元後、Selection Mode が有効で、内部データにチェックボックスの状態が維持されていることを確認
# (DOMロケーターの不安定さを回避するためevaluateでデータを直接検証)
is_persisted = page.evaluate("""() => {
const node = app.graph._nodes.find(n => n.type === 'WebuiMonacoPromptMultiText');
if (!node || !node.multitext_widget || !node.multitext_widget.data) return false;
const data = node.multitext_widget.data;
if (!data.selectionMode) return false;
const defaultTxt = data.tree.find(i => i.name === 'default.txt');
const subFolder = data.tree.find(i => i.name === 'SubFolder');
const subFile = subFolder && subFolder.children ? subFolder.children.find(i => i.name === 'sub_file.txt') : null;
// 全て output が false もしくは "false" となっていることを確認
const isDefaultTxtUnchecked = defaultTxt && (defaultTxt.output === false || defaultTxt.output === "false");
const isSubFolderUnchecked = subFolder && (subFolder.output === false || subFolder.output === "false");
const isSubFileUnchecked = subFile && (subFile.output === false || subFile.output === "false");
return isDefaultTxtUnchecked && isSubFolderUnchecked && isSubFileUnchecked;
}""")
assert is_persisted, "Selection Mode and uncheck states should be persisted after reload"
print("E2E Test: Selection Mode persistence and logic verified.")