Files
WiFiManager/test/portal-harness

Portal browser harness

This test fixture starts only the WiFiManager captive portal. It intentionally has no station credentials, MQTT configuration, DeviceFramework dependency, or application logic.

Use it through the repository runner so a secondary Wi-Fi adapter is explicitly selected and protected from becoming the host default route:

./tools/portal-hardware run \
  --platform esp8266 \
  --port /dev/serial/by-id/... \
  --client-interface wlx...

The ESP8266 portal SSID is WM Test Harness ESP8266; the ESP32 SSID is WM Test Harness ESP32. Both use default1 exclusively for local development tests.

The runner cleans up only the temporary connection it creates on the named secondary interface. It refuses to run if that interface is the system default route.

NetworkManager authority is a host prerequisite, not a fixture secret. A GUI Polkit session may authorize the adapter directly; a headless/SSH invocation validates sudo before flashing, then elevates only the generated portal connection actions. Leave the runner itself unprivileged so its private state and browser artifacts remain owned by the developer. See docs/TESTING.md for the WM_NMCLI_AUTH options.

A/B portal OTA fixture

The same fixture has dedicated *_ota_a and *_ota_b PlatformIO environments for the physical portal HTTP OTA test harness. A and B differ only by a compiled marker served from the fixture-only /api/test/firmware-marker endpoint and an immutable serial boot marker. The test harness requires serial A, updater start/completion, then serial B, so it proves a B boot without trusting saved portal values, EEPROM, or a filename.

./tools/portal-hardware ota \
  --platform esp32 \
  --port /dev/serial/by-id/... \
  --client-interface wlx...

ESP8266 explicitly uses eagle.flash.4m1m.ld. ESP32 uses the tracked two-slot partitions/esp32_ota_4m_no_fs.csv layout on the maintained Arduino-ESP32 3.3.11 fixture lane. Both are 4 MB layouts. The runner builds and preserves A and B before it touches the board, validates the matching ESP32 slots (or the live ESP8266 updater capacity), then erases the explicitly selected test board before serial-flashing A. A successful run leaves B installed in the portal-only fixture. It requires Python PySerial and retains a passive, no-reset serial-ota.log in the private run artifact directory for both success and failure diagnosis.