Web UI Manual Testing
Context
The web UI serves three distinct roles on different ports. Manual testing
verifies that the automation pipeline, heartbeat system, and UI rendering
work end-to-end. Automated tests (pytest tests/test_webui_*.py) cover
component logic and API contracts but cannot catch visual regressions,
layout overflows, or workflow usability issues.
Prerequisites
ALWAYS verify these before starting a manual test session:
- System fully converged:
molecule test (or molecule converge) MUST
have completed successfully. ALL 6 hosts on the 10.10.10.x LAN, ALL
containers deployed and heartbeating, mesh established. NEVER start
manual testing against a partially deployed or pre-mesh system. If the
system is not converged, run molecule test first — do NOT proceed.
- Environment loaded:
set -a && source test.env && set +a
- Virtualenv active:
source .venv/bin/activate
- All hosts reachable:
pytest tests/test_webui_heartbeat.py -k "infrastructure" -v
— if any host is unreachable, STOP. Fix reachability first.
- Callhome API running: Check
.state/callhome_url exists, or start
via python scripts/webui/app.py --headless
Previous catastrophe (2026-04-10): Agent started manual testing in pre-mesh
state without running molecule test first. Every playbook that touched WAN
hosts returned "No route to host." The entire session was wasted verifying
expected failures instead of testing real functionality.
Starting the apps
# SuperManager (port 9001 default)
python scripts/webui/app.py
# Production mode (with .env)
set -a && source .env && set +a && python scripts/webui/app.py
# Test mode (with test.env)
set -a && source test.env && set +a && python scripts/webui/app.py
All three roles (SuperManager, Manager, Kiosk) run from the same app.
The sidebar navigation switches between them. Kiosk pages are also
accessible from their own nav bar.
Test walkthrough checklist
Phase 1: Dashboard (SuperManager)
Route: / (Dashboard)
- Fleet card renders: Health score, online/offline counts, bucket
summary line ("N Production · N Lab · N Test Units").
- All configured hosts appear: Count matches
test.env hosts + TEST_UNITS.
In test mode, expect 6 hosts (4 env + 2 test units from TEST_UNITS).
- Host status colors correct: Green = heartbeating. Orange = reachable
but no heartbeat. Grey = offline/unknown. Red = NEVER for "no data."
- View Fleet button navigates to
/nodes.
- No layout overflows: Resize browser to 1024px, 768px, mobile width.
No text truncation, no overlapping buttons, no elements escaping cards.
Phase 2: Fleet Nodes
Route: /nodes
- Bucket sections visible: Hosts grouped under "PRODUCTION", "LAB",
"TEST UNITS" headers with counts.
- Each host card clickable → navigates to
/nodes/{hostname}.
- Add Host form works: Expand "Add Host", fill name + IP, submit.
Confirm success message and host appears in list after refresh.
- Auto-detect bucket: Submit a host with IP ending in .201 → should
classify as "test". IP ending in .50 → "lab". IP ending in .180 → "lab".
Phase 3: Node Detail
Route: /nodes/{hostname} (click any host card)
- Summary header: Status dot, hostname, IP, uptime, version.
- Resource gauges: Disk and memory with color thresholds (green < 70%,
orange < 90%, red >= 90%).
- Guests table: Shows containers/VMs with VMID, name, type, status.
If no heartbeat, shows "Waiting for heartbeat data."
- Deploy history: Pass/fail badges with human-readable descriptions
(NOT raw "rc=2"). Recent success after past failure → host shows healthy.
- Network info: IPs, interfaces from extensions.
- Back navigation: Back button returns to
/nodes.
Phase 4: Deploy workflow
Route: /deploy
- Service selection: Tags load from
SERVICE_TAGS in data.py.
- Start Deploy: Confirm dialog, progress indication, completion status.
- Deploy history updates on dashboard after deploy finishes.
Phase 5: Kiosk (Home Hub)
Route: /hub
- All hub services render: Jellyfin, Kodi, Moonlight, Desktop tiles.
- Section grouping: Services grouped by section ("Entertainment",
"Infrastructure", etc.).
- Launch tiles functional: Click Jellyfin →
/launch?vmid=... →
shows launch page with description.
- Kiosk nav bar: Shows "Home" icon + breadcrumb, NOT "Home Hub" text.
Phase 6: Network pages (Manager)
Routes: /bridge, /mesh, /router
- Bridge: Both bridge hosts show with WiFi status (AP/STA roles).
Batman status renders. Signal quality colors use theme semantics.
- Mesh: Mesh AP + STAs visible. WiFi metrics (signal, channel, PHY).
- Router: OpenWrt status, WAN/LAN interface info.
- Containers: Container list with start/stop controls.
Phase 7: Cross-cutting checks
- Sidebar navigation: Every nav item leads to the correct page.
No duplicate icons. Active page highlighted.
- Color consistency: Same status = same color across all pages.
Red ONLY for errors/failures. Grey for no-data/offline.
- No raw internals visible: No exit codes, no JSON, no stack traces,
no internal identifiers shown to users anywhere.
- Responsive layout: Cards, tables, and gauges adapt to window resize.
No horizontal scrolling at 1024px width.
When tests fail
When a manual test reveals an issue:
- Fix the automation, not the symptom. If wrapper scripts are missing,
run
molecule converge, don't manually SSH and deploy. If heartbeats
aren't starting, fix the callhome config, don't hardcode status.
- Add a pytest test that catches the regression. If the issue was a
layout overflow, add a test that verifies the element exists with the
correct CSS class. If it was a missing host, add a test for host count.
- Update this skill if the test walkthrough missed the failure case.
Rules
- NEVER manually deploy scripts, config files, or patches during testing.
If infrastructure is missing, run the proper automation (
molecule converge,
build-images.sh, build.py).
- ALWAYS verify the system is fully converged (molecule test completed,
all hosts on LAN, all containers running) before starting manual testing.
NEVER test in a pre-mesh or partially deployed state — it produces a wall
of "No route to host" errors that prove nothing.
- ALWAYS test with both
test.env AND .env if production hosts are
available. Bucket classification, host counts, and heartbeat behavior
differ between environments.
- ALWAYS resize the browser to multiple widths during testing. Layout
bugs only appear at non-default sizes.
- NEVER consider manual testing complete until every phase above has
been checked. Skipping Phase 6 (network pages) because "they probably
work" is how batman_trigger.sh went unnoticed for multiple sessions.
- ALWAYS check that new hosts from
TEST_UNITS appear in the correct
bucket on first load (no manual registration needed).
- After manual testing, run
pytest tests/ -v to confirm automated tests
still pass — manual fixes sometimes break automated expectations.
- NEVER fabricate heartbeat data with curl/scripts to simulate real nodes.
Use the REAL physical test machines with REAL heartbeats from REAL
containers. Fabricated data is the same anti-pattern as mocking — it
produces a pretty dashboard that proves nothing about whether the
system actually works.
- ALWAYS actually engage interactive features during testing. Viewing
a page is NOT testing it. Click the batman toggle. Click Restart WiFi.
Click bridge mode switch. Verify the REAL operation ran on the REAL
hardware by checking the REAL output.
- NEVER claim "manual testing complete" after only viewing pages.
Every button, toggle, and action on the page must be exercised against
real infrastructure. If an action can't be tested because infrastructure
is missing, that is a test failure — not a skip.
Cluster Manager testing (4-tier)
When testing the Cluster Manager (kiosk_server.py --config with
IS_CLUSTER_MANAGER=true):
- The Cluster Manager runs on the router node (home). Its child Managers
are the 5 other nodes: mesh1, mesh2, ai, bridge-1, bridge-2.
- Child Managers must be REAL kiosk_server instances running on REAL
hosts, heartbeating to the Cluster Manager. NEVER simulate heartbeats
with curl.
- Batman mode testing: enable batman from the Cluster Manager GUI. Verify
the event was broadcast to ALL child Managers. Verify each child Manager
executed batman_trigger.sh on its local containers. Check batman status
on every node.
- Bridge/WiFi testing: restart WiFi, switch AP/STA modes, verify the
operations ran on the actual bridge containers.
- If kiosk containers are not deployed on the test hosts, deploy them
first with
molecule converge — do NOT fake the data.
Manual Testing Playbooks
Step-by-step runbooks with exact commands for each test scenario are in
docs/manual-testing-playbooks.md. The playbooks cover:
- Playbook 1: Kiosk container health checks (all 6 hosts)
- Playbook 2: Cluster Manager fleet verification (API queries)
- Playbook 3: Batman mode enable/disable/status (with verification on real containers)
- Playbook 4: WiFi status on mesh/bridge containers
- Playbook 5: Guest management via Manager API
- Playbook 6: Child Manager direct communication
- Playbook 7: End-to-end heartbeat chain (Container → Manager → Cluster Manager)
- Playbook 8: Image version pipeline verification
- Playbook 9: SuperManager verification (API + fleet health)
- Playbook 10: Kiosk functionality on each unit (service, HTTP, callhome, config)
- Playbook 11: Manager kiosk fleet dashboard (home CM)
- Playbook 12: Browser-based UI verification (SM, CM, NM visual + interactive)
- Playbook 13: Hierarchical kiosk control via KasmVNC display pipeline
(KasmVNC iframe streaming, single-process display service per container/VM,
two-level drill-down with back-navigation, EVERY hub app tested via iframe —
13.4a: 3 display apps (Desktop, Kodi, Moonlight) via
/console iframe,
13.4b: 8 external web UIs (Jellyfin, Home Assistant, Gaming, OpenWrt,
Pi-hole, WireGuard, Netdata, Logs), 13.4c: 4 internal pages (Bridge, Mesh,
Router, Containers), 13.4d: two-level drill-down app launch,
CM-perspective display, error/edge cases, legacy VNC absence verification)
- Playbook 14: Cluster Manager sidebar and submenu navigation (full nav tree)
- Playbook 15: NodeManager kiosk navigation on individual unit
- Playbook 16: SuperManager full page navigation (all SM pages: Dashboard,
Environment, Hosts, Services, Images, Deploy, Timeline, Nodes)
- Playbook 17: Display app launch flow end-to-end (hub tile → launch page →
guest start → console page → KasmVNC iframe. Session switching KDE↔GNOME,
app switcher round-trips, display conflict resolution)
- Playbook 18: External web UI testing via hub tiles (Jellyfin, Home Assistant,
Router, Pi-hole, WireGuard, Netdata, Logs, Gaming — iframe viewer + X-Frame-Options)
- Playbook 19: Internal kiosk pages — CM interactive elements deep test
(Bridge WiFi restart, Mesh batman cycle, Router data verification,
Containers guest lifecycle)
- Playbook 20: SuperManager display pipeline — every host verification
(6-host × 4-app matrix, child picker drill-down, non-available app errors)
ALWAYS use the playbooks for manual testing. They contain the exact commands
to run against real infrastructure — no improvisation needed. EXECUTE every
section — do not just read through them. Every button, toggle, and action
must be exercised against real hardware.
When WRITING new playbooks, load the manual-testing-playbook-writing skill
for exhaustive feature enumeration, section structure, host-awareness, and
per-app test step patterns.
Previous bugs found by manual testing
- "Desktop" icon (
monitoring) overlapped adjacent buttons at narrow
widths. Fix: changed to lan icon with proper spacing.
- Fleet card showed "No nodes registered" when API server wasn't running.
Fix: show configured hosts from env with "waiting for heartbeats" message.
rc=2 displayed raw in deploy badges. Fix: exit_code_label() mapping.
- Only 4 of 6 hosts appeared in production mode. Fix:
HostRegistry +
TEST_UNITS env var support.
- Bridge batman tests failed because wrapper scripts were never deployed
via automation. Fix:
molecule converge -s bridge-lxc instead of manual
SSH deployment.
- Color semantics violated: red used for "no WoL", "stopped", "no data".
Fix: systematic audit using grey/orange per semantic rules.
- Stale
vnc_shared.cpython-312.pyc in __pycache__ caused the running SM
to serve legacy noVNC JavaScript (rfb.js, vnc-container) even after
vnc_shared.py was deleted. The SM process started before the migration was
using cached bytecode. Fix: delete stale .pyc files and restart the SM
process after code changes. ALWAYS restart long-running Python processes
after source code deletions — __pycache__ keeps serving deleted modules.
- Orphan socat process (from previous session) held port 16080 on the primary
host, causing
kiosk-display-relay-mesh1.service to crash-loop with "Address
already in use" (1720+ restarts). Fix: kill the orphan, restart the service.
ALWAYS check for orphan socat processes before investigating service failures.
- Router VM (VMID 100) was stopped during manual testing session. Batman status
returned "No route to host" for all child Managers because the router provides
LAN↔WAN routing. Fix: always verify the router VM is running before starting
batman/WiFi tests. The router may stop between molecule test runs.
- SSH tunnels for DNAT hosts (ai, mesh2) must target the container's NAT IP
directly (
10.99.x.20:6080) not 127.0.0.1:6080. DNAT only matches on
the external interface, not loopback.
- mesh1 KasmVNC display is blank from the SM when the controller has no VPN
interface (wg0). The
_env_node_resolver falls back to LAN IP (10.10.10.210)
which the browser can't reach directly — it's behind the OpenWrt router.
In production with VPN, this resolves to the VPN IP (10.0.0.2) and works.
This is expected behavior in the non-VPN development environment.
- SM Hub "Launch" buttons are shared code designed for kiosk (NM) usage.
On the SM, the launch page renders correctly but the "View Console" link
is absent because the SM has no
host_name set. The correct flow for
remote app launching is SM → Open Kiosk (remote display) → use the kiosk's
own Hub to launch apps within the KasmVNC iframe.
- SM Hub external service tiles (Jellyfin, HA, Pi-hole, Netdata) show
"Not available" because those URLs come from kiosk
config.json which the
SM doesn't have. Test these tiles through the remote kiosk viewer by clicking
"Open Kiosk" on a node detail page and interacting within the KasmVNC iframe.
- CM tunnel via socat + nsenter is required for browser testing. Direct SSH
tunnels to the kiosk LAN IP (10.10.10.23) can fail due to IP collisions.
Use:
lxc-info -n 401 to get PID, nsenter -t $PID -n socat to relay
through the container's network namespace.
- KasmVNC iframe content cannot be interacted with via browser automation tools
(Playwright/Puppeteer). The iframe renders a VNC stream, not DOM elements.
Mouse/keyboard interaction must be verified manually or via VNC-specific APIs.
Browser automation can verify the iframe loads (non-blank) and the viewer bar
controls work, but NOT the content inside the stream.
- Quasar QSelect components with opacity:0 inputs cannot be clicked via browser
automation refs. Use coordinate-based clicks on the visible label/arrow area,
or test via direct URL navigation instead.
1---2name: webui-manual-testing3description: Manual testing procedures for the NiceGUI web UIs (SuperManager, Manager, Kiosk). Use when performing manual test walkthroughs, verifying UI behavior after changes, or when the user asks to "manually test" the web apps.4---56# Web UI Manual Testing78## Context910The web UI serves three distinct roles on different ports. Manual testing11verifies that the automation pipeline, heartbeat system, and UI rendering12work end-to-end. Automated tests (`pytest tests/test_webui_*.py`) cover13component logic and API contracts but cannot catch visual regressions,14layout overflows, or workflow usability issues.1516## Prerequisites1718ALWAYS verify these before starting a manual test session:19201. **System fully converged**: `molecule test` (or `molecule converge`) MUST21 have completed successfully. ALL 6 hosts on the 10.10.10.x LAN, ALL22 containers deployed and heartbeating, mesh established. NEVER start23 manual testing against a partially deployed or pre-mesh system. If the24 system is not converged, run `molecule test` first — do NOT proceed.252. **Environment loaded**: `set -a && source test.env && set +a`263. **Virtualenv active**: `source .venv/bin/activate`274. **All hosts reachable**: `pytest tests/test_webui_heartbeat.py -k "infrastructure" -v`28 — if any host is unreachable, STOP. Fix reachability first.295. **Callhome API running**: Check `.state/callhome_url` exists, or start30 via `python scripts/webui/app.py --headless`3132Previous catastrophe (2026-04-10): Agent started manual testing in pre-mesh33state without running molecule test first. Every playbook that touched WAN34hosts returned "No route to host." The entire session was wasted verifying35expected failures instead of testing real functionality.3637## Starting the apps3839```bash40# SuperManager (port 9001 default)41python scripts/webui/app.py4243# Production mode (with .env)44set -a && source .env && set +a && python scripts/webui/app.py4546# Test mode (with test.env)47set -a && source test.env && set +a && python scripts/webui/app.py48```4950All three roles (SuperManager, Manager, Kiosk) run from the same app.51The sidebar navigation switches between them. Kiosk pages are also52accessible from their own nav bar.5354## Test walkthrough checklist5556### Phase 1: Dashboard (SuperManager)5758Route: `/` (Dashboard)59601. **Fleet card renders**: Health score, online/offline counts, bucket61 summary line ("N Production · N Lab · N Test Units").622. **All configured hosts appear**: Count matches `test.env` hosts + `TEST_UNITS`.63 In test mode, expect 6 hosts (4 env + 2 test units from `TEST_UNITS`).643. **Host status colors correct**: Green = heartbeating. Orange = reachable65 but no heartbeat. Grey = offline/unknown. Red = NEVER for "no data."664. **View Fleet button navigates** to `/nodes`.675. **No layout overflows**: Resize browser to 1024px, 768px, mobile width.68 No text truncation, no overlapping buttons, no elements escaping cards.6970### Phase 2: Fleet Nodes7172Route: `/nodes`73741. **Bucket sections visible**: Hosts grouped under "PRODUCTION", "LAB",75 "TEST UNITS" headers with counts.762. **Each host card clickable** → navigates to `/nodes/{hostname}`.773. **Add Host form works**: Expand "Add Host", fill name + IP, submit.78 Confirm success message and host appears in list after refresh.794. **Auto-detect bucket**: Submit a host with IP ending in .201 → should80 classify as "test". IP ending in .50 → "lab". IP ending in .180 → "lab".8182### Phase 3: Node Detail8384Route: `/nodes/{hostname}` (click any host card)85861. **Summary header**: Status dot, hostname, IP, uptime, version.872. **Resource gauges**: Disk and memory with color thresholds (green < 70%,88 orange < 90%, red >= 90%).893. **Guests table**: Shows containers/VMs with VMID, name, type, status.90 If no heartbeat, shows "Waiting for heartbeat data."914. **Deploy history**: Pass/fail badges with human-readable descriptions92 (NOT raw "rc=2"). Recent success after past failure → host shows healthy.935. **Network info**: IPs, interfaces from extensions.946. **Back navigation**: Back button returns to `/nodes`.9596### Phase 4: Deploy workflow9798Route: `/deploy`991001. **Service selection**: Tags load from `SERVICE_TAGS` in `data.py`.1012. **Start Deploy**: Confirm dialog, progress indication, completion status.1023. **Deploy history updates** on dashboard after deploy finishes.103104### Phase 5: Kiosk (Home Hub)105106Route: `/hub`1071081. **All hub services render**: Jellyfin, Kodi, Moonlight, Desktop tiles.1092. **Section grouping**: Services grouped by section ("Entertainment",110 "Infrastructure", etc.).1113. **Launch tiles functional**: Click Jellyfin → `/launch?vmid=...` →112 shows launch page with description.1134. **Kiosk nav bar**: Shows "Home" icon + breadcrumb, NOT "Home Hub" text.114115### Phase 6: Network pages (Manager)116117Routes: `/bridge`, `/mesh`, `/router`1181191. **Bridge**: Both bridge hosts show with WiFi status (AP/STA roles).120 Batman status renders. Signal quality colors use theme semantics.1212. **Mesh**: Mesh AP + STAs visible. WiFi metrics (signal, channel, PHY).1223. **Router**: OpenWrt status, WAN/LAN interface info.1234. **Containers**: Container list with start/stop controls.124125### Phase 7: Cross-cutting checks1261271. **Sidebar navigation**: Every nav item leads to the correct page.128 No duplicate icons. Active page highlighted.1292. **Color consistency**: Same status = same color across all pages.130 Red ONLY for errors/failures. Grey for no-data/offline.1313. **No raw internals visible**: No exit codes, no JSON, no stack traces,132 no internal identifiers shown to users anywhere.1334. **Responsive layout**: Cards, tables, and gauges adapt to window resize.134 No horizontal scrolling at 1024px width.135136## When tests fail137138When a manual test reveals an issue:1391401. **Fix the automation, not the symptom.** If wrapper scripts are missing,141 run `molecule converge`, don't manually SSH and deploy. If heartbeats142 aren't starting, fix the callhome config, don't hardcode status.1432. **Add a pytest test** that catches the regression. If the issue was a144 layout overflow, add a test that verifies the element exists with the145 correct CSS class. If it was a missing host, add a test for host count.1463. **Update this skill** if the test walkthrough missed the failure case.147148## Rules1491501. NEVER manually deploy scripts, config files, or patches during testing.151 If infrastructure is missing, run the proper automation (`molecule converge`,152 `build-images.sh`, `build.py`).1532. ALWAYS verify the system is fully converged (molecule test completed,154 all hosts on LAN, all containers running) before starting manual testing.155 NEVER test in a pre-mesh or partially deployed state — it produces a wall156 of "No route to host" errors that prove nothing.1573. ALWAYS test with both `test.env` AND `.env` if production hosts are158 available. Bucket classification, host counts, and heartbeat behavior159 differ between environments.1604. ALWAYS resize the browser to multiple widths during testing. Layout161 bugs only appear at non-default sizes.1625. NEVER consider manual testing complete until every phase above has163 been checked. Skipping Phase 6 (network pages) because "they probably164 work" is how batman_trigger.sh went unnoticed for multiple sessions.1656. ALWAYS check that new hosts from `TEST_UNITS` appear in the correct166 bucket on first load (no manual registration needed).1677. After manual testing, run `pytest tests/ -v` to confirm automated tests168 still pass — manual fixes sometimes break automated expectations.1698. NEVER fabricate heartbeat data with curl/scripts to simulate real nodes.170 Use the REAL physical test machines with REAL heartbeats from REAL171 containers. Fabricated data is the same anti-pattern as mocking — it172 produces a pretty dashboard that proves nothing about whether the173 system actually works.1749. ALWAYS actually engage interactive features during testing. Viewing175 a page is NOT testing it. Click the batman toggle. Click Restart WiFi.176 Click bridge mode switch. Verify the REAL operation ran on the REAL177 hardware by checking the REAL output.17810. NEVER claim "manual testing complete" after only viewing pages.179 Every button, toggle, and action on the page must be exercised against180 real infrastructure. If an action can't be tested because infrastructure181 is missing, that is a test failure — not a skip.182183### Cluster Manager testing (4-tier)184185When testing the Cluster Manager (`kiosk_server.py --config` with186`IS_CLUSTER_MANAGER=true`):187188- The Cluster Manager runs on the router node (home). Its child Managers189 are the 5 other nodes: mesh1, mesh2, ai, bridge-1, bridge-2.190- Child Managers must be REAL kiosk_server instances running on REAL191 hosts, heartbeating to the Cluster Manager. NEVER simulate heartbeats192 with curl.193- Batman mode testing: enable batman from the Cluster Manager GUI. Verify194 the event was broadcast to ALL child Managers. Verify each child Manager195 executed batman_trigger.sh on its local containers. Check batman status196 on every node.197- Bridge/WiFi testing: restart WiFi, switch AP/STA modes, verify the198 operations ran on the actual bridge containers.199- If kiosk containers are not deployed on the test hosts, deploy them200 first with `molecule converge` — do NOT fake the data.201202## Manual Testing Playbooks203204Step-by-step runbooks with exact commands for each test scenario are in205`docs/manual-testing-playbooks.md`. The playbooks cover:206207- Playbook 1: Kiosk container health checks (all 6 hosts)208- Playbook 2: Cluster Manager fleet verification (API queries)209- Playbook 3: Batman mode enable/disable/status (with verification on real containers)210- Playbook 4: WiFi status on mesh/bridge containers211- Playbook 5: Guest management via Manager API212- Playbook 6: Child Manager direct communication213- Playbook 7: End-to-end heartbeat chain (Container → Manager → Cluster Manager)214- Playbook 8: Image version pipeline verification215- Playbook 9: SuperManager verification (API + fleet health)216- Playbook 10: Kiosk functionality on each unit (service, HTTP, callhome, config)217- Playbook 11: Manager kiosk fleet dashboard (home CM)218- Playbook 12: Browser-based UI verification (SM, CM, NM visual + interactive)219- Playbook 13: Hierarchical kiosk control via KasmVNC display pipeline220 (KasmVNC iframe streaming, single-process display service per container/VM,221 two-level drill-down with back-navigation, EVERY hub app tested via iframe —222 13.4a: 3 display apps (Desktop, Kodi, Moonlight) via `/console` iframe,223 13.4b: 8 external web UIs (Jellyfin, Home Assistant, Gaming, OpenWrt,224 Pi-hole, WireGuard, Netdata, Logs), 13.4c: 4 internal pages (Bridge, Mesh,225 Router, Containers), 13.4d: two-level drill-down app launch,226 CM-perspective display, error/edge cases, legacy VNC absence verification)227- Playbook 14: Cluster Manager sidebar and submenu navigation (full nav tree)228- Playbook 15: NodeManager kiosk navigation on individual unit229- Playbook 16: SuperManager full page navigation (all SM pages: Dashboard,230 Environment, Hosts, Services, Images, Deploy, Timeline, Nodes)231- Playbook 17: Display app launch flow end-to-end (hub tile → launch page →232 guest start → console page → KasmVNC iframe. Session switching KDE↔GNOME,233 app switcher round-trips, display conflict resolution)234- Playbook 18: External web UI testing via hub tiles (Jellyfin, Home Assistant,235 Router, Pi-hole, WireGuard, Netdata, Logs, Gaming — iframe viewer + X-Frame-Options)236- Playbook 19: Internal kiosk pages — CM interactive elements deep test237 (Bridge WiFi restart, Mesh batman cycle, Router data verification,238 Containers guest lifecycle)239- Playbook 20: SuperManager display pipeline — every host verification240 (6-host × 4-app matrix, child picker drill-down, non-available app errors)241242ALWAYS use the playbooks for manual testing. They contain the exact commands243to run against real infrastructure — no improvisation needed. EXECUTE every244section — do not just read through them. Every button, toggle, and action245must be exercised against real hardware.246247When WRITING new playbooks, load the `manual-testing-playbook-writing` skill248for exhaustive feature enumeration, section structure, host-awareness, and249per-app test step patterns.250251## Previous bugs found by manual testing252253- "Desktop" icon (`monitoring`) overlapped adjacent buttons at narrow254 widths. Fix: changed to `lan` icon with proper spacing.255- Fleet card showed "No nodes registered" when API server wasn't running.256 Fix: show configured hosts from env with "waiting for heartbeats" message.257- `rc=2` displayed raw in deploy badges. Fix: `exit_code_label()` mapping.258- Only 4 of 6 hosts appeared in production mode. Fix: `HostRegistry` +259 `TEST_UNITS` env var support.260- Bridge batman tests failed because wrapper scripts were never deployed261 via automation. Fix: `molecule converge -s bridge-lxc` instead of manual262 SSH deployment.263- Color semantics violated: red used for "no WoL", "stopped", "no data".264 Fix: systematic audit using grey/orange per semantic rules.265- Stale `vnc_shared.cpython-312.pyc` in `__pycache__` caused the running SM266 to serve legacy noVNC JavaScript (`rfb.js`, `vnc-container`) even after267 `vnc_shared.py` was deleted. The SM process started before the migration was268 using cached bytecode. Fix: delete stale `.pyc` files and restart the SM269 process after code changes. ALWAYS restart long-running Python processes270 after source code deletions — `__pycache__` keeps serving deleted modules.271- Orphan socat process (from previous session) held port 16080 on the primary272 host, causing `kiosk-display-relay-mesh1.service` to crash-loop with "Address273 already in use" (1720+ restarts). Fix: kill the orphan, restart the service.274 ALWAYS check for orphan socat processes before investigating service failures.275- Router VM (VMID 100) was stopped during manual testing session. Batman status276 returned "No route to host" for all child Managers because the router provides277 LAN↔WAN routing. Fix: always verify the router VM is running before starting278 batman/WiFi tests. The router may stop between molecule test runs.279- SSH tunnels for DNAT hosts (ai, mesh2) must target the container's NAT IP280 directly (`10.99.x.20:6080`) not `127.0.0.1:6080`. DNAT only matches on281 the external interface, not loopback.282- mesh1 KasmVNC display is blank from the SM when the controller has no VPN283 interface (wg0). The `_env_node_resolver` falls back to LAN IP (10.10.10.210)284 which the browser can't reach directly — it's behind the OpenWrt router.285 In production with VPN, this resolves to the VPN IP (10.0.0.2) and works.286 This is expected behavior in the non-VPN development environment.287- SM Hub "Launch" buttons are shared code designed for kiosk (NM) usage.288 On the SM, the launch page renders correctly but the "View Console" link289 is absent because the SM has no `host_name` set. The correct flow for290 remote app launching is SM → Open Kiosk (remote display) → use the kiosk's291 own Hub to launch apps within the KasmVNC iframe.292- SM Hub external service tiles (Jellyfin, HA, Pi-hole, Netdata) show293 "Not available" because those URLs come from kiosk `config.json` which the294 SM doesn't have. Test these tiles through the remote kiosk viewer by clicking295 "Open Kiosk" on a node detail page and interacting within the KasmVNC iframe.296- CM tunnel via socat + nsenter is required for browser testing. Direct SSH297 tunnels to the kiosk LAN IP (10.10.10.23) can fail due to IP collisions.298 Use: `lxc-info -n 401` to get PID, `nsenter -t $PID -n socat` to relay299 through the container's network namespace.300- KasmVNC iframe content cannot be interacted with via browser automation tools301 (Playwright/Puppeteer). The iframe renders a VNC stream, not DOM elements.302 Mouse/keyboard interaction must be verified manually or via VNC-specific APIs.303 Browser automation can verify the iframe loads (non-blank) and the viewer bar304 controls work, but NOT the content inside the stream.305- Quasar QSelect components with opacity:0 inputs cannot be clicked via browser306 automation refs. Use coordinate-based clicks on the visible label/arrow area,307 or test via direct URL navigation instead.