From f87289b1291ffd96a50ccf83a8959d8b0d6c2ef3 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 1 Aug 2026 22:25:10 +0200 Subject: [PATCH] fix: validate typed frontend startup in Chromium --- .../assets/web/generated/app.js | 8 +-- .../trx-frontend-http/frontend/src/README.md | 4 +- .../trx-frontend-http/frontend/src/app.ts | 6 +-- .../frontend/src/plugin-loader.ts | 2 +- .../frontend/tests/browser-smoke.mjs | 49 ++++++++++++++++++- 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js index 99aabae9..e388c310 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/generated/app.js @@ -1590,7 +1590,7 @@ async function loadPlugins(group) { for (const path of pluginGroups[group]) await loadPlugin(path); } async function loadEagerPlugins() { - await Promise.all(["digital-modes", "map-data", "bookmarks", "settings"].map(loadPlugins)); + await Promise.all(["digital-modes", "bookmarks", "settings"].map(loadPlugins)); } async function loadPluginsForTab(tab) { await loadPlugins(tab); @@ -1892,7 +1892,7 @@ var centerFreqEl = requiredElement("center-freq"); var wavelengthEl = requiredElement("wavelength"); var sigStrengthEl = requiredElement("sig-strength"); var modeEl = requiredElement("mode"); -var bandLabel = requiredElement("band-label"); +var bandLabel = document.getElementById("band-label"); var powerBtn = requiredElement("power-btn"); var powerHint = requiredElement("power-hint"); var vfoPicker = requiredElement("vfo-picker"); @@ -1912,7 +1912,7 @@ var loadingEl = requiredElement("loading"); var contentEl = requiredElement("content"); var serverSubtitle = requiredElement("server-subtitle"); var rigSubtitle = requiredElement("rig-subtitle"); -var ownerSubtitle = requiredElement("owner-subtitle"); +var ownerSubtitle = document.getElementById("owner-subtitle"); var locationSubtitle = requiredElement("location-subtitle"); var loadingTitle = requiredElement("loading-title"); var loadingSub = requiredElement("loading-sub"); @@ -2241,7 +2241,7 @@ function setTheme(theme) { invalidateBookmarkColors(); } function invalidateBookmarkColors() { - const bookmarks = window.trx.modules.bookmarks; + const bookmarks = window.trx?.modules.bookmarks; if (!bookmarks) return; bookmarks.invalidateColors(); void getComputedStyle(document.documentElement).getPropertyValue("--bg"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/README.md b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/README.md index edd71e05..3b997837 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/README.md +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/README.md @@ -26,8 +26,8 @@ npm run verify-generated `npm run verify-generated` regenerates Rust wire contracts and browser bundles, then rejects drift from the committed files. The browser smoke test needs a -local Chromium-family executable; set `CHROMIUM_PATH` when it is not installed -at a conventional system path. +local Chromium-family executable; set `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` +when it is not installed at `/usr/bin/chromium`. Cargo consumes committed output under `../assets/web/generated`. It never invokes Node.js, installs packages, or accesses the network. After changing a diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts index 73ef96a9..490b2218 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/app.ts @@ -673,7 +673,7 @@ const centerFreqEl = requiredElement("center-freq"); const wavelengthEl = requiredElement("wavelength"); const sigStrengthEl = requiredElement("sig-strength"); const modeEl = requiredElement("mode"); -const bandLabel = requiredElement("band-label"); +const bandLabel = document.getElementById("band-label"); const powerBtn = requiredElement("power-btn"); const powerHint = requiredElement("power-hint"); const vfoPicker = requiredElement("vfo-picker"); @@ -693,7 +693,7 @@ const loadingEl = requiredElement("loading"); const contentEl = requiredElement("content"); const serverSubtitle = requiredElement("server-subtitle"); const rigSubtitle = requiredElement("rig-subtitle"); -const ownerSubtitle = requiredElement("owner-subtitle"); +const ownerSubtitle = document.getElementById("owner-subtitle"); const locationSubtitle = requiredElement("location-subtitle"); const loadingTitle = requiredElement("loading-title"); const loadingSub = requiredElement("loading-sub"); @@ -1063,7 +1063,7 @@ function setTheme(theme: string) { // Recolour bookmark chips after any palette/theme change (setTheme or setStyle). function invalidateBookmarkColors() { - const bookmarks = window.trx.modules.bookmarks; + const bookmarks = window.trx?.modules.bookmarks; if (!bookmarks) return; bookmarks.invalidateColors(); // Force the browser to recalculate styles so getComputedStyle reads new values. diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts index 2af00620..78894eb6 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/src/plugin-loader.ts @@ -37,7 +37,7 @@ async function loadPlugins(group: string): Promise { } export async function loadEagerPlugins(): Promise { - await Promise.all(["digital-modes", "map-data", "bookmarks", "settings"].map(loadPlugins)); + await Promise.all(["digital-modes", "bookmarks", "settings"].map(loadPlugins)); } export async function loadPluginsForTab(tab: string): Promise { diff --git a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs index bd242766..7bc84060 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs +++ b/src/trx-client/trx-frontend/trx-frontend-http/frontend/tests/browser-smoke.mjs @@ -17,6 +17,49 @@ const jsonRoutes = new Map([ ["/auth/session", { authenticated: true, role: "control", auth_disabled: true }], ["/decoders", []], ["/rigs", { rigs: [], active_remote: null }], + ["/status", { + info: { + manufacturer: "Smoke", + model: "Fixture", + revision: "1", + access: { Tcp: { addr: "127.0.0.1:0" } }, + capabilities: { + min_freq_step_hz: 1, + supported_bands: [], + supported_modes: ["FM"], + num_vfos: 1, + lock: false, + lockable: false, + attenuator: false, + preamp: false, + rit: false, + rpt: false, + split: false, + tx: false, + tx_limit: false, + vfo_switch: false, + filter_controls: false, + signal_meter: false, + }, + }, + status: { freq: { hz: 100_000_000 }, mode: "FM", tx_en: false, vfo: null, tx: null, rx: null, lock: null }, + band: null, + enabled: true, + initialized: true, + cw_auto: false, + cw_wpm: 20, + cw_tone_hz: 700, + aprs_decode_enabled: false, + hf_aprs_decode_enabled: false, + cw_decode_enabled: false, + ft8_decode_enabled: false, + ft4_decode_enabled: false, + ft2_decode_enabled: false, + wspr_decode_enabled: false, + lrpt_decode_enabled: false, + wefax_decode_enabled: false, + recorder_enabled: false, + }], ["/bandplan.json", {}], ["/api/recorder/status", []], ["/api/recorder/files", []], @@ -80,10 +123,12 @@ const executablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH const browser = await chromium.launch({ executablePath, headless: true, args: ["--no-sandbox"] }); const page = await browser.newPage(); const runtimeErrors = []; -page.on("pageerror", (error) => runtimeErrors.push(error.message)); +page.on("pageerror", (error) => runtimeErrors.push(error.stack ?? error.message)); try { - await page.goto(`http://127.0.0.1:${address.port}/`, { waitUntil: "networkidle" }); + await page.goto(`http://127.0.0.1:${address.port}/`, { waitUntil: "domcontentloaded" }); + await page.waitForTimeout(500); + assert.deepEqual(runtimeErrors, []); await page.locator("#content").waitFor({ state: "visible" }); assert.equal(await page.locator("#auth-gate").isVisible(), false); assert.equal(await page.locator("#tab-main").isVisible(), true);