feat(web-console): rebuild intelligent cockpit sync interface

This commit is contained in:
vee
2026-08-18 13:24:36 +08:00
parent 344f662970
commit 9b24d5c8c9
30 changed files with 4100 additions and 12594 deletions
+104 -103
View File
@@ -174,37 +174,81 @@ operations:
finally:
self.service.mode_profiles = original_registry
def test_head_unit_display_sync_tracks_tasks_and_usb_catalog(self) -> None:
task_request = urllib.request.Request(
f"{self.base_url}/api/requests",
method="POST",
headers={"Content-Type": "application/json"},
data=json.dumps(
{
"utterance": "副驾有点热,有点闷",
"provider": "local",
"conversation_id": "conv-head-unit-display-sync",
"vehicle_id": "denza_n7",
"execution_mode": "dry_run",
"client_type": "head_unit",
},
ensure_ascii=False,
).encode("utf-8"),
)
with self.opener.open(task_request, timeout=3) as response:
task_result = json.loads(response.read().decode("utf-8"))
peripheral = {
"id": "cap-usb-light-on",
"device_id": "usb-light-1",
"device_name": "USB 氛围灯条",
"device_type": "light",
"type_label": "灯光外设",
"action": "开启灯带",
}
extension_request = urllib.request.Request(
f"{self.base_url}/api/extension/display-sync",
method="POST",
headers={"Content-Type": "application/json"},
data=json.dumps(
{"device": {"id": "usb-light-1"}, "peripherals": [peripheral]},
ensure_ascii=False,
).encode("utf-8"),
)
with self.opener.open(extension_request, timeout=3) as response:
extension_result = json.loads(response.read().decode("utf-8"))
with self.opener.open(
f"{self.base_url}/api/vehicle-display-sync", timeout=3
) as response:
snapshot = json.loads(response.read().decode("utf-8"))
self.assertEqual(task_result["status"], "succeeded")
self.assertTrue(extension_result["success"])
self.assertEqual(snapshot["task"]["utterance"], "副驾有点热,有点闷")
self.assertEqual(snapshot["task"]["result"]["request_id"], task_result["request_id"])
self.assertEqual(snapshot["extension"]["catalog"], [peripheral])
self.assertEqual(snapshot["extension"]["last_action"]["type"], "hardware_detected")
self.assertIn("extension_health", snapshot)
def test_static_console_and_runtime_endpoint(self) -> None:
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-82", timeout=3) as response:
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-85", timeout=3) as response:
app_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/gesture-control.js?v=android-pad-7", timeout=3
f"{self.base_url}/gesture-control.js?v=android-pad-8", timeout=3
) as response:
gesture_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/console-v2.js?v=desktop-refactor-36", timeout=3
f"{self.base_url}/console-v2.js?v=sync-layout-11", timeout=3
) as response:
console_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/console-v2.css?v=desktop-refactor-107", timeout=3
f"{self.base_url}/console-v2.css?v=sync-layout-11", timeout=3
) as response:
console_styles = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-evolution.js?v=seat-weighing-2", timeout=3
) as response:
evolution_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-evolution.css?v=seat-weighing-5", timeout=3
) as response:
evolution_styles = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-extension.js?v=extension-21", timeout=3
f"{self.base_url}/self-extension.js?v=sync-layout-11", timeout=3
) as response:
extension_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-extension.css?v=extension-19", timeout=3
f"{self.base_url}/self-extension.css?v=sync-layout-11", timeout=3
) as response:
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
with self.opener.open(
@@ -231,75 +275,48 @@ operations:
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
self.assertNotIn("透明车体 · 实体设备", html)
self.assertIn('src="/app.js?v=dual-seat-82"', html)
self.assertIn('src="/app.js?v=dual-seat-85"', html)
self.assertIn('href="/styles.css?v=dual-seat-9"', html)
self.assertIn('src="/gesture-control.js?v=android-pad-7"', html)
self.assertIn('src="/gesture-control.js?v=android-pad-8"', html)
self.assertNotIn('class="vehicle-system-hud"', html)
self.assertIn('src="/console-v2.js?v=desktop-refactor-36"', html)
self.assertIn('href="/console-v2.css?v=desktop-refactor-107"', html)
self.assertIn('src="/self-evolution.js?v=seat-weighing-2"', html)
self.assertIn('href="/self-evolution.css?v=seat-weighing-5"', html)
self.assertIn('src="/self-extension.js?v=extension-21"', html)
self.assertIn('href="/self-extension.css?v=extension-19"', html)
self.assertIn('src="/console-v2.js?v=sync-layout-11"', html)
self.assertIn('href="/console-v2.css?v=sync-layout-11"', html)
self.assertIn('src="/self-extension.js?v=sync-layout-11"', html)
self.assertIn('href="/self-extension.css?v=sync-layout-11"', html)
self.assertNotIn("self-calibration", html)
self.assertNotIn("self-repair", html)
self.assertNotIn("self-evolution", html)
self.assertNotIn("滚动查看更多", html)
self.assertIn('aria-label="执行车舱控制指令"', html)
self.assertIn('sessionStorage.getItem("cognitive-agent.demo-role")', console_script)
self.assertNotIn('"admin"', console_script)
self.assertNotIn("v2RoleSelect", console_script)
self.assertIn('id="v2SidebarToggle"', console_script)
self.assertIn('id = "v2ModuleSwitcher"', console_script)
self.assertIn(
'panelHeading.classList.add("v2-operation-heading", "v2-module-heading")',
console_script,
)
self.assertIn('kicker.textContent = "SELF-ORCHESTRATION"', console_script)
self.assertIn(
self.assertIn('kicker.textContent = "REMOTE ORCHESTRATION · LIVE SYNC"', console_script)
self.assertIn('fetch("/api/vehicle-display-sync"', console_script)
self.assertIn('document.body.classList.add("v2-passive-display", "v2-interactive-sync")', console_script)
self.assertIn('input.readOnly = false', console_script)
self.assertIn('function buildTwinDeviceInteractions()', console_script)
self.assertIn('id="v2SyncLogList"', console_script)
self.assertIn('data-panel-toggle="devices"', console_script)
self.assertIn('data-panel-toggle="extensions"', console_script)
self.assertIn('id="vehicleExtensionSync"', console_script)
self.assertNotIn(
'if (ui.activeModule === "self-extension" && ui.role !== "user")',
console_script,
)
self.assertIn(
'if (ui.activeModule === "self-extension" && role !== "user" && !allowModuleTransition)',
self.assertNotIn(
'if (ui.activeModule === "self-extension" && role !== "user"',
console_script,
)
self.assertIn('label: "自编排", available: true', console_script)
self.assertIn('id: "self-calibration"', console_script)
self.assertIn('label: "自标定"', console_script)
self.assertLess(
console_script.index('label: "自标定"'),
console_script.index('label: "自编排"'),
)
self.assertIn("adminOnly: true", console_script)
self.assertIn(
"$('.v2-module-tab[data-module=\"self-calibration\"]')",
console_script,
)
self.assertIn('label: "自扩展", available: true', console_script)
self.assertIn('aria-label="自扩展面向所有登录用户"', console_script)
self.assertIn('511工程-智能座舱', console_script)
self.assertIn('网页控制链路', console_script)
self.assertIn('<span>Hi,</span><strong>user</strong>', console_script)
self.assertIn('id: "self-repair"', console_script)
self.assertIn('label: "自修复"', console_script)
self.assertIn('id: "self-evolution", label: "自进化", available: true', console_script)
self.assertIn(
'window.CognitiveAgentSelfEvolution?.mount(mainPanel)',
console_script,
)
self.assertIn('caption.textContent = evolutionMode ? "进化视图" : "控制方式"', console_script)
self.assertIn('{ label: "座椅称重进化", icon: "scale"', console_script)
self.assertIn('{ label: "系统能力日志", icon: "scroll-text"', console_script)
self.assertIn('aria-label", "自进化 · 座椅称重个性化模型进化"', evolution_script)
self.assertIn('SELF-EVOLUTION · SEAT WEIGHING', evolution_script)
self.assertIn('座椅称重模型进化', evolution_script)
self.assertIn('系统能力日志', evolution_script)
self.assertIn('data-family-model="${model.id}"', evolution_script)
self.assertIn('用户 A', evolution_script)
self.assertIn('用户 B', evolution_script)
self.assertIn('用户 C', evolution_script)
self.assertIn('访客', evolution_script)
self.assertIn('退化保护已触发', evolution_script)
self.assertIn('本次未参与发布', evolution_script)
self.assertIn('演示进化过程', evolution_script)
self.assertIn('家庭体脂秤', evolution_script)
self.assertIn('目标误差', evolution_script)
self.assertIn('["self-orchestration", "self-extension", "self-evolution"]', console_script)
self.assertIn('roleSelect.disabled = ui.activeModule === "self-repair"', console_script)
self.assertIn('["self-orchestration", "self-extension"]', console_script)
self.assertNotIn('label: "自诊断"', console_script)
self.assertIn('<small>手势模型</small>', console_script)
self.assertIn('id="v2GestureModelValue">MediaPipe GR</strong>', console_script)
@@ -316,13 +333,12 @@ operations:
self.assertIn('const dryRunButton = $(\'[data-execution-mode="dry_run"]\')', console_script)
self.assertIn('if (!dryRunButton.classList.contains("active")) dryRunButton.click()', console_script)
self.assertIn('userTargetText.textContent = "执行目标:虚拟 CAN"', console_script)
self.assertIn("真实台架仅由管理员显式启用", console_script)
self.assertIn("user 固定使用虚拟 CAN 安全执行环境", console_script)
self.assertNotIn("user 演示固定使用真实台架", console_script)
self.assertIn('grid-template-columns: clamp(245px, 18.5vw, 310px)', console_styles)
self.assertIn('.v2-control-switch {', console_styles)
self.assertIn('.v2-module-switcher {', console_styles)
self.assertIn('grid-template-columns: repeat(5, minmax(0, 1fr))', console_styles)
self.assertIn('[data-module="self-evolution"] .v2-self-evolution', evolution_styles)
self.assertIn('grid-template-columns: repeat(2, minmax(0, 1fr))', console_styles)
self.assertIn(
'.console-v2 .v2-module-heading.v2-module-heading h1',
console_styles,
@@ -334,22 +350,6 @@ operations:
'grid-template-rows: 36px clamp(236px, 33dvh, 246px) minmax(0, 1fr)',
console_styles,
)
self.assertIn(
'[data-module="self-calibration"] .v2-self-calibration',
console_styles,
)
self.assertIn(
'[data-module="self-repair"] .v2-self-repair',
console_styles,
)
self.assertIn('.v2-evolution-version.active', evolution_styles)
self.assertIn('[data-evolution-scope="admin"]', evolution_styles)
self.assertIn('.v2-weighing-workspace', evolution_styles)
self.assertIn('.v2-family-model.active', evolution_styles)
self.assertIn('.v2-personal-line', evolution_styles)
self.assertIn('.v2-linkage-guard', evolution_styles)
self.assertIn('.v2-parameter-grid', evolution_styles)
self.assertIn('[data-demo-role="admin"] [data-evolution-scope="admin"]', evolution_styles)
self.assertIn(
'color-mix(in srgb, var(--v2-accent-soft) 86%, white)',
console_styles,
@@ -362,14 +362,13 @@ operations:
self.assertIn('background: color-mix(in srgb, var(--v2-accent) 30%, var(--v2-accent-soft))', console_styles)
self.assertIn('width: clamp(48px, 45%, 72px)', console_styles)
self.assertIn('font-weight: 800', console_styles)
self.assertIn('width: clamp(300px, 28vw, 360px)', console_styles)
self.assertIn('white-space: nowrap', console_styles)
self.assertIn('.v2-gesture-model-field', console_styles)
self.assertIn('.v2-inline-gesture .v2-camera-toggle', console_styles)
self.assertIn('.v2-inline-gesture .v2-gesture-action-rail', console_styles)
self.assertIn('grid-template-rows: 42px 10px 42px', console_styles)
self.assertIn('background: var(--v2-accent);', console_styles)
self.assertIn('[data-demo-role="user"] .v2-inline-gesture #gestureArmButton svg', console_styles)
self.assertNotIn("gestureArmButton", console_styles)
self.assertIn('.v2-inline-gesture #gestureStopButton', console_styles)
self.assertIn('.v2-inline-gesture .v2-gesture-result-stack', console_styles)
self.assertIn('.v2-inline-gesture .v2-gesture-execution-slot', console_styles)
@@ -380,7 +379,7 @@ operations:
self.assertIn('.v2-edge-fade-right', console_styles)
self.assertIn('.mode-group-divider', console_styles)
self.assertIn('.joint-twin-shell > .cabin-band', console_styles)
self.assertIn('[data-demo-role="user"] .v2-page-index', console_styles)
self.assertIn('.console-v2 .v2-page-index', console_styles)
self.assertIn('.joint-twin-shell .twin-readout', console_styles)
self.assertIn('.joint-seat-panel .thermal-states', console_styles)
self.assertIn('.joint-twin-shell .domain-readouts', console_styles)
@@ -421,7 +420,7 @@ operations:
self.assertIn("function styleReferencePassiveGlass", app_script)
self.assertIn("function styleReferencePassiveRearExterior", app_script)
self.assertIn('twinRole: "reference_passive_rear_bumper"', app_script)
self.assertIn('"cognitive-agent:demo-role-change"', console_script)
self.assertNotIn('"cognitive-agent:demo-role-change"', console_script)
self.assertIn('resultStack.id = "v2GestureResultStack"', console_script)
self.assertIn('executionSlot.id = "v2GestureExecutionSlot"', console_script)
self.assertIn('statusTarget.append(requestStatus)', console_script)
@@ -450,9 +449,9 @@ operations:
self.assertIn('healthState.message || "GSCU1B 尚未连接"', extension_script)
self.assertIn('window.addEventListener("pageshow"', extension_script)
self.assertIn('[data-module="self-extension"]', extension_styles)
self.assertIn('[data-module="self-extension"] .v2-role-select', extension_styles)
self.assertNotIn('[data-module="self-extension"] .v2-role-select', extension_styles)
self.assertIn('.v2-extension-composer .primary-action {', extension_styles)
self.assertIn('--v2-accent: #0e7773', extension_styles)
self.assertNotIn('--v2-accent:', extension_styles)
self.assertIn('.v2-sidebar-collapsed[data-module="self-extension"] .v2-extension-sidebar', extension_styles)
self.assertIn('.v2-extension-capability span {', extension_styles)
self.assertIn('.v2-extension-parameter-list > span {', extension_styles)
@@ -482,14 +481,17 @@ operations:
)
self.assertIn("const stageHasLayout = () =>", app_script)
self.assertIn("window.requestAnimationFrame(initSeatTwin)", app_script)
self.assertIn('client_type: "web_console"', app_script)
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
self.assertIn('"cognitive-agent:module-change"', app_script)
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v120"', service_worker)
self.assertIn('"/self-evolution.js?v=seat-weighing-2"', service_worker)
self.assertIn('"/self-evolution.css?v=seat-weighing-5"', service_worker)
self.assertIn(
'"/assets/models/n7_seat.glb?v=e7f50f03"', service_worker
)
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v163"', service_worker)
self.assertIn('"/assets/pets/self-extension-cat.png?v=3"', service_worker)
self.assertIn(".then(() => self.skipWaiting())", service_worker)
self.assertNotIn("const cached = await caches.match(request)", service_worker)
self.assertNotIn("self-evolution", service_worker)
self.assertNotIn("self-calibration", service_worker)
self.assertNotIn("self-repair", service_worker)
self.assertNotIn("n7_seat.glb", service_worker)
self.assertIn("hardware_access_required", app_script)
self.assertIn('button.setAttribute("aria-label", "关闭摄像头")', gesture_script)
self.assertIn("function refreshCameraAvailability", gesture_script)
@@ -499,9 +501,8 @@ operations:
self.assertIn('case "NotAllowedError":', gesture_script)
self.assertIn('case "NotReadableError":', gesture_script)
self.assertIn('case "OverconstrainedError":', gesture_script)
self.assertIn("function sendSimulatedGesture()", gesture_script)
self.assertIn('document.body.dataset.demoRole !== "admin"', gesture_script)
self.assertIn("function syncGestureDebugRole", gesture_script)
self.assertNotIn("sendSimulatedGesture", gesture_script)
self.assertNotIn("gestureDebug", gesture_script)
self.assertIn("HARDWARE_REQUEST_TIMEOUT_MS = 15 * 60 * 1000", app_script)
self.assertIn(
"requestTimeoutForExecutionMode(state.executionMode)", app_script
@@ -695,11 +696,11 @@ operations:
def test_self_extension_vehicle_success_contract_is_strict(self) -> None:
with self.opener.open(
f"{self.base_url}/self-extension.js?v=extension-21", timeout=3
f"{self.base_url}/self-extension.js?v=extension-44", timeout=3
) as response:
extension_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/app.js?v=dual-seat-82", timeout=3
f"{self.base_url}/app.js?v=dual-seat-83", timeout=3
) as response:
app_script = response.read().decode("utf-8")
+12 -67
View File
@@ -130,73 +130,18 @@ class TestSelfCalibrationApi:
assert reset["transport"]["state"] == "disconnected"
assert [axis["permille"] for axis in reset["axes"]] == [0, 0, 0, 0]
def test_frontend_and_original_model_are_served_by_existing_service(self) -> None:
def test_frontend_and_original_model_are_removed(self) -> None:
with urllib.request.urlopen(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-calibration.js?v=calibration-8",
timeout=3,
) as response:
script = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-calibration.css?v=calibration-12",
timeout=3,
) as response:
styles = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/console-v2.js?v=desktop-refactor-31",
timeout=3,
) as response:
console_script = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/assets/models/n7_seat.glb",
timeout=3,
) as response:
model = response.read()
content_type = response.headers["Content-Type"]
content_security_policy = response.headers["Content-Security-Policy"]
unversioned_cache_control = response.headers["Cache-Control"]
with urllib.request.urlopen(
f"{self.base_url}/assets/models/n7_seat.glb?v=e7f50f03",
timeout=3,
) as response:
versioned_model_prefix = response.read(4)
versioned_cache_control = response.headers["Cache-Control"]
with pytest.raises(urllib.error.HTTPError) as missing_model:
urllib.request.urlopen(
f"{self.base_url}/assets/models/missing-seat.glb?v=missing",
timeout=3,
)
assert "self-calibration" not in html
assert "vendor/mediamtx" not in html
assert 'href="/self-calibration.css?v=calibration-12"' in html
assert 'src="/self-calibration.js?v=calibration-8"' in html
assert 'data-calibration-page="live"' in script
assert 'data-calibration-view="live"' in script
assert 'data-calibration-page="hall"' not in script
assert 'data-calibration-page="stereo"' not in script
assert "座椅与双目联合标定" in script
assert 'class="v2-module-heading"' in script
assert 'SELF-CALIBRATION' in script
assert 'padding: var(--v2-module-title-block) 0 0' in styles
assert 'margin-inline: var(--v2-module-title-inline)' in styles
assert "实时座椅姿态" in script
assert "四轴实时控制" in script
assert "ZED 实时画面" in script
assert "双目位姿数据" not in script
assert 'class="v2-calibration-panel v2-calibration-mqtt-panel"' not in script
assert "grid-template-columns: minmax(420px, 1.06fr) minmax(380px, 0.94fr)" in styles
assert "标定仿真台架" in script
assert "X-Cognitive-Agent-Role" in script
assert "--v2-sidebar: #dae3f5" in styles
assert "--v2-sidebar-deep: #b6c7ea" in styles
assert "--v2-accent-strong: #1e386b" in styles
assert '["self-calibration", "self-repair"].includes(module)' in console_script
assert 'setRole("admin", { allowModuleTransition: true })' in console_script
assert len(model) == 845_992
assert versioned_model_prefix == model[:4]
assert content_type.startswith("model/gltf-binary")
assert unversioned_cache_control == "no-cache"
assert versioned_cache_control == "public, max-age=31536000, immutable"
assert missing_model.value.code == 404
assert "img-src 'self' data: blob:;" in content_security_policy
assert "connect-src 'self' blob:;" in content_security_policy
for path in (
"/self-calibration.js",
"/self-calibration.css",
"/vendor/mediamtx/reader.js",
"/assets/models/n7_seat.glb",
):
with pytest.raises(urllib.error.HTTPError) as missing_asset:
urllib.request.urlopen(f"{self.base_url}{path}", timeout=3)
assert missing_asset.value.code == 404
+11 -61
View File
@@ -142,74 +142,24 @@ class TestSelfRepairApi:
assert result["ok"] is True
assert result["self_test"]["result"] == "PASS"
def test_frontend_assets_and_role_transition_are_served(self) -> None:
def test_frontend_assets_and_role_transition_are_removed(self) -> None:
with urllib.request.urlopen(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-repair.js?v=repair-21", timeout=3
) as response:
script = response.read().decode("utf-8").replace("\r\n", "\n")
with urllib.request.urlopen(
f"{self.base_url}/self-repair.css?v=repair-21", timeout=3
) as response:
styles = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-repair-seat3d.js?v=repair-21", timeout=3
) as response:
seat3d = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/console-v2.js?v=desktop-refactor-31", timeout=3
) as response:
console_script = response.read().decode("utf-8")
assert 'href="/self-repair.css?v=repair-21"' in html
assert 'src="/self-repair.js?v=repair-21"' in html
assert 'class="v2-module-heading"' in script
assert 'SELF-REPAIR' in script
assert 'padding: var(--v2-module-title-block) 0 0' in styles
assert 'margin-inline: var(--v2-module-title-inline)' in styles
assert 'src="/self-repair-seat3d.js?v=repair-21"' in html
assert "量产故障注入" in script
assert "运行与自修复日志" in script
assert "启动座椅运行" in script
assert "开始正常任务" not in script
assert 'id="repairSeatCanvas"' in script
assert 'id="repairSeatViewer"' in script
assert "self-repair-seat-reset" in script
assert "seat-healing-state" in script
assert 'id="repairConfirmDialog"' in script
assert "confirmAction(" in script
assert "window.confirm" not in script
assert 'hardware.link?.adapter === "OK"' in script
assert 'PASSED: ["21针通过", "online"]' in script
assert "connectorRows" in script
assert "Polling refreshes data only" in script
assert "renderRecords();\n showView(activeView);" not in script
assert 'let latest = fallbackState();' in script
assert 'renderAll();\n window.lucide?.createIcons' in script
assert '"WDT复位后失能"' in script
assert "--repair-deep-blue" in styles
assert '.console-v2[data-module="self-repair"] .section-kicker' in styles
assert 'grid-template-areas: "twin cases logs"' in styles
assert ".v2-repair-event-stream" in styles
assert "background: rgba(255, 255, 255, 0.94)" in styles
assert "border-color: color-mix(in srgb, var(--v2-accent) 18%, white)" in styles
assert ".v2-repair-toolbar button.primary:hover:not(:disabled)" in styles
assert "color: #fff;" in styles
assert "seat-healing-state" in seat3d
assert "function backrestShape" in seat3d
assert "new THREE.CapsuleGeometry" in seat3d
assert 'cssThemeColor("--v2-accent", "#315eaa")' in seat3d
assert 'viewer.addEventListener("pointerdown"' in seat3d
assert 'window.addEventListener("pointermove"' in seat3d
assert "window.addEventListener(resetEvent" in seat3d
assert "function viewerHasLayout(viewer)" in seat3d
assert "function deferSeat3dUntilVisible(viewer, options)" in seat3d
assert "if (!viewerHasLayout(viewer))" in seat3d
assert 'window.addEventListener("cognitive-agent:module-change"' in seat3d
assert 'canvas.addEventListener("webglcontextlost"' in seat3d
assert '["self-calibration", "self-repair"].includes(module)' in console_script
assert 'roleSelect.disabled = ui.activeModule === "self-repair"' in console_script
assert "self-repair" not in html
assert '"admin"' not in console_script
for path in (
"/self-repair.js",
"/self-repair.css",
"/self-repair-seat3d.js",
):
with pytest.raises(urllib.error.HTTPError) as missing_asset:
urllib.request.urlopen(f"{self.base_url}{path}", timeout=3)
assert missing_asset.value.code == 404
def test_self_repair_bridge_uses_the_backend_runtime_directory() -> None: