Skip to content

🚧 Add Wokwi ESP32 simulator CI workflow with Playwright web interface testing, boot validation scenarios, combined firmware image support, and network debugging #76

🚧 Add Wokwi ESP32 simulator CI workflow with Playwright web interface testing, boot validation scenarios, combined firmware image support, and network debugging

🚧 Add Wokwi ESP32 simulator CI workflow with Playwright web interface testing, boot validation scenarios, combined firmware image support, and network debugging #76

Workflow file for this run

name: Wokwi ESP32 Simulation Test
on:
push:
branches: [ "mdev", "copilot/**" ]
pull_request:
branches: [ "mdev" ]
workflow_dispatch:
jobs:
wokwi-test:
name: Test WLED with Wokwi Simulator
runs-on: ubuntu-22.04
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache PlatformIO
uses: actions/cache@v4
with:
path: ~/.platformio
key: ${{ runner.os }}-pio-esp32_V4_wokwi_debug
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.9'
- name: Install PlatformIO
run: pip install -r requirements.txt
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install Node.js dependencies
run: npm ci
- name: Build web UI
run: npm run build
- name: Build firmware for ESP32
env:
WLED_RELEASE: True
run: pio run -e esp32_V4_wokwi_debug
- name: Install Wokwi CLI
run: |
curl -L https://wokwi.com/ci/install.sh | sh
echo "Wokwi CLI installed to: $HOME/.wokwi-ci/bin/"
ls -la "$HOME/.wokwi-ci/bin/" || echo "Directory not found"
export PATH="$HOME/.wokwi-ci/bin:$PATH"
wokwi-cli --version || echo "Warning: wokwi-cli not accessible"
- name: Prepare firmware for Wokwi
run: ./test/wokwi/prepare-firmware.sh esp32_V4_wokwi_debug
- name: Debug - Verify token is set
run: |
if [ -z "$WOKWI_CLI_TOKEN" ]; then
echo "❌ ERROR: WOKWI_CLI_TOKEN is not set"
echo "Please configure WOKWI_CLI_TOKEN as a repository secret"
exit 1
else
echo "✅ WOKWI_CLI_TOKEN is set (length: ${#WOKWI_CLI_TOKEN} characters)"
fi
env:
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
- name: Quick boot validation with scenario
working-directory: test/wokwi
env:
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
run: |
# Create log directory
mkdir -p logs
# Add Wokwi CLI to PATH for this step
export PATH="$HOME/.wokwi-ci/bin:$PATH"
# Verify combined firmware image exists (recommended approach for Wokwi)
echo "Checking for combined firmware image..."
if [ ! -f "firmware-combined.bin" ]; then
echo "❌ ERROR: firmware-combined.bin not found in $(pwd)"
echo "Available files:"
ls -la
exit 1
fi
echo "✅ firmware-combined.bin found ($(du -h firmware-combined.bin | cut -f1))"
if [ -f "firmware.elf" ]; then
echo "✅ firmware.elf found ($(du -h firmware.elf | cut -f1))"
else
echo "⚠️ firmware.elf not found (optional for simulation)"
fi
# Verify combined firmware image structure
echo ""
echo "Verifying combined firmware image structure..."
python3 <<'EOF'
import sys
try:
with open('firmware-combined.bin', 'rb') as f:
data = f.read()
size_mb = len(data) / 1024 / 1024
print(f"✓ Image size: {len(data)} bytes ({size_mb:.2f} MB)")
# Check bootloader at 0x1000
if len(data) > 0x1000 and data[0x1000] == 0xe9:
print("✓ Bootloader found at 0x1000 (magic byte: 0xe9)")
else:
print("⚠ Bootloader magic byte not found at 0x1000")
sys.exit(1)
# Check partition table at 0x8000
if len(data) > 0x8000 and data[0x8000:0x8002] == b'\xaa\x50':
print("✓ Partition table found at 0x8000 (magic: 0xaa50)")
else:
print("⚠ Partition table magic not found at 0x8000")
sys.exit(1)
# Check application at 0x10000
if len(data) > 0x10000 and data[0x10000] == 0xe9:
print("✓ Application found at 0x10000 (magic byte: 0xe9)")
else:
print("⚠ Application magic byte not found at 0x10000")
sys.exit(1)
print("✅ Combined firmware image structure is valid")
except Exception as e:
print(f"❌ ERROR verifying combined image: {e}")
sys.exit(1)
EOF
if [ $? -ne 0 ]; then
echo "❌ Combined firmware image verification failed"
echo "Hex dump of first 256 bytes:"
hexdump -C firmware-combined.bin | head -16
exit 1
fi
echo ""
echo "Running quick boot check scenario (15 seconds)..."
echo "Wokwi CLI location: $(which wokwi-cli || echo 'NOT FOUND')"
echo "Wokwi CLI version:"
wokwi-cli --version || echo "Warning: Could not get version"
echo ""
# Run boot check with increased timeout to account for startup time
# Using 30 second timeout for a 15 second scenario to allow for network delays
# Capture serial output (stdout) and Wokwi CLI diagnostics (stderr) separately
if wokwi-cli --timeout 30000 --scenario scenarios/boot-check.yaml . > logs/boot-check-serial.log 2>logs/boot-check.log; then
echo "✅ Boot check passed - firmware boots without crashes"
echo ""
echo "=== Boot check serial output (last 50 lines) ==="
tail -50 logs/boot-check-serial.log
echo ""
echo "=== Boot check CLI log (last 50 lines) ==="
tail -50 logs/boot-check.log
else
EXIT_CODE=$?
echo "❌ Boot check failed with exit code $EXIT_CODE"
echo ""
echo "=== Boot check CLI log ==="
cat logs/boot-check.log
echo ""
echo "=== Boot check serial output ==="
cat logs/boot-check-serial.log
# Check if it's a WebSocket connection error (code 1006)
if grep -q "code 1006" logs/boot-check.log || grep -q "Connection.*closed" logs/boot-check.log; then
echo ""
echo "⚠️ WebSocket connection error detected (code 1006)"
echo "This is typically a transient network issue with Wokwi's API."
echo "The workflow will continue with the full simulation test."
echo "If this persists, check Wokwi service status or network connectivity."
# Don't fail - this might be a transient issue
else
# Other errors should fail the build
exit 1
fi
fi
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Start Wokwi simulator in background
env:
WOKWI_CLI_TOKEN: ${{ secrets.WOKWI_CLI_TOKEN }}
run: |
cd test/wokwi
# Create log directory
mkdir -p logs
# Export the token so it's available to child processes
export WOKWI_CLI_TOKEN
# Start simulator in background with a 90 second timeout
# Wokwi CLI outputs to stderr, serial output goes to stdout
## WOKWI_TIMEOUT=300 ./run-simulator.sh >logs/serial.log 2>&1 &
WOKWI_TIMEOUT=90 ./run-simulator.sh >logs/serial.log 2>&1 &
WOKWI_PID=$!
echo "WOKWI_PID=$WOKWI_PID" >> $GITHUB_ENV
echo "Started Wokwi simulator with PID $WOKWI_PID"
# Wait for simulator to start and web server to be ready
echo "Waiting for WLED web server to be ready..."
#max_wait=240
max_wait=50
elapsed=0
while [ $elapsed -lt $max_wait ]; do
if curl -s -f http://localhost:9080 > /dev/null 2>&1; then
echo "✅ Web server is ready on port 9080 after $elapsed seconds!"
break
fi
if curl -s -f http://localhost:8080 > /dev/null 2>&1; then
echo "✅ Web server is ready on port 8080 after $elapsed seconds!"
break
fi
if ! kill -0 $WOKWI_PID 2>/dev/null; then
echo "❌ Error: Wokwi simulator process died"
echo ""
echo "=== Last 100 lines of Wokwi CLI log ==="
tail -100 logs/wokwi.log || true
echo ""
echo "=== Last 100 lines of Serial output ==="
tail -100 logs/serial.log || true
exit 1
fi
echo "Still waiting... ($elapsed seconds)"
sleep 5
elapsed=$((elapsed + 5))
done
if [ $elapsed -ge $max_wait ]; then
echo "❌ Error: Web server did not start within $max_wait seconds"
echo ""
echo "=== Last 100 lines of Wokwi CLI log ==="
tail -100 logs/wokwi.log || true
echo ""
echo "=== Last 100 lines of Serial output ==="
tail -100 logs/serial.log || true
kill $WOKWI_PID || true
exit 1
fi
echo "WLED is ready for testing!"
echo ""
echo "=== First 50 lines of Serial output ==="
head -50 logs/serial.log || true
- name: Run Playwright tests
run: npm run test:wokwi
env:
CI: true
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Stop Wokwi simulator
if: always()
run: |
if [ ! -z "$WOKWI_PID" ]; then
kill $WOKWI_PID || true
fi
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: wokwi-test-results
path: |
test/wokwi/logs/
test/wokwi/firmware-combined.bin
test-results/
playwright-report/
retention-days: 7