Skip to content

Commit 361b28e

Browse files
committed
feat(parameter-lab): complete F60 dynamic dimension picker with safe rendering guards, E2E coverage, and roadmap/record closeout
1 parent 68e7d06 commit 361b28e

File tree

3 files changed

+397
-52
lines changed

3 files changed

+397
-52
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ ComfyUI-OpenClaw is a **security-first orchestration layer** for ComfyUI that co
99
- **A secure-by-default HTTP API** for automation (webhooks, triggers, schedules, approvals, presets)
1010
- **Public-ready control-plane split architecture** (embedded UX + externalized high-risk control surfaces)
1111
- **Verification-first hardening lanes** (route drift, real-backend E2E, adversarial fuzz/mutation gates)
12-
- **Now supports major messaging platforms, including Discord, Telegram, WhatsApp, LINE, WeChat, KakaoTalk, and Slack.**
12+
- **Now supports 7 major messaging platforms, including Discord, Telegram, WhatsApp, LINE, WeChat, KakaoTalk, and Slack.**
1313
- **And more exciting features being added continuously**
1414

15-
---
15+
This project is designed to make **ComfyUI a reliable automation target** with an explicit admin boundary and hardened defaults.
1616

17-
This project is intentionally **not** a general-purpose assistant platform with broad remote execution surfaces. It is designed to make **ComfyUI a reliable automation target** with an explicit admin boundary and hardened defaults.
17+
---
1818

19-
**Security stance (how this project differs from convenience-first automation packs):**
19+
## Security stance (how this project differs from convenience-first automation packs):
2020

2121
- Public MAE route-plane posture is a hard guarantee: startup gate + CI no-skip drift suites must both pass
2222
- Control Plane Split is enforced for public posture: high-risk control surfaces are externalized while embedded UI stays on safe UX/read paths
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { test, expect } from '@playwright/test';
2+
import { mockComfyUiCore, waitForMoltbotReady, clickTab } from '../utils/helpers.js';
3+
4+
test.describe('Parameter Lab - Dynamic Dimensions', () => {
5+
test.beforeEach(async ({ page }) => {
6+
// 1. Setup mock environment
7+
await mockComfyUiCore(page);
8+
await page.goto('test-harness.html');
9+
await waitForMoltbotReady(page);
10+
11+
// 2. Inject mock graph with nodes and widgets
12+
await page.evaluate(() => {
13+
window.app.graph = {
14+
_nodes: [
15+
{
16+
id: 10,
17+
type: "KSampler",
18+
title: "My Sampler",
19+
widgets: [
20+
{ name: "seed", type: "number", value: 1234, options: {} },
21+
{ name: "steps", type: "number", value: 20, options: { values: [20, 30, 40] } },
22+
{ name: "sampler_name", type: "combo", value: "euler", options: { values: ["euler", "ddim", "uni_pc"] } }
23+
]
24+
},
25+
{
26+
id: 20,
27+
type: "CheckpointLoader",
28+
title: "Load Model",
29+
widgets: [
30+
{ name: "ckpt_name", type: "combo", value: "base.ckpt", options: { values: ["base.ckpt", "v2.ckpt", "xl.ckpt"] } }
31+
]
32+
}
33+
],
34+
getNodeById(id) { return this._nodes.find(n => n.id === id); },
35+
serialize() { return { "test_graph": true }; }
36+
};
37+
});
38+
39+
// 3. Open Parameter Lab
40+
await clickTab(page, 'Parameter Lab');
41+
});
42+
43+
test('can select node, widget, and add values via dropdown', async ({ page }) => {
44+
// Add Dimension
45+
await page.click('#lab-add-dim');
46+
await expect(page.locator('.moltbot-lab-dim-row.dynamic')).toBeVisible();
47+
48+
// Select Node (KSampler id=10)
49+
await page.selectOption('.dim-node-select', { value: '10' });
50+
51+
// Select Widget (sampler_name)
52+
await page.selectOption('.dim-widget-select', { value: 'sampler_name' });
53+
54+
// Verify candidates are populated
55+
const candidates = page.locator('.dim-candidate-select option');
56+
await expect(candidates).toHaveCount(4); // "Add option..." + 3 values
57+
58+
// Select a candidate "ddim"
59+
await page.selectOption('.dim-candidate-select', { value: 'ddim' });
60+
61+
// Verify chip added
62+
await expect(page.locator('.moltbot-chip >> text=ddim')).toBeVisible();
63+
64+
// Select another "uni_pc"
65+
await page.selectOption('.dim-candidate-select', { value: 'uni_pc' });
66+
await expect(page.locator('.moltbot-chip >> text=uni_pc')).toBeVisible();
67+
68+
// Verify remove chip
69+
await page.click('.moltbot-chip:has-text("ddim") .chip-rm');
70+
await expect(page.locator('.moltbot-chip >> text=ddim')).not.toBeVisible();
71+
});
72+
73+
test('can add custom manual values', async ({ page }) => {
74+
await page.click('#lab-add-dim');
75+
76+
// Select Node (KSampler id=10)
77+
await page.selectOption('.dim-node-select', { value: '10' });
78+
79+
// Select Widget (seed)
80+
await page.selectOption('.dim-widget-select', { value: 'seed' });
81+
82+
// Type custom value
83+
await page.fill('.dim-manual-input', '9999');
84+
await page.press('.dim-manual-input', 'Enter');
85+
86+
// Verify chip
87+
await expect(page.locator('.moltbot-chip >> text=9999')).toBeVisible();
88+
});
89+
90+
test('generates correct plan payload', async ({ page }) => {
91+
// Mock network request
92+
let payload = null;
93+
await page.route('**/openclaw/lab/sweep', async route => {
94+
payload = JSON.parse(route.request().postData());
95+
await route.fulfill({
96+
status: 200,
97+
contentType: 'application/json',
98+
body: JSON.stringify({ ok: true, data: { plan: { runs: [], experiment_id: "exp123" } } })
99+
});
100+
});
101+
102+
// Configure dimension
103+
await page.click('#lab-add-dim');
104+
await page.selectOption('.dim-node-select', { value: '20' }); // CheckpointLoader
105+
await page.selectOption('.dim-widget-select', { value: 'ckpt_name' });
106+
107+
// Add value "v2.ckpt" via candidate
108+
await page.selectOption('.dim-candidate-select', { value: 'v2.ckpt' });
109+
110+
// Add value "xl.ckpt" via candidate
111+
await page.selectOption('.dim-candidate-select', { value: 'xl.ckpt' });
112+
113+
// Click Generate
114+
await page.click('#lab-generate');
115+
116+
// Verify payload
117+
expect(payload).toBeTruthy();
118+
expect(payload.params).toHaveLength(1);
119+
expect(payload.params[0]).toEqual({
120+
node_id: 20,
121+
widget_name: "ckpt_name",
122+
values: ["v2.ckpt", "xl.ckpt"],
123+
strategy: "grid"
124+
});
125+
});
126+
});

0 commit comments

Comments
 (0)