Files

123 lines
4.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ComfyUI-OpenClaw - Test Harness</title>
<style>
body { margin:0; padding:0; background:#1e1e1e; color:#fff; font-family:system-ui,-apple-system,sans-serif; }
#test-status { position:fixed; top:10px; right:10px; padding:8px 12px; background:#2d2d2d; border-radius:6px; font-size:12px; z-index:10000; }
#test-status.ready { background:#0f4c0f; }
#mock-sidebar-tabs { width: 340px; height: 100vh; border-right: 1px solid #444; float:left; background:#161616; }
#comfy-root { margin-left: 340px; height: 100vh; }
</style>
</head>
<body>
<div id="test-status">Loading...</div>
<div id="mock-sidebar-tabs"></div>
<div id="comfy-root"></div>
<script type="module">
import { createMockComfyUIApp } from './mocks/comfyui-app.js';
window.__openclawTestReady = false;
window.__openclawTestError = null;
window.__openclawTestLoadAttempts = 0;
// Mock ComfyUI app BEFORE importing extension
window.app = createMockComfyUIApp();
// Minimal fetch mocks for OpenClaw UI calls
const realFetch = window.fetch.bind(window);
window.fetch = async (input, init) => {
const url = typeof input === 'string' ? input : (input?.url || '');
// Handle both new (/openclaw/...) and legacy (/moltbot/...) paths for robustness in tests
if (
url.endsWith('/openclaw/capabilities') || url.endsWith('/api/openclaw/capabilities') ||
url.endsWith('/moltbot/capabilities') || url.endsWith('/api/moltbot/capabilities')
) {
return new Response(JSON.stringify({
api_version: 1,
pack: { name: 'ComfyUI-OpenClaw', version: 'test' },
features: {
assist_planner: true,
assist_refiner: true,
presets: true,
approvals: true,
scheduler: true,
explorer: true,
preflight: true,
checkpoints: true,
packs: true,
model_manager: true,
png_info: true,
},
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
}
if (
url.endsWith('/openclaw/health') || url.endsWith('/api/openclaw/health') ||
url.endsWith('/moltbot/health') || url.endsWith('/api/moltbot/health')
) {
return new Response(JSON.stringify({ ok: true, pack: { name: 'ComfyUI-OpenClaw', version: 'test' }, access_policy: { observability: 'loopback_only', token_configured: false }, config: {} }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
// Default: pass through (so we see failures if something unexpected happens)
return realFetch(input, init);
};
function isTransientModuleFetchError(error) {
const message = String(error?.message || error || '');
return message.includes('Failed to fetch dynamically imported module');
}
async function loadOpenClawWithRetry() {
const maxAttempts = 4;
let lastError = null;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
window.__openclawTestLoadAttempts = attempt;
await import(`../../web/openclaw.js?openclaw_harness_attempt=${attempt}`);
// Allow async setup to mount
await new Promise(r => setTimeout(r, 500));
return;
} catch (error) {
lastError = error;
if (!isTransientModuleFetchError(error) || attempt >= maxAttempts) {
throw error;
}
// IMPORTANT: keep this retry window small and transient-fetch-specific.
// Windows CI can occasionally drop up to three early module-graph
// fetches under parallel Playwright load, but broad retries here would
// risk masking real import/runtime regressions.
await new Promise(r => setTimeout(r, 250 * attempt));
}
}
throw lastError;
}
try {
await loadOpenClawWithRetry();
window.__openclawTestReady = true;
document.getElementById('test-status').textContent = '✓ Ready for Testing';
document.getElementById('test-status').className = 'ready';
window.dispatchEvent(new CustomEvent('openclaw-ready'));
} catch (e) {
console.error('[Test Harness] Failed to load OpenClaw UI:', e);
window.__openclawTestError = e;
document.getElementById('test-status').textContent = '✗ Load Failed: ' + (e?.message || String(e));
document.getElementById('test-status').style.background = '#4c0f0f';
}
</script>
</body>
</html>