Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
c0aa75c
Initial plan
Copilot Dec 25, 2025
d8934bb
Add QEMU-based E2E testing infrastructure with Playwright
Copilot Dec 25, 2025
afbe8fc
Complete QEMU-based E2E testing infrastructure
Copilot Dec 25, 2025
98accba
Add implementation summary for QEMU E2E testing
Copilot Dec 25, 2025
314bf7f
Fix QEMU download URL - update to esp-develop-9.2.2-20250817
Copilot Dec 26, 2025
19df216
Fix missing QEMU dependencies - install libSDL2 and verify libraries
Copilot Dec 26, 2025
129a138
Change port forwarding from 80 to 8080 (non-privileged port)
Copilot Dec 26, 2025
e3f25f6
Add exception decoding and crash analysis for QEMU output
Copilot Dec 26, 2025
0bcd288
Switch to ethernet build to avoid WiFi crashes in QEMU
Copilot Dec 26, 2025
79a1d5c
Switch to esp32_4MB_V4_S_eth build with ESP32-POE ethernet config
Copilot Dec 27, 2025
3c9a68f
Document QEMU DHCP networking and add static IP option
Copilot Dec 27, 2025
a957eed
Fix remaining esp32dev references in scripts
Copilot Dec 27, 2025
104e4ec
(experimental) WLED_DISABLE_WIFI
softhack007 Dec 27, 2025
28a0d00
adding [esp32_16MB_V4_M_eth_debug] for QEMU tests
softhack007 Dec 27, 2025
e5dfccb
Switch to esp32_16MB_V4_M_eth_debug build for QEMU tests
Copilot Dec 27, 2025
3a8b123
Configure QEMU for 16MB flash to match debug build
Copilot Dec 27, 2025
9bda207
Add stack trace analysis and manual exception decoder instructions
Copilot Dec 27, 2025
4beb79b
(experimental) enable open_eth for esp-idf
softhack007 Dec 27, 2025
cee5708
Add WLED_QEMU workaround to skip ethernet hardware init
Copilot Dec 27, 2025
dafd718
Add WLED_QEMU WiFi workaround to prevent crashes
Copilot Dec 27, 2025
949d42f
fix WLED_QEMU build
softhack007 Dec 27, 2025
496b5c3
rename esp32_16MB_V4_M_eth_debug -> esp32_16MB_QEMU_debug
softhack007 Dec 27, 2025
d947fdd
allow to set static IP adress at compile time
softhack007 Dec 27, 2025
4b3f5ed
remove confliciting usermods from QEMU build
softhack007 Dec 27, 2025
b99ebed
Add network verification tests and improve logging in CI
Copilot Dec 27, 2025
f79f0bd
QEMU: manually init ethernet and DHCP via esp-idf
softhack007 Dec 27, 2025
6f7138a
script tinkering
softhack007 Dec 27, 2025
451103e
next try
softhack007 Dec 27, 2025
13e9f60
typo fixo
softhack007 Dec 27, 2025
495cd69
keep registering callback in initConnection()
softhack007 Dec 27, 2025
d541c21
let's try with the tasmota platform
softhack007 Dec 27, 2025
c811a0c
back to standard arduino framework
softhack007 Dec 27, 2025
e47b3ef
typo
softhack007 Dec 27, 2025
2dd31c6
let's try a simpler build
softhack007 Dec 27, 2025
ddd148e
QEMU: remove serial protocols
softhack007 Dec 27, 2025
d1d71f7
Fix confusing exit code 1 in QEMU status check
Copilot Dec 27, 2025
aa580c3
disabling some more wifi calls, and script tinkering
softhack007 Dec 27, 2025
6308df9
shorter wait time (1min), less verbose
softhack007 Dec 27, 2025
db57333
minor e2e script optimization
softhack007 Dec 27, 2025
f0ee919
Add enhanced DHCP/network diagnostics and ping tests
Copilot Dec 28, 2025
01a5df1
Fix HTTP server startup by bypassing uninitialized ETH object in QEMU
Copilot Dec 28, 2025
7c1aef6
Apply coderabbitai recommendations: OpenETH driver init, fix WLED_CON…
Copilot Jun 10, 2026
947f959
Add push trigger for feature branch to QEMU E2E workflow
Copilot Jun 10, 2026
be3b2cf
Merge branch 'mdev' into copilot/add-ci-workflow-for-esp32
softhack007 Jun 10, 2026
6b8a2fc
fix compilation error
softhack007 Jun 10, 2026
0885fff
📝 CodeRabbit Chat: Implement requested code changes
coderabbitai[bot] Jun 10, 2026
a49ffac
fix for AI slop
softhack007 Jun 10, 2026
970a289
Merge pull request #368 from MoonModules/coderabbitai/chat/6b8a2fc
softhack007 Jun 10, 2026
b7827cf
Fix OpenETH build: vendor esp_eth_mac_openeth from ESP-IDF v4.4.7 as …
Copilot Jun 10, 2026
5ade91c
align python version with requirements.txt
softhack007 Jun 10, 2026
4b12993
fix: install @playwright/test before browser install in CI workflow
Copilot Jun 10, 2026
d1dc072
fix: test /json/info and /json/state instead of /index.htm in CI
Copilot Jun 10, 2026
4174af8
fix: add missing test:e2e script to package.json
Copilot Jun 10, 2026
9fe5cd8
fix: use correct WLED server routes in Playwright E2E tests
Copilot Jun 10, 2026
b226767
Add JS console log capture as downloadable CI artifacts
Copilot Jun 10, 2026
14d506a
move documentation to e2e-tests folder
softhack007 Jun 10, 2026
be2e5a6
Fix Playwright test title expectations to match actual WLED page titles
Copilot Jun 10, 2026
89157fb
QEMU config update
softhack007 Jun 10, 2026
c57b788
disable simple UI for e2e tests
softhack007 Jun 10, 2026
2a2fd32
Update e2e-tests/fixtures.js
softhack007 Jun 10, 2026
54c1894
e2e test: bypass welcome page
softhack007 Jun 10, 2026
e61c732
move DEMU docs into e2e-tests folder
softhack007 Jun 10, 2026
3ff7128
fix for AI slop
softhack007 Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/scripts/monitor-qemu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""
Monitor QEMU ESP32 serial output and decode exceptions
This script watches the QEMU serial output and uses the ESP32 exception decoder
to translate stack traces into human-readable format.
"""

import sys
import re
import subprocess
import os

def find_elf_file(firmware_dir):
"""Find the ELF file for symbol resolution"""
elf_path = os.path.join(firmware_dir, "firmware.elf")
if os.path.exists(elf_path):
return elf_path
return None

def decode_exception(lines, elf_file):
"""Decode an ESP32 exception using addr2line"""
if not elf_file or not os.path.exists(elf_file):
return None

# Extract addresses from backtrace
addresses = []
for line in lines:
# Look for patterns like: 0x4008xxxx:0x3ffbxxxx
matches = re.findall(r'0x[0-9a-fA-F]{8}', line)
addresses.extend(matches)

if not addresses:
return None

# Use addr2line to decode addresses
try:
# Get the toolchain path from environment or use default
toolchain_prefix = os.environ.get('TOOLCHAIN_PREFIX', 'xtensa-esp32-elf-')
addr2line = f"{toolchain_prefix}addr2line"

cmd = [addr2line, '-e', elf_file, '-f', '-C'] + addresses
result = subprocess.run(cmd, capture_output=True, text=True, timeout=5)

if result.returncode == 0 and result.stdout:
return result.stdout
except Exception as e:
print(f"[Decoder] Error decoding: {e}", file=sys.stderr)

return None

def monitor_output(firmware_dir):
"""Monitor stdin and decode exceptions"""
elf_file = find_elf_file(firmware_dir)

if elf_file:
print(f"[Decoder] Using ELF file: {elf_file}", file=sys.stderr)
else:
print(f"[Decoder] Warning: ELF file not found in {firmware_dir}", file=sys.stderr)
print(f"[Decoder] Exception decoding will not be available", file=sys.stderr)

exception_lines = []
in_exception = False

for line in sys.stdin:
# Print the original line
print(line, end='', flush=True)

# Detect exception start
if 'Guru Meditation Error' in line or 'Backtrace:' in line or 'abort()' in line:
in_exception = True
exception_lines = [line]
print("\n[Decoder] ========== ESP32 EXCEPTION DETECTED ==========", file=sys.stderr)
elif in_exception:
exception_lines.append(line)

# Check if exception block ended
if line.strip() == '' or 'ELF file SHA256' in line or len(exception_lines) > 20:
# Try to decode
decoded = decode_exception(exception_lines, elf_file)
if decoded:
print("\n[Decoder] Decoded stack trace:", file=sys.stderr)
print(decoded, file=sys.stderr)
print("[Decoder] ================================================\n", file=sys.stderr)
else:
print("[Decoder] Could not decode exception (toolchain not available)", file=sys.stderr)
print("[Decoder] ================================================\n", file=sys.stderr)

in_exception = False
exception_lines = []

Comment on lines +64 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Decode pending exception block on EOF.

If input ends before an empty line/marker appears, the captured exception is never decoded.

Suggested fix
     for line in sys.stdin:
         # Print the original line
         print(line, end='', flush=True)
@@
                 in_exception = False
                 exception_lines = []
+
+    # Flush trailing exception block at EOF
+    if in_exception and exception_lines:
+      decoded = decode_exception(exception_lines, elf_file)
+      if decoded:
+        print("\n[Decoder] Decoded stack trace:", file=sys.stderr)
+        print(decoded, file=sys.stderr)
+      else:
+        print("[Decoder] Could not decode trailing exception block", file=sys.stderr)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/monitor-qemu.py around lines 64 - 90, At end of the stdin
loop in monitor-qemu.py, ensure any pending captured exception block is decoded
on EOF: after the for line in sys.stdin loop completes, check if in_exception is
True and exception_lines is non-empty and then call
decode_exception(exception_lines, elf_file) and print the same decoded or
failure messages to stderr (matching the existing prints inside the loop) before
clearing in_exception/exception_lines; reference the in_exception flag,
exception_lines list, decode_exception function and elf_file variable to locate
where to add this EOF-handling logic.

if __name__ == '__main__':
firmware_dir = sys.argv[1] if len(sys.argv) > 1 else '.pio/build/esp32_16MB_QEMU_debug'
monitor_output(firmware_dir)
108 changes: 108 additions & 0 deletions .github/scripts/run-qemu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/bin/bash
# Run WLED firmware in QEMU ESP32
# This script starts QEMU with the compiled firmware and enables network access
#
# Note: QEMU ESP32 emulation has limitations:
# - Not all peripherals are fully emulated (WiFi, I2C, some GPIOs)
# - Some firmware features may crash in QEMU but work on real hardware
# - This is expected behavior for testing web UI functionality

set -e

FIRMWARE_DIR="${1:-.pio/build/esp32_16MB_QEMU_debug}"
QEMU_DIR="${2:-qemu-esp32}"
HTTP_PORT="${3:-8080}" # Default to 8080 (non-privileged port)

if [ ! -d "$FIRMWARE_DIR" ]; then
echo "Error: Firmware directory not found: $FIRMWARE_DIR"
exit 1
fi

if [ ! -f "${QEMU_DIR}/qemu-system-xtensa" ] && [ ! -f "${QEMU_DIR}/bin/qemu-system-xtensa" ]; then
echo "Error: QEMU not found at ${QEMU_DIR}/qemu-system-xtensa or ${QEMU_DIR}/bin/qemu-system-xtensa"
echo "Please run setup-qemu.sh first"
exit 1
fi

# Determine QEMU binary location
if [ -f "${QEMU_DIR}/qemu-system-xtensa" ]; then
QEMU_BIN="${QEMU_DIR}/qemu-system-xtensa"
else
QEMU_BIN="${QEMU_DIR}/bin/qemu-system-xtensa"
fi

# Check for required firmware files
BOOTLOADER="${FIRMWARE_DIR}/bootloader.bin"
PARTITIONS="${FIRMWARE_DIR}/partitions.bin"
FIRMWARE="${FIRMWARE_DIR}/firmware.bin"

if [ ! -f "$BOOTLOADER" ]; then
echo "Error: Bootloader not found: $BOOTLOADER"
exit 1
fi

if [ ! -f "$FIRMWARE" ]; then
echo "Error: Firmware not found: $FIRMWARE"
exit 1
fi

echo "Starting QEMU ESP32 with WLED firmware"
echo "Firmware directory: $FIRMWARE_DIR"
echo "HTTP will be accessible at: http://localhost:${HTTP_PORT}"

# Create a merged flash image as QEMU expects
FLASH_IMAGE="/tmp/wled_flash.bin"
echo "Creating flash image at $FLASH_IMAGE"

# Create a 16MB flash image (0x1000000 bytes) for esp32_16MB_QEMU_debug
dd if=/dev/zero of="$FLASH_IMAGE" bs=1M count=16 2>/dev/null

# Write bootloader at 0x1000
if [ -f "$BOOTLOADER" ]; then
dd if="$BOOTLOADER" of="$FLASH_IMAGE" bs=1 seek=$((0x1000)) conv=notrunc 2>/dev/null
fi

# Write partitions at 0x8000
if [ -f "$PARTITIONS" ]; then
dd if="$PARTITIONS" of="$FLASH_IMAGE" bs=1 seek=$((0x8000)) conv=notrunc 2>/dev/null
fi

# Write firmware at 0x10000
dd if="$FIRMWARE" of="$FLASH_IMAGE" bs=1 seek=$((0x10000)) conv=notrunc 2>/dev/null

echo "Flash image created successfully"

# Run QEMU ESP32
# Note: ESP32 in QEMU has limited peripheral support
# Network configuration uses user-mode networking with port forwarding
# -nic user,model=open_eth,id=lo0,hostfwd=tcp:127.0.0.1:PORT_HOST-:PORT_GUEST # for port forwarding
# -global driver=timer.esp32.timg,property=wdt_disable,value=true # disables TG watchdog timers
echo "Starting QEMU..."
${QEMU_BIN} \
-nographic \
-machine esp32 \
-drive file=${FLASH_IMAGE},if=mtd,format=raw \
-nic user,model=open_eth,id=lo0,hostfwd=tcp::${HTTP_PORT}-:80 \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bind QEMU host-forwarded HTTP port to localhost explicitly.

Line 85 currently forwards on all interfaces (tcp::...), which is broader than needed and contradicts the localhost intent in comments.

Suggested fix
-    -nic user,model=open_eth,id=lo0,hostfwd=tcp::${HTTP_PORT}-:80 \
+    -nic "user,model=open_eth,id=lo0,hostfwd=tcp:127.0.0.1:${HTTP_PORT}-:80" \
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 85-85: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/run-qemu.sh at line 85, Replace the host-forwarding that
binds to all interfaces in the QEMU nic option: locate the nic/user line
containing "hostfwd=tcp::${HTTP_PORT}-:80" and change it to explicitly bind to
localhost, e.g. "hostfwd=tcp:127.0.0.1:${HTTP_PORT}-:80" so the forwarded HTTP
port is only accessible on localhost.

-global driver=timer.esp32.timg,property=wdt_disable,value=true \
-serial mon:stdio &

QEMU_PID=$!
echo "QEMU started with PID: $QEMU_PID"
echo $QEMU_PID > qemu.pid

# Wait for QEMU to initialize
echo "Waiting for QEMU to initialize (30 seconds)..."
sleep 30

# Check if QEMU is still running
if ! kill -0 $QEMU_PID 2>/dev/null; then
echo "Error: QEMU process died"
exit 1
fi

echo "QEMU is running"
echo "To stop QEMU: kill $QEMU_PID"
echo "Or use: kill \$(cat qemu.pid)"

# Wait for QEMU process
wait $QEMU_PID
101 changes: 101 additions & 0 deletions .github/scripts/setup-qemu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/bin/bash
# Setup QEMU ESP32 emulation environment
# This script downloads and sets up QEMU for ESP32

set -e

QEMU_DIR="qemu-esp32"

echo "Setting up QEMU ESP32..."

# Create directory for QEMU
mkdir -p ${QEMU_DIR}

# Check if QEMU is already installed
if [ -f "${QEMU_DIR}/qemu-system-xtensa" ]; then
echo "QEMU ESP32 already installed"
echo "QEMU binary: ${QEMU_DIR}/qemu-system-xtensa"
exit 0
fi

# Try multiple QEMU sources in order of preference
echo "Attempting to download QEMU ESP32..."

# List of potential QEMU download URLs to try
# Using the latest stable releases from Espressif
QEMU_URLS=(
"esp-develop-9.2.2-20250817|https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-x86_64-linux-gnu.tar.xz"
"esp-develop-9.1.0-20240606|https://github.com/espressif/qemu/releases/download/esp-develop-9.1.0-20240606/qemu-xtensa-softmmu-esp_develop_9.1.0_20240606-x86_64-linux-gnu.tar.xz"
"esp-develop-9.0.0-20231220|https://github.com/espressif/qemu/releases/download/esp-develop-9.0.0-20231220/qemu-xtensa-softmmu-esp_develop_9.0.0_20231220-x86_64-linux-gnu.tar.xz"
)

DOWNLOAD_SUCCESS=false

for ENTRY in "${QEMU_URLS[@]}"; do
VERSION="${ENTRY%%|*}"
URL="${ENTRY##*|}"

echo "Trying version ${VERSION}..."
echo "URL: ${URL}"

if wget --spider -q "${URL}" 2>/dev/null; then
echo "Found available version: ${VERSION}"
echo "Downloading from ${URL}..."

if wget -q "${URL}" -O qemu.tar.xz; then
echo "Download successful, extracting..."
if tar -xf qemu.tar.xz -C ${QEMU_DIR} --strip-components=1; then
rm qemu.tar.xz
DOWNLOAD_SUCCESS=true
echo "QEMU ESP32 version ${VERSION} installed successfully"
Comment on lines +45 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Verify downloaded QEMU archive integrity before extraction.

The script downloads a prebuilt binary and extracts it immediately without checksum/signature validation. In CI, this is a supply-chain risk.

Suggested hardening
 QEMU_URLS=(
-    "esp-develop-9.2.2-20250817|https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-x86_64-linux-gnu.tar.xz"
+    "esp-develop-9.2.2-20250817|https://github.com/espressif/qemu/releases/download/esp-develop-9.2.2-20250817/qemu-xtensa-softmmu-esp_develop_9.2.2_20250817-x86_64-linux-gnu.tar.xz|<SHA256_HERE>"
 )
@@
-    URL="${ENTRY##*|}"
+    URL="$(echo "$ENTRY" | cut -d'|' -f2)"
+    EXPECTED_SHA="$(echo "$ENTRY" | cut -d'|' -f3)"
@@
-        if wget -q "${URL}" -O qemu.tar.xz; then
+        if wget -q "${URL}" -O qemu.tar.xz; then
+            echo "${EXPECTED_SHA}  qemu.tar.xz" | sha256sum -c -
             echo "Download successful, extracting..."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/setup-qemu.sh around lines 45 - 50, The script currently
extracts qemu.tar.xz immediately after wget; instead, add integrity verification
before extraction: after downloading to qemu.tar.xz verify its
checksum/signature (e.g., compare sha256sum against an expected CHECKSUM
variable or verify a detached signature with GPG using a trusted public key) and
only proceed to tar -xf and set DOWNLOAD_SUCCESS=true if verification passes; on
verification failure log an error, remove the downloaded file, and exit
non-zero. Use the existing variables (URL, qemu.tar.xz, QEMU_DIR, VERSION,
DOWNLOAD_SUCCESS) and ensure the verification step occurs between the wget and
tar commands.

break
else
echo "Extraction failed, trying next source..."
rm -f qemu.tar.xz
fi
else
echo "Download failed, trying next source..."
rm -f qemu.tar.xz
fi
else
echo "Version ${VERSION} not available, trying next..."
fi
done

if [ "$DOWNLOAD_SUCCESS" = false ]; then
echo "ERROR: Could not download QEMU ESP32 from any source"
echo "Please check https://github.com/espressif/qemu/releases for available versions"
exit 1
fi

# Make QEMU executable (try both possible locations)
if [ -f "${QEMU_DIR}/qemu-system-xtensa" ]; then
chmod +x ${QEMU_DIR}/qemu-system-xtensa
QEMU_BIN="${QEMU_DIR}/qemu-system-xtensa"
elif [ -f "${QEMU_DIR}/bin/qemu-system-xtensa" ]; then
chmod +x ${QEMU_DIR}/bin/qemu-system-xtensa
# Create symlink for easier access
ln -sf bin/qemu-system-xtensa ${QEMU_DIR}/qemu-system-xtensa
QEMU_BIN="${QEMU_DIR}/bin/qemu-system-xtensa"
else
echo "ERROR: Could not find qemu-system-xtensa binary"
exit 1
fi

echo "QEMU ESP32 setup complete"
echo "QEMU binary: ${QEMU_BIN}"

# Verify QEMU can run by checking for required libraries
echo "Verifying QEMU dependencies..."
if ! ldd "${QEMU_BIN}" | grep -q "not found"; then
echo "All required libraries found"
${QEMU_BIN} --version
else
echo "WARNING: Missing required libraries:"
ldd "${QEMU_BIN}" | grep "not found"
echo ""
echo "Install missing dependencies with:"
echo " sudo apt-get update"
echo " sudo apt-get install -y libsdl2-2.0-0 libpixman-1-0 libglib2.0-0"
exit 1
fi
Loading
Loading