test(e2e): real-device Playwright messaging suite (#1121)
* fix(connections): connect to the just-added connection via the live store addConnectionAndConnect() adds a connection and then connects to it in the same tick, but connect() resolved the id against the memoized `connections` closure, which is stale until the hook re-renders. The just-added id was therefore reported as an unknown connection id and Save silently never connected any HTTP/Serial/Bluetooth device. Read savedConnections from useDeviceStore.getState() so the lookup always sees the live store. * test(e2e): real-device Playwright messaging suite Drives the actual web app in Chromium against real meshtasticd firmware over the HTTP phone API and verifies text messaging in both directions across a two-node mesh. Nodes mesh over the firmware's built-in UDP multicast (224.0.0.69) with no MQTT/relay; distinct node numbers, real encryption. - Default backend: two Docker meshtasticd sim nodes (daily-debian). The same specs run against physical hardware via E2E_DEVICE_MODE=hardware. - An off-browser Python meshtastic peer (e2e/peer/peer.py) drives/asserts the non-browser node over the TCP phone API, mirroring firmware mcp-server tests. - Coverage: connect over HTTPS, mesh->web receive, web->mesh send. Direct messages are fixme'd (see below). CI workflow runs it on Linux. Bugs surfaced by the suite: - Fixed (prior commit): connect-on-save never connected (stale-closure id lookup in useConnections). - Not fixed: apps/web/src/core/subscriptions.ts throws 'ReferenceError: nodeDB is not defined' on every device-metrics telemetry packet (the #1050 migration removed that store); caught per-packet, so messaging still works. - Not fixed: direct messages are blocked by a PKI 'Keys Mismatch' (the SDK's stored peer public key != the key presented during NodeInfo exchange), seen even with fresh sim nodes. * test(e2e): address Copilot review feedback - waitForTcp(): destroy the probe socket on the error path so repeated connection failures don't accumulate sockets/FDs across the retry loop. - Don't remove the mesh containers in Playwright globalTeardown in CI — it raced the workflow's failure log capture. Teardown is now gated on E2E_DOCKER_DOWN only; CI dumps device logs on failure and tears the mesh down in a final always() workflow step. * fix(sdk): fold device-metrics telemetry into nodes apps/web/src/core/subscriptions.ts called nodeDB.addDeviceMetrics() on every device-metrics telemetry packet, but the #1050 migration removed that store — so it threw 'ReferenceError: nodeDB is not defined' on each telemetry packet (caught per-packet by the SDK's HandleFromRadio, so messaging still worked but the error spammed the console). Route device metrics into the SDK NodesClient via onTelemetryPacket instead — mirroring the existing position handler; the Node domain already carries a deviceMetrics field — and drop the dead app-side handler. Adds a NodesClient test covering the fold. * docs(e2e): accurate DM root cause + bug status The direct-message fixme is a simulator limitation, not a web-app bug: the keyless meshtasticd sim nodes NAK a DM with NO_CHANNEL (routing error 6) — no Curve25519 keypair is provisioned/shared, and current firmware can't deliver a direct message without a per-node key / decryptable channel. The app surfaces this correctly (key-refresh dialog). Re-enable against hardware or once the sim provisions keys. Also: mark the nodeDB telemetry bug fixed and note the CI teardown change. * docs(e2e): precise DM root cause (firmware/sim PKI) Followed up on the suggestion to provision keys in config.security: the keys ARE settable and persist (verified via admin), but on the native meshtasticd sim they don't sync to the node's owner / NodeInfo key — owner.public_key stays empty and the node keeps its MAC-derived num — so the two nodes never exchange keys. Combined with the firmware refusing non-PKI DMs ('Unknown public key for destination ... refusing to send legacy DM'), the DM is NAK'd with NO_CHANNEL. A firmware/sim limitation; DMs work on real hardware. Spec stays fixme. * docs(e2e): definitive DM root cause (SimRadio PKC payload limit) Per the steer to research the firmware: PKI keygen is gated on a set LoRa region (NodeDB.cpp:3051) and the sim boots region-UNSET — setting lora.region via admin DOES make the nodes generate and exchange keys (verified both ways). But a PKI-encrypted DM still can't traverse the SimRadio: the PKC overhead exceeds its payload limit ('Payload size larger than compressed message allows! Send empty payload'), so the packet is truncated and the receiver NAKs NO_CHANNEL ('No suitable channel found for decoding, hash 0x0'). The firmware skips PKC under --sim (Router.cpp:730) for exactly this reason, but --sim also disables the config-file loading the web app needs, so they're mutually exclusive. DMs work on real hardware; spec stays fixme with this detail. --------- Co-authored-by: Dan Ditomaso <dan.ditomaso@gmail.com>
This commit is contained in:
co-authored by
Dan Ditomaso
parent
a7aa25aa30
commit
ad692eef74
@@ -0,0 +1,43 @@
|
||||
import { peerNodeNum, startPeerRecv } from "../fixtures/peer.ts";
|
||||
import { expect, test } from "../fixtures/test.ts";
|
||||
|
||||
/**
|
||||
* Direct (node-addressed) messaging across the real two-node mesh.
|
||||
*
|
||||
* NOTE: marked fixme — this is a SIMULATOR limitation, not a web-app issue, and
|
||||
* it bottoms out in the firmware's SimRadio:
|
||||
* - DMs go out PKI-encrypted. PKI key generation is gated on a set LoRa region
|
||||
* (NodeDB.cpp:3051 — keygen is skipped while region == UNSET) and the sim
|
||||
* nodes boot region-UNSET. Setting `lora.region` via admin DOES fix key gen
|
||||
* + exchange (verified: both nodes learn each other's public key).
|
||||
* - But a PKI-encrypted DM still can't traverse the SimRadio: the PKC overhead
|
||||
* pushes the packet past the SimRadio payload limit ("Payload size larger
|
||||
* than compressed message allows! Send empty payload"), so it is truncated
|
||||
* and the receiver can't decode it ("No suitable channel found for decoding,
|
||||
* hash 0x0") -> NO_CHANNEL. The firmware deliberately skips PKC under the
|
||||
* --sim flag (Router.cpp:730) for exactly this reason, but --sim also
|
||||
* disables config-file loading (Webserver/EnableUDP/MAC) that the web app's
|
||||
* HTTP API + UDP mesh require, so the two are mutually exclusive.
|
||||
* The web app behaves correctly (it raises the key-refresh dialog). Broadcast
|
||||
* already covers bidirectional messaging; re-enable against real hardware, where
|
||||
* real LoRa carries PKC fine.
|
||||
*/
|
||||
test.describe("direct messaging over a real two-node mesh", () => {
|
||||
test.beforeEach(async ({ connectionPage, messagesPage, device }) => {
|
||||
await connectionPage.connectHttp({ host: device.host, tls: device.tls });
|
||||
await messagesPage.waitReady();
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
"delivers a direct message from the browser to the peer node (web -> mesh)",
|
||||
async ({ messagesPage }) => {
|
||||
const peerNum = await peerNodeNum();
|
||||
const nonce = `dm-${Date.now()}`;
|
||||
const recv = await startPeerRecv(nonce, { timeout: 60 });
|
||||
await messagesPage.openDirectMessageByNodeNum(peerNum);
|
||||
await messagesPage.send(nonce);
|
||||
const from = await recv.received;
|
||||
expect(from).toBeGreaterThan(0);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user