Skip to content

Commit ea090ed

Browse files
committed
feat(ui): move Trust & Health + Anonymous Telemetry from Settings to Statistics and stabilize Playwright E2E routes
1 parent e8deeda commit ea090ed

17 files changed

+825
-343
lines changed

i18n.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
"ai_provider_label": "AI Provider",
181181
"base_url_label": "Base URL",
182182
"api_key_label": "API Key",
183+
"api_key_tip": "Tip: Prefer using Google AI Studio's free API quota for AI debugging. For more info, see:",
183184
"model_name_label": "Model Name",
184185
"loading_models": "Loading models...",
185186
"set_base_url_first": "Please set Base URL first",
@@ -335,6 +336,7 @@
335336
"ai_provider_label": "AI 供應商",
336337
"base_url_label": "基礎 URL",
337338
"api_key_label": "API 金鑰",
339+
"api_key_tip": "小提示:建議優先使用 Google AI Studio 的免費 API 額度來進行 AI 除錯。更多說明請至:",
338340
"model_name_label": "模型名稱",
339341
"loading_models": "正在載入模型...",
340342
"set_base_url_first": "請先設定基礎 URL",
@@ -489,6 +491,7 @@
489491
"ai_provider_label": "AI 供应商",
490492
"base_url_label": "基础 URL",
491493
"api_key_label": "API 密钥",
494+
"api_key_tip": "小提示:建议优先使用 Google AI Studio 的免费 API 额度进行 AI 调试。更多说明请访问:",
492495
"model_name_label": "模型名称",
493496
"loading_models": "正在加载模型...",
494497
"set_base_url_first": "请先设置基础 URL",
@@ -643,6 +646,7 @@
643646
"ai_provider_label": "AIプロバイダー",
644647
"base_url_label": "ベースURL",
645648
"api_key_label": "APIキー",
649+
"api_key_tip": "ヒント:AI デバッグには、まず Google AI Studio の無料 API 枠の利用をおすすめします。詳しくは:",
646650
"model_name_label": "モデル名",
647651
"loading_models": "モデルを読み込み中...",
648652
"set_base_url_first": "最初にベースURLを設定してください",
@@ -795,6 +799,7 @@
795799
"ai_provider_label": "KI-Anbieter",
796800
"base_url_label": "Basis-URL",
797801
"api_key_label": "API-Schlüssel",
802+
"api_key_tip": "Tipp: Nutze für AI-Debugging bevorzugt das kostenlose API-Kontingent von Google AI Studio. Mehr Infos:",
798803
"model_name_label": "Modellname",
799804
"loading_models": "Modelle werden geladen...",
800805
"set_base_url_first": "Bitte zuerst Basis-URL festlegen",
@@ -949,6 +954,7 @@
949954
"ai_provider_label": "Fournisseur d'IA",
950955
"base_url_label": "URL de base",
951956
"api_key_label": "Clé API",
957+
"api_key_tip": "Astuce : pour le débogage IA, privilégiez d’abord le quota gratuit de Google AI Studio. Plus d’infos :",
952958
"model_name_label": "Nom du modèle",
953959
"loading_models": "Chargement des modèles...",
954960
"set_base_url_first": "Veuillez d'abord définir l'URL de base",
@@ -1103,6 +1109,7 @@
11031109
"ai_provider_label": "Provider IA",
11041110
"base_url_label": "URL Base",
11051111
"api_key_label": "Chiave API",
1112+
"api_key_tip": "Suggerimento: per il debug con AI, usa prima la quota gratuita delle API di Google AI Studio. Maggiori info:",
11061113
"model_name_label": "Nome del modello",
11071114
"loading_models": "Caricamento modelli...",
11081115
"set_base_url_first": "Imposta prima l'URL Base",
@@ -1257,6 +1264,7 @@
12571264
"ai_provider_label": "Proveedor de IA",
12581265
"base_url_label": "URL Base",
12591266
"api_key_label": "Clave API",
1267+
"api_key_tip": "Consejo: para depurar con IA, prioriza el cupo gratuito de API de Google AI Studio. Más información:",
12601268
"model_name_label": "Nombre del modelo",
12611269
"loading_models": "Cargando modelos...",
12621270
"set_base_url_first": "Por favor, establezca primero la URL Base",
@@ -1411,6 +1419,7 @@
14111419
"ai_provider_label": "AI 공급자",
14121420
"base_url_label": "기본 URL",
14131421
"api_key_label": "API 키",
1422+
"api_key_tip": "팁: AI 디버깅에는 Google AI Studio의 무료 API 할당량을 우선 사용해 보세요. 자세히 보기:",
14141423
"model_name_label": "모델 이름",
14151424
"loading_models": "모델 로딩 중...",
14161425
"set_base_url_first": "먼저 기본 URL을 설정하세요",

playwright.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
// @ts-check
22
const { defineConfig, devices } = require('@playwright/test');
3+
const path = require('node:path');
34

45
const webServerPort = Number(process.env.PW_WEB_SERVER_PORT) || 3000;
56
const baseURL = process.env.PW_BASE_URL || `http://127.0.0.1:${webServerPort}/tests/e2e/`;
67
const pythonCmd = process.platform === 'win32' ? 'python' : (process.env.PW_PYTHON || 'python3');
8+
const testOutputDir = process.env.PW_TEST_OUTPUT_DIR || 'test-results';
9+
const htmlReportDir = process.env.PW_HTML_REPORT_DIR || 'playwright-report';
710

811
/**
912
* Playwright Configuration for ComfyUI-Doctor
@@ -17,6 +20,9 @@ module.exports = defineConfig({
1720
// Test directory
1821
testDir: './tests/e2e/specs',
1922

23+
// Test artifacts output
24+
outputDir: path.resolve(testOutputDir),
25+
2026
// Maximum time one test can run
2127
timeout: 30 * 1000,
2228

@@ -39,7 +45,7 @@ module.exports = defineConfig({
3945

4046
// Reporter to use
4147
reporter: [
42-
['html'],
48+
['html', { outputFolder: path.resolve(htmlReportDir), open: 'never' }],
4349
['list'],
4450
...(process.env.CI ? [['github']] : []),
4551
],

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "comfyui-doctor"
33
description = "A real-time runtime diagnostics suite for ComfyUI, featuring interactive debugging chat, and 50+ fix patterns. Automatically intercepts terminal output from startup, and delivers prioritized fix suggestions with node-level context extraction. Now supports JSON-based pattern management with hot-reload and full i18n support for 9 languages."
4-
version = "1.5.2"
4+
version = "1.5.3"
55
license = {text = "MIT"}
66
readme = "README.md"
77
requires-python = ">=3.10"

scripts/run-playwright.mjs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,52 @@
11
import { spawnSync } from 'node:child_process';
22
import fs from 'node:fs';
3+
import os from 'node:os';
34
import path from 'node:path';
45
import process from 'node:process';
56

67
function ensureWritableTempDir() {
78
if (process.platform === 'win32') return;
89
if (process.env.TMPDIR && process.env.TMP && process.env.TEMP) return;
910

10-
const repoRoot = process.cwd();
11-
const tmpDir = path.join(repoRoot, '.tmp', 'playwright');
11+
const tmpDir = (isWsl() && isDrvFsPath(process.cwd()))
12+
? path.join('/tmp', 'comfyui-doctor', 'playwright-tmp')
13+
: path.join(process.cwd(), '.tmp', 'playwright');
1214
fs.mkdirSync(tmpDir, { recursive: true });
1315

1416
process.env.TMPDIR = process.env.TMPDIR || tmpDir;
1517
process.env.TMP = process.env.TMP || tmpDir;
1618
process.env.TEMP = process.env.TEMP || tmpDir;
1719
}
1820

21+
function isWsl() {
22+
return !!process.env.WSL_DISTRO_NAME || fs.existsSync('/proc/sys/fs/binfmt_misc/WSLInterop');
23+
}
24+
25+
function isDrvFsPath(p) {
26+
return p.startsWith('/mnt/');
27+
}
28+
1929
ensureWritableTempDir();
2030

31+
function ensureWritablePlaywrightOutputDirs() {
32+
if (process.platform === 'win32') return;
33+
if (!isWsl()) return;
34+
if (!isDrvFsPath(process.cwd())) return;
35+
36+
if (process.env.PW_TEST_OUTPUT_DIR && process.env.PW_HTML_REPORT_DIR) return;
37+
38+
const baseDir = path.join(os.tmpdir(), 'comfyui-doctor', 'playwright');
39+
const testOutputDir = path.join(baseDir, 'test-results');
40+
const htmlReportDir = path.join(baseDir, 'playwright-report');
41+
fs.mkdirSync(testOutputDir, { recursive: true });
42+
fs.mkdirSync(htmlReportDir, { recursive: true });
43+
44+
process.env.PW_TEST_OUTPUT_DIR = process.env.PW_TEST_OUTPUT_DIR || testOutputDir;
45+
process.env.PW_HTML_REPORT_DIR = process.env.PW_HTML_REPORT_DIR || htmlReportDir;
46+
}
47+
48+
ensureWritablePlaywrightOutputDirs();
49+
2150
const args = ['test', ...process.argv.slice(2)];
2251
const result = spawnSync('playwright', args, {
2352
stdio: 'inherit',
@@ -26,4 +55,3 @@ const result = spawnSync('playwright', args, {
2655
});
2756

2857
process.exit(result.status ?? 1);
29-

tests/E2E_TESTING_SOP.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ Notes:
3434
Ensure `python` is available on PATH (WSL2 may only have `python3`).
3535
- If you use WSL and run from `/mnt/c/...`, set a writable temp directory to avoid
3636
Playwright transform cache permission errors (e.g. `.tmp/playwright` or `/tmp`).
37+
- If you use WSL and run from `/mnt/c/...`, Playwright may also fail to delete/write large
38+
test artifacts (videos, HTML report) due to Windows/DrvFS permission semantics.
39+
- Best: clone/move the repo into the WSL Linux filesystem (e.g. `~/src/ComfyUI-Doctor`).
40+
- Alternative: redirect Playwright outputs using `PW_TEST_OUTPUT_DIR` and `PW_HTML_REPORT_DIR`.
41+
`scripts/run-playwright.mjs` auto-defaults these to `/tmp/comfyui-doctor/playwright/...`
42+
when it detects WSL + `/mnt/*`, so you usually don't need to set them manually.
43+
- If you run tests from a non-interactive shell (CI/automation/tools), `nvm` may not auto-load.
44+
Run `source ~/.nvm/nvm.sh` and confirm `node -v` shows 18+ before `npm test`.
3745

3846
---
3947

@@ -63,6 +71,7 @@ Expected result: `55 passed` with no failures.
6371
# From the repository root
6472
source ~/.nvm/nvm.sh
6573
nvm use 18
74+
node -v
6675
python3 --version
6776

6877
# Provide `python` if only python3 exists (local shim)
@@ -115,6 +124,7 @@ npm test
115124
source ~/.nvm/nvm.sh
116125
nvm install 18
117126
nvm use 18
127+
node -v
118128
python3 --version
119129
```
120130

tests/e2e/mocks/ui-text.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"save_settings_btn": "Save Settings",
1919
"language_label": "Language",
2020
"api_key_label": "API Key",
21+
"api_key_tip": "Tip: Prefer using Google AI Studio's free API quota for AI debugging. For more info, see:",
2122
"api_key_placeholder": "Enter API key",
2223
"model_label": "Model",
2324
"model_name_label": "Model",
@@ -69,8 +70,9 @@
6970
"tab_settings": "設定",
7071
"no_errors_detected": "エラーなし",
7172
"system_running_smoothly": "システムは正常に動作中",
73+
"api_key_tip": "ヒント:AI デバッグには、まず Google AI Studio の無料 API 枠の利用をおすすめします。詳しくは:",
7274
"statistics_title": "エラー統計",
7375
"stats_total_errors": "合計 (30日)",
7476
"stats_last_24h": "過去24時間"
7577
}
76-
}
78+
}

tests/e2e/specs/error-boundaries.spec.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ test.describe('Error Boundaries', () => {
138138
await page.goto('test-harness.html');
139139

140140
// Wait for Doctor UI to initialize
141-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
141+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
142142

143143
// Wait for ErrorBoundary UI to appear
144144
await expect(page.locator('.error-boundary-container')).toBeVisible({ timeout: 5000 });
@@ -163,7 +163,7 @@ test.describe('Error Boundaries', () => {
163163
});
164164

165165
await page.goto('test-harness.html');
166-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
166+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
167167

168168
// Verify error UI appears
169169
await expect(page.locator('.error-boundary-container')).toBeVisible({ timeout: 5000 });
@@ -189,7 +189,7 @@ test.describe('Error Boundaries', () => {
189189
});
190190

191191
await page.goto('test-harness.html');
192-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
192+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
193193

194194
// Verify error UI appears
195195
await expect(page.locator('.error-boundary-container')).toBeVisible({ timeout: 5000 });
@@ -226,7 +226,7 @@ test.describe('Error Boundaries', () => {
226226
});
227227

228228
await page.goto('test-harness.html');
229-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
229+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
230230

231231
// Wait for error UI
232232
await expect(page.locator('.error-boundary-container')).toBeVisible({ timeout: 5000 });
@@ -256,7 +256,7 @@ test.describe('Error Boundaries', () => {
256256
});
257257

258258
await page.goto('test-harness.html');
259-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
259+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
260260

261261
// Chat should show error
262262
await expect(page.locator('.error-boundary-container')).toBeVisible({ timeout: 5000 });
@@ -294,7 +294,7 @@ test.describe('Error Boundaries', () => {
294294
});
295295

296296
await page.goto('test-harness.html');
297-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
297+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
298298

299299
// Wait for error and logging
300300
await expect(page.locator('.error-boundary-container')).toBeVisible({ timeout: 5000 });
@@ -310,7 +310,7 @@ test.describe('Error Boundaries', () => {
310310
test('normal operation without error injection', async ({ page }) => {
311311
// No error injection - normal operation
312312
await page.goto('test-harness.html');
313-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
313+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
314314

315315
// Error boundary should NOT appear
316316
await page.waitForTimeout(1000);
@@ -333,7 +333,7 @@ test.describe('Error Boundaries', () => {
333333
});
334334

335335
await page.goto('test-harness.html');
336-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
336+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
337337

338338
// Wait for error boundary to appear and logs to be captured
339339
await expect(page.locator('.error-boundary-container')).toBeVisible({ timeout: 5000 });

tests/e2e/specs/preact-loader.spec.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ test.describe('Preact Loader', () => {
3333
await page.goto('test-harness.html');
3434

3535
// Wait for Doctor UI to be ready (can be slower on /mnt/c + sandboxed environments)
36-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 20000 });
36+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 20000 });
3737
});
3838

3939
test.describe('Module Loading', () => {
@@ -104,7 +104,7 @@ test.describe('Preact Loader', () => {
104104

105105
// Reload to apply the flag
106106
await page.reload();
107-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 10000 });
107+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 10000 });
108108

109109
const isEnabled = await page.evaluate(async () => {
110110
const { isPreactEnabled } = await import('/web/preact-loader.js');
@@ -143,7 +143,7 @@ test.describe('Preact Loader', () => {
143143

144144
// Re-navigate so the blocking routes apply before any module imports happen.
145145
await page.goto('test-harness.html');
146-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 20000 });
146+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 20000 });
147147
});
148148

149149
test('should handle complete load failure gracefully', async ({ page }) => {
@@ -225,7 +225,7 @@ test.describe('Preact Loader', () => {
225225

226226
// Navigate to test harness (Preact will fail to load)
227227
await page.goto('test-harness.html');
228-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
228+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
229229

230230
// Verify Chat UI still renders (vanilla fallback)
231231
const chatMessages = page.locator('#doctor-messages');
@@ -261,7 +261,7 @@ test.describe('Preact Loader', () => {
261261

262262
// Navigate to test harness
263263
await page.goto('test-harness.html');
264-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
264+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
265265

266266
// Switch to Stats tab
267267
await page.click('.doctor-tab-button[data-tab-id="stats"]');
@@ -280,7 +280,7 @@ test.describe('Preact Loader', () => {
280280
await simulateVendorLoadFailure(page);
281281

282282
await page.goto('test-harness.html');
283-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
283+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
284284

285285
// Use shared helper from helpers.js
286286
await assertChatFallbackUI(page);
@@ -291,7 +291,7 @@ test.describe('Preact Loader', () => {
291291
await disablePreact(page);
292292

293293
await page.goto('test-harness.html');
294-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
294+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
295295

296296
// Use shared helper from helpers.js
297297
await assertChatFallbackUI(page);
@@ -306,7 +306,7 @@ test.describe('Preact Loader', () => {
306306

307307
test('should record error when island render function throws', async ({ page }) => {
308308
await page.goto('test-harness.html');
309-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
309+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
310310

311311
// Register a test island that throws on render, then mount it
312312
const result = await page.evaluate(async () => {
@@ -362,7 +362,7 @@ test.describe('Preact Loader', () => {
362362
await simulateVendorLoadFailure(page);
363363

364364
await page.goto('test-harness.html');
365-
await page.waitForFunction(() => window.__doctorTestReady === true, { timeout: 15000 });
365+
await page.waitForFunction(() => window.__doctorTestReady === true, null, { timeout: 15000 });
366366

367367
// Verify input is fully functional
368368
const input = page.locator('#doctor-input');

0 commit comments

Comments
 (0)