diff --git a/.github/workflows/wokwi-test.yml b/.github/workflows/wokwi-test.yml new file mode 100644 index 0000000000..e901929157 --- /dev/null +++ b/.github/workflows/wokwi-test.yml @@ -0,0 +1,301 @@ +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 + + # Add PATH for wokwi-cli + export PATH="$HOME/.wokwi-ci/bin:$PATH" + + # Start simulator in background with a 90 second timeout + # Serial output (stdout) goes to serial.log + # Wokwi CLI diagnostics (stderr) including debug logs go to wokwi.log + WOKWI_TIMEOUT=90 ./run-simulator.sh >logs/serial.log 2>logs/wokwi.log & + 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 + echo "" + echo "=== Wokwi CLI debug log (network traffic) ===" + tail -100 logs/wokwi.log || echo "No wokwi.log available" + + - 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 diff --git a/.gitignore b/.gitignore index c3e06ea53b..92aaf7da81 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,18 @@ compile_commands.json /wled00/wled00.ino.cpp /wled00/html_*.h _codeql_detected_source_root + +# Playwright and test artifacts +/test-results/ +/playwright-report/ +/playwright/.cache/ + +# Wokwi runtime files +/test/wokwi/firmware.bin +/test/wokwi/firmware.elf +/test/wokwi/firmware-combined.bin +/test/wokwi/bootloader.bin +/test/wokwi/partitions.bin +/test/wokwi/.wokwi/ +/test/wokwi/logs/ + diff --git a/package-lock.json b/package-lock.json index ee40deced7..89fb1bbeda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "14.7.0-mdev", "license": "EUPL-1.2", "dependencies": { + "@playwright/test": "^1.40.0", "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "nodemon": "^3.1.9", @@ -76,6 +77,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -620,6 +636,50 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/package.json b/package.json index 2bd32fb12c..52a604bc03 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,11 @@ }, "scripts": { "build": "node tools/cdata.js", - "test": "node --test", - "dev": "nodemon -e js,html,htm,css,png,jpg,gif,ico,js -w tools/ -w wled00/data/ -x node tools/cdata.js" + "test": "node --test tools/cdata-test.js", + "dev": "nodemon -e js,html,htm,css,png,jpg,gif,ico,js -w tools/ -w wled00/data/ -x node tools/cdata.js", + "test:cdata": "node tools/cdata-test.js", + "test:wokwi-cdata": "node tools/wokwi-test.js", + "test:wokwi": "playwright test test/playwright/wokwi-basic.spec.js" }, "repository": { "type": "git", @@ -26,7 +29,8 @@ "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "web-resource-inliner": "^7.0.0", - "nodemon": "^3.1.9" + "nodemon": "^3.1.9", + "@playwright/test": "^1.40.0" }, "engines": { "node": ">=20.0.0" diff --git a/platformio.ini b/platformio.ini index d26b9fa7e8..435740fcb2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -2025,6 +2025,67 @@ monitor_filters = esp32_exception_decoder ; RAM: [=== ] 26.4% (used 86356 bytes from 327680 bytes) ; Flash: [======== ] 83.6% (used 1753461 bytes from 2097152 bytes) + +## for testing with Wokwi +[env:esp32_V4_wokwi_debug] +extends = esp32_4MB_V4_M_base +;; platform = ${esp32.platformTasmota} +;; platform_packages = ${esp32.platform_packagesTasmota} +;board = esp32_16MB-poe ;; needed for ethernet boards (selects "esp32-poe" as variant) +;;board_build.partitions = ${esp32.extreme_partitions} ;; WLED extended for 16MB flash: 3.2MB firmware, 9 MB filesystem +board_build.partitions = ${esp32.big_partitions} +build_unflags = ${esp32_4MB_V4_S_base.build_unflags} + ;; removing some usermods to keep it simple + -D USERMOD_DALLASTEMPERATURE + -D USERMOD_FOUR_LINE_DISPLAY + ;;-D USERMOD_ARTIFX + -D USERMOD_ROTARY_ENCODER_UI + -D USERMOD_AUTO_SAVE + -D USERMOD_PIRSWITCH + -D USERMOD_MULTI_RELAY + -D USE_ALT_DISPLAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI + -D USERMOD_MPU6050_IMU ; gyro/accelero for USERMOD_GAMES (ONLY WORKS IF USERMOD_FOUR_LINE_DISPLAY NOT INCLUDED - I2C SHARING BUG) + -D USERMOD_GAMES ; WLEDMM usermod + ${common_mm.animartrix_build_flags} + ${common_mm.HUB75_build_flags} + -D WLED_DEBUG_HOST='"192.168.x.x"' ;; to disable net print + ;; more debug output + -DCORE_DEBUG_LEVEL=0 + -DNDEBUG + ;;${Speed_Flags.build_unflags} ;; to override -Os +build_flags = ${esp32_4MB_V4_S_base.build_flags} + ${common_mm.build_disable_sync_interfaces} + -D WLED_RELEASE_NAME=esp32_16MB_Wokwi_debug ; This will be included in the firmware.bin filename + -D SERVERNAME='"WLED-WOKWI"' + -D CLIENT_SSID='"Wokwi-GUEST"' -D CLIENT_PASS='""' + -D LEDPIN=4 + ;;${Speed_Flags.build_flags_V4} ;; optimize for speed + -g3 -ggdb ;; better debug output + -DCORE_DEBUG_LEVEL=5 ;; max core debug output + -DDEBUG -D WLED_DEBUG -DWLED_DEBUG_JSON ;; -DWLED_DEBUG_FS ;; max WLED debugging output + -D WLED_DISABLE_BROWNOUT_DET -D WLED_WATCHDOG_TIMEOUT=0 + ;;-D WLED_USE_ETHERNET + ;; -D WLED_ETH_DEFAULT=2 ;; ESP32-POE board configuration (works with QEMU open_eth) + -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only + -D WLED_DISABLE_OTA + ;;-D WLED_DISABLE_ADALIGHT ;; WLEDMM Better to disable serial protocols, to avoid crashes (see upstream #3128) + -D MDNS_NAME=\"\" ;; disable MDNS + -D WLED_DISABLE_WEBSOCKETS ;; twmporarily disabled + -D WLED_DISABLE_INFRARED + -D LEDPIN=4 + -D BTNPIN=-1 -D RLYPIN=-1 -D IRPIN=-1 ;; disable all extra pins + -D SR_DMTYPE=254 -D AUDIOPIN=-1 ;; set AR into "received only" mode +lib_deps = ${esp32_4MB_V4_S_base.esp32_lib_deps} +lib_ignore = + IRremoteESP8266 ; use with WLED_DISABLE_INFRARED for faster compilation + OneWire ; used for USERMOD_FOUR_LINE_DISPLAY and USERMOD_DALLASTEMPERATURE + U8g2 ; used for USERMOD_FOUR_LINE_DISPLA + ${common_mm.HUB75_lib_ignore} +monitor_filters = esp32_exception_decoder +; RAM: [=== ] 25.7% (used 84372 bytes from 327680 bytes) +; Flash: [========= ] 85.3% (used 1621925 bytes from 1900544 bytes) + + ;; experimental environment for boards with PSRAM (needs ESP-IDF 4.4.1). HUB75 included (may have PIN conflicts) [env:esp32_4MB_PSRAM_S] extends = esp32_4MB_V4_S_base diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000000..6e2867b563 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,52 @@ +const { defineConfig, devices } = require('@playwright/test'); + +/** + * Playwright configuration for WLED-MM Wokwi testing + * See https://playwright.dev/docs/test-configuration. + */ +module.exports = defineConfig({ + testDir: './test/playwright', + + /* Run tests in files in parallel */ + fullyParallel: false, + + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + + /* Opt out of parallel tests on CI. */ + workers: 1, + + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:8080', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Screenshot on failure */ + screenshot: 'only-on-failure', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'echo "Wokwi simulator should be started separately"', + url: 'http://localhost:8080', + reuseExistingServer: !process.env.CI, + timeout: 120000, + }, +}); diff --git a/test/playwright/wokwi-basic.spec.js b/test/playwright/wokwi-basic.spec.js new file mode 100644 index 0000000000..4466afe899 --- /dev/null +++ b/test/playwright/wokwi-basic.spec.js @@ -0,0 +1,136 @@ +const { test, expect } = require('@playwright/test'); + +/** + * Basic WLED-MM Web Interface Tests + * These tests verify that the web interface loads correctly + * and doesn't have JavaScript errors on basic pages. + */ + +test.describe('WLED-MM Basic Web Interface', () => { + let consoleErrors = []; + let pageErrors = []; + + test.beforeEach(async ({ page }) => { + // Reset error collectors + consoleErrors = []; + pageErrors = []; + + // Listen for console errors + page.on('console', msg => { + if (msg.type() === 'error') { + consoleErrors.push(msg.text()); + } + }); + + // Listen for page errors + page.on('pageerror', error => { + pageErrors.push(error.message); + }); + }); + + test('should load main index page without errors', async ({ page }) => { + await page.goto('/'); + + // Wait for page to be loaded + await page.waitForLoadState('networkidle'); + + // Check for page title or main content + const title = await page.title(); + expect(title).toBeTruthy(); + + // Verify no JavaScript errors occurred + expect(consoleErrors).toHaveLength(0); + expect(pageErrors).toHaveLength(0); + + console.log('Main page loaded successfully'); + }); + + test('should load settings page without errors', async ({ page }) => { + await page.goto('/settings.htm'); + + // Wait for page to be loaded + await page.waitForLoadState('networkidle'); + + // Verify no JavaScript errors occurred + expect(consoleErrors).toHaveLength(0); + expect(pageErrors).toHaveLength(0); + + console.log('Settings page loaded successfully'); + }); + + test('should load WiFi settings page without errors', async ({ page }) => { + await page.goto('/settings/wifi'); + + // Wait for page to be loaded + await page.waitForLoadState('networkidle'); + + // Verify no JavaScript errors occurred + expect(consoleErrors).toHaveLength(0); + expect(pageErrors).toHaveLength(0); + + console.log('WiFi settings page loaded successfully'); + }); + + test('should load LED settings page without errors', async ({ page }) => { + await page.goto('/settings/leds'); + + // Wait for page to be loaded + await page.waitForLoadState('networkidle'); + + // Verify no JavaScript errors occurred + expect(consoleErrors).toHaveLength(0); + expect(pageErrors).toHaveLength(0); + + console.log('LED settings page loaded successfully'); + }); + + test('should load UI settings page without errors', async ({ page }) => { + await page.goto('/settings/ui'); + + // Wait for page to be loaded + await page.waitForLoadState('networkidle'); + + // Verify no JavaScript errors occurred + expect(consoleErrors).toHaveLength(0); + expect(pageErrors).toHaveLength(0); + + console.log('UI settings page loaded successfully'); + }); + + test('should load edit page without errors', async ({ page }) => { + await page.goto('/edit.htm'); + + // Wait for page to be loaded + await page.waitForLoadState('networkidle'); + + // Verify no JavaScript errors occurred + expect(consoleErrors).toHaveLength(0); + expect(pageErrors).toHaveLength(0); + + console.log('Edit page loaded successfully'); + }); + + test('should be able to check JSON API info', async ({ page }) => { + const response = await page.goto('/json/info'); + + expect(response?.status()).toBe(200); + + const json = await response?.json(); + expect(json).toBeTruthy(); + expect(json.ver).toBeTruthy(); // Should have version + + console.log('JSON API responding correctly, version:', json.ver); + }); + + test('should be able to check JSON API state', async ({ page }) => { + const response = await page.goto('/json/state'); + + expect(response?.status()).toBe(200); + + const json = await response?.json(); + expect(json).toBeTruthy(); + expect(json.on).toBeDefined(); // Should have on/off state + + console.log('JSON state API responding correctly'); + }); +}); diff --git a/test/wokwi/README.md b/test/wokwi/README.md new file mode 100644 index 0000000000..75c0ca624e --- /dev/null +++ b/test/wokwi/README.md @@ -0,0 +1,308 @@ +# WLED-MM Wokwi Simulation Testing + +This directory contains configuration and tests for running WLED-MM in the Wokwi ESP32 simulator with Playwright-based web interface testing. + +## Overview + +The Wokwi testing workflow: +1. Builds the WLED firmware for ESP32 +2. Creates a combined firmware image with esptool.py +3. Runs the firmware in the Wokwi ESP32 simulator +4. Uses Playwright to test the web interface +5. Verifies pages load without JavaScript errors + +## Files + +- `diagram.json` - Wokwi hardware configuration (ESP32 DevKit) with serial monitor settings +- `wokwi.toml` - Wokwi CLI configuration using combined firmware image +- `prepare-firmware.sh` - Script to create combined firmware image using esptool.py +- `run-simulator.sh` - Script to start the Wokwi simulator +- `firmware-combined.bin` - Combined firmware image (bootloader + partitions + app) +- `firmware.elf` - Firmware with debug symbols (copied from build) + +## Combined Firmware Image + +**Wokwi's recommended approach** is to use a single combined firmware image that includes bootloader, partition table, and application. This ensures proper filesystem support and eliminates potential issues with separate flash files. + +**wokwi.toml configuration:** +```toml +[wokwi] +firmware = "firmware-combined.bin" # Combined image with everything +elf = "firmware.elf" # Debug symbols +``` + +**Combined image structure:** +- `0x1000` - Bootloader (ESP32 second-stage bootloader) +- `0x8000` - Partition table (defines flash memory layout) +- `0x10000` - Application (main WLED firmware) + +**How it's created:** +The `prepare-firmware.sh` script uses `esptool.py merge_bin` to combine the three components: +```bash +esptool.py --chip esp32 merge_bin \ + -o firmware-combined.bin \ + --flash_mode dio \ + --flash_freq 40m \ + --flash_size 4MB \ + 0x1000 bootloader.bin \ + 0x8000 partitions.bin \ + 0x10000 firmware.bin +``` + +**Why this approach:** +- Recommended by Wokwi for reliable filesystem support +- Ensures correct alignment and offsets for all components +- Eliminates "partition not found" errors +- Single file is simpler and more reliable than multiple flash files +- Matches how real ESP32 devices are typically flashed + +## Serial Monitor Configuration + +The `diagram.json` file includes critical serial monitor configuration required for capturing firmware output in CI environments: + +```json +"serialMonitor": { + "display": "always", + "newline": "lf" +} +``` + +**Why this is needed:** +- Without `display: "always"`, serial output is not captured in headless/CI mode +- The Wokwi simulator only records serial output when explicitly configured +- This is **required** for debugging boot issues and verifying firmware execution + +**Configuration options:** +- `display: "always"` - Ensures serial output is captured even in headless mode (CI) +- `display: "auto"` - Only shows serial monitor when running interactively (not suitable for CI) +- `newline: "lf"` - Line ending format (LF for Unix-style, CRLF for Windows) + +**Troubleshooting missing serial output:** +If you see empty serial logs (`boot-check-serial.log` or `serial.log`): +1. Verify `serialMonitor` section exists in `diagram.json` +2. Check firmware.bin has valid ESP32 header (starts with `0xe9` magic byte) +3. Ensure firmware was built successfully and copied to test directory +4. Review firmware build logs for compilation errors + +## Running Tests Locally + +### Prerequisites + +1. Install Node.js dependencies: + ```bash + npm ci + ``` + +2. Install Wokwi CLI: + ```bash + curl -L https://wokwi.com/ci/install.sh | sh + ``` + +3. Install Playwright browsers: + ```bash + npx playwright install --with-deps chromium + ``` + +### Build and Test + +1. Build the web interface: + ```bash + npm run build + ``` + +2. Build the firmware: + ```bash + pio run -e esp32_V4_wokwi_debug + ``` + +3. Prepare firmware for testing: + ```bash + ./test/wokwi/prepare-firmware.sh esp32_V4_wokwi_debug + ``` + +4. Start the Wokwi simulator (in a separate terminal): + ```bash + cd test/wokwi + ./run-simulator.sh + ``` + +5. Run Playwright tests (in another terminal): + ```bash + npm run test:wokwi + ``` + +## CI Integration + +The GitHub Actions workflow (`.github/workflows/wokwi-test.yml`) automatically runs these tests on: +- Push to `mdev` branch +- Pull requests to `mdev` branch +- Manual workflow dispatch + +## Test Cases + +The Playwright tests (`test/playwright/wokwi-basic.spec.js`) verify: +- Main index page loads without errors +- Settings pages load without errors +- Edit page loads without errors +- JSON API endpoints respond correctly + +## Boot Validation Scenarios + +Wokwi CLI supports test scenarios that can validate firmware boot without requiring a full Playwright test suite. Two scenarios are provided: + +### Quick Boot Check (`scenarios/boot-check.yaml`) +A fast 15-second validation that ensures WLED boots without immediate crashes. + +**Features:** +- Simple delay-based validation +- Total runtime: ~15 seconds +- Fails if simulator crashes or hangs during boot +- Perfect for CI pre-flight checks +- Tolerates transient network issues (code 1006 errors) + +**Usage:** +```bash +cd test/wokwi +~/.wokwi-ci/bin/wokwi-cli --timeout 30000 --scenario scenarios/boot-check.yaml . +``` + +**Note:** The CLI timeout (30 seconds) is set higher than the scenario timeout (15 seconds) to account for network delays and API connection time. + +### Comprehensive Boot Validation (`scenarios/boot-full.yaml`) +A thorough 30-second validation with extended timing for WiFi AP and HTTP server initialization. + +**Features:** +- Allows full system initialization +- Total runtime: ~30 seconds +- More detailed validation +- Better for local testing and troubleshooting + +**Usage:** +```bash +cd test/wokwi +~/.wokwi-ci/bin/wokwi-cli --timeout 40000 --scenario scenarios/boot-full.yaml . +``` + +### Creating Custom Scenarios + +You can create your own scenario files in YAML format: + +```yaml +name: "Custom Test" +version: 1 +timeout: 15000 # milliseconds + +steps: + - name: "Description of step" + delay: 5s # wait 5 seconds (note: time units required!) +``` + +The scenario will fail if: +- The simulator crashes during execution +- The timeout is exceeded +- Any step encounters an error + +## Extending Tests + +To add more tests: +1. Edit `test/playwright/wokwi-basic.spec.js` +2. Add new test cases using Playwright's `test()` function +3. Follow the existing pattern of checking for console errors +4. Create custom scenario files in `scenarios/` directory + +## Troubleshooting + +### Simulator doesn't start +- Check that firmware-combined.bin exists in test/wokwi/ +- Verify Wokwi CLI is installed: `wokwi-cli --version` +- Check Wokwi CLI logs for errors + +### No serial output from firmware +- Verify `serialMonitor` configuration in diagram.json +- Check firmware-combined.bin is valid: + ```bash + hexdump -C firmware-combined.bin | head -16 + # Should show bootloader at 0x1000, partitions at 0x8000, app at 0x10000 + ``` +- Ensure combined image was created: `ls -lh test/wokwi/firmware-combined.bin` +- Check firmware build logs for errors + +### Filesystem/partition errors +**Error:** `partition "spiffs" could not be found` + +**Cause:** Missing or incorrect combined firmware image + +**Solutions:** +1. Verify firmware-combined.bin exists and has correct structure: + ```bash + python3 << 'EOF' + with open('test/wokwi/firmware-combined.bin', 'rb') as f: + data = f.read() + print(f"Size: {len(data)} bytes") + print(f"Bootloader at 0x1000: {data[0x1000:0x1004].hex()}") + print(f"Partitions at 0x8000: {data[0x8000:0x8004].hex()}") + print(f"App at 0x10000: {data[0x10000:0x10004].hex()}") + EOF + ``` + +2. Rebuild combined image: + ```bash + cd test/wokwi + ./prepare-firmware.sh esp32_V4_wokwi_debug + ``` + +3. Verify esptool.py is installed: + ```bash + pip install esptool + ``` + +4. Check that bootloader.bin, partitions.bin, and firmware.bin exist in build directory: + ```bash + ls -lh .pio/build/esp32_V4_wokwi_debug/{bootloader.bin,partitions.bin,firmware.bin} + # May also check: .pio/build/esp32_V4_wokwi_debug/bootloader/bootloader.bin + ``` + +4. Check the partition table source CSV file: + ```bash + # The esp32_V4_wokwi_debug build uses: + cat tools/WLED_ESP32_4MB_256KB_FS.csv + # Should include a line with: spiffs, data, spiffs, ... + ``` + +5. Rebuild firmware to regenerate partition files: + ```bash + pio run -e esp32_V4_wokwi_debug --target clean + pio run -e esp32_V4_wokwi_debug + ``` + +6. Check wokwi.toml flash configuration: + ```toml + [wokwi] + partitions = "partitions.bin" + + [[wokwi.flashFiles]] + offset = 0x1000 + file = "bootloader.bin" + + [[wokwi.flashFiles]] + offset = 0x8000 + file = "partitions.bin" + ``` + +**Note:** The bootloader and partition table are essential for filesystem support. Without them, SPIFFS cannot mount and the firmware will show "partition not found" errors. + +### Web server not accessible +- Wait 30-60 seconds for the ESP32 to boot and start WiFi +- Check that port 9080 is not already in use +- Verify port forwarding in wokwi.toml + +### Tests fail +- Check Playwright report: `npx playwright show-report` +- Look for console errors in test output +- Verify firmware build completed successfully + +## References + +- [Wokwi Documentation](https://docs.wokwi.com/) +- [Wokwi CLI](https://docs.wokwi.com/wokwi-ci/getting-started) +- [Playwright Documentation](https://playwright.dev/) diff --git a/test/wokwi/diagram.json b/test/wokwi/diagram.json new file mode 100644 index 0000000000..40635896fc --- /dev/null +++ b/test/wokwi/diagram.json @@ -0,0 +1,28 @@ +{ + "version": 1, + "author": "WLED-MM CI", + "editor": "wokwi", + "parts": [ + { + "type": "wokwi-esp32-devkit-v1", + "id": "esp", + "top": 0, + "left": 0, + "attrs": {} + }, + { "type": "wokwi-neopixel", "id": "neopixel", "top": 20, "left": 219, "attrs": {} } + ], + "connections": [ + [ "esp:TX0", "$serialMonitor:RX", "", [] ], + [ "esp:RX0", "$serialMonitor:TX", "", [] ], + [ "esp:GND.1", "neopixel:VSS", "black", [ "h0" ] ], + [ "esp:D4", "neopixel:DIN", "green", [ "h133.53", "v-143.57" ] ], + [ "esp:VIN", "neopixel:VDD", "red", [ "h-10.83", "v-197.43" ] ] + ], + "dependencies": {}, + "serialMonitor": { + "display": "always", + "newline": "lf", + "bufferSize": 8192 + } +} diff --git a/test/wokwi/prepare-firmware.sh b/test/wokwi/prepare-firmware.sh new file mode 100755 index 0000000000..73d6778296 --- /dev/null +++ b/test/wokwi/prepare-firmware.sh @@ -0,0 +1,218 @@ +#!/bin/bash +# Script to prepare firmware for Wokwi testing +# This creates a combined firmware image with bootloader, partitions, and app + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +WOKWI_DIR="$PROJECT_ROOT/test/wokwi" + +# Check if environment is specified +if [ -z "$1" ]; then + echo "Usage: $0 " + echo "Example: $0 esp32_V4_wokwi_debug" + exit 1 +fi + +ENV_NAME=$1 +BUILD_DIR="$PROJECT_ROOT/.pio/build/$ENV_NAME" +FIRMWARE_BIN="$BUILD_DIR/firmware.bin" +FIRMWARE_ELF="$BUILD_DIR/firmware.elf" + +echo "=== Preparing firmware for Wokwi simulation ===" +echo "Environment: $ENV_NAME" +echo "Build directory: $BUILD_DIR" +echo "" + +# Check if build directory exists +if [ ! -d "$BUILD_DIR" ]; then + echo "❌ ERROR: Build directory not found: $BUILD_DIR" + echo "Please build the firmware first: pio run -e $ENV_NAME" + exit 1 +fi + +echo "Searching for required files in build directory..." +echo "" + +# Function to find file with multiple possible locations +# Debug output goes to stderr, only the file path goes to stdout +find_file() { + local file_desc=$1 + shift + local found_file="" + + echo "Looking for $file_desc:" >&2 + for path in "$@"; do + echo " Checking: $path" >&2 + if [ -f "$path" ]; then + found_file="$path" + echo " ✓ Found at: $path" >&2 + echo "$found_file" + return 0 + fi + done + + echo " ❌ Not found in any expected location" >&2 + return 1 +} + +# Find firmware.bin +FIRMWARE_BIN=$(find_file "firmware.bin" \ + "$BUILD_DIR/firmware.bin") || { + echo "" + echo "❌ ERROR: firmware.bin not found" + FIRMWARE_BIN="" +} +echo "" + +# Find bootloader.bin - check multiple common locations +BOOTLOADER_BIN=$(find_file "bootloader.bin" \ + "$BUILD_DIR/bootloader.bin" \ + "$BUILD_DIR/bootloader/bootloader.bin" \ + "$BUILD_DIR/esp-idf/bootloader/bootloader.bin") || { + echo "" + echo "❌ ERROR: bootloader.bin not found" + echo "Searching entire build directory for bootloader.bin:" + find "$BUILD_DIR" -name "bootloader.bin" -type f 2>/dev/null | while read f; do + echo " Found: $f" + done + BOOTLOADER_BIN="" +} +echo "" + +# Find partitions.bin - check multiple common locations +PARTITIONS_BIN=$(find_file "partitions.bin" \ + "$BUILD_DIR/partitions.bin" \ + "$BUILD_DIR/partitions/partitions.bin") || { + echo "" + echo "❌ ERROR: partitions.bin not found" + echo "Searching entire build directory for partitions.bin:" + find "$BUILD_DIR" -name "partitions.bin" -type f 2>/dev/null | while read f; do + echo " Found: $f" + done + PARTITIONS_BIN="" +} +echo "" + +# Check if any files are missing +MISSING_FILES="" +[ -z "$FIRMWARE_BIN" ] && MISSING_FILES="${MISSING_FILES}firmware.bin " +[ -z "$BOOTLOADER_BIN" ] && MISSING_FILES="${MISSING_FILES}bootloader.bin " +[ -z "$PARTITIONS_BIN" ] && MISSING_FILES="${MISSING_FILES}partitions.bin " + +if [ -n "$MISSING_FILES" ]; then + echo "❌ ERROR: Missing required files: $MISSING_FILES" + echo "" + echo "Build directory structure:" + echo "----------------------------------------" + ls -lR "$BUILD_DIR" | head -50 + echo "----------------------------------------" + echo "" + echo "These files are generated by PlatformIO/ESP-IDF during the build process." + echo "" + echo "Troubleshooting steps:" + echo "1. Ensure the build completed successfully: pio run -e $ENV_NAME" + echo "2. Check for build errors in PlatformIO output" + echo "3. Verify partition table is specified in platformio.ini" + echo "4. For ESP-IDF framework, bootloader should be auto-generated" + echo "" + echo "If files are in unexpected locations, the script will show them above." + exit 1 +fi + +echo "✅ All required files found!" +echo "" + +echo "Creating combined firmware image for Wokwi..." +echo "Using files from $ENV_NAME build:" +echo " - Bootloader: $(basename $(dirname "$BOOTLOADER_BIN"))/$(basename "$BOOTLOADER_BIN")" +echo " - Partitions: $PARTITIONS_BIN" +echo " - Firmware: $FIRMWARE_BIN" + +# Create combined firmware image using esptool.py +# Standard ESP32 flash layout: +# 0x1000 - bootloader +# 0x8000 - partition table +# 0x10000 - application +echo "" +echo "Merging firmware components into combined image..." + +# Check if esptool.py is available +if ! command -v esptool.py &> /dev/null; then + echo "❌ ERROR: esptool.py not found" + echo "Installing esptool..." + pip install esptool || { + echo "❌ ERROR: Failed to install esptool" + echo "Please install manually: pip install esptool" + exit 1 + } +fi + +# Merge binaries into a single combined image +# This is the recommended approach for Wokwi to ensure proper filesystem support +esptool.py --chip esp32 merge_bin \ + -o "$WOKWI_DIR/firmware-combined.bin" \ + --flash_mode dio \ + --flash_freq 40m \ + --flash_size 4MB \ + 0x1000 "$BOOTLOADER_BIN" \ + 0x8000 "$PARTITIONS_BIN" \ + 0x10000 "$FIRMWARE_BIN" + +echo "✓ Created combined firmware image: firmware-combined.bin" + +# Also copy ELF file for debugging +if [ -f "$FIRMWARE_ELF" ]; then + cp "$FIRMWARE_ELF" "$WOKWI_DIR/firmware.elf" + echo "✓ Copied firmware.elf for debugging" +else + echo "⚠ Warning: firmware.elf not found" +fi + +# Display combined image information +echo "" +echo "Combined firmware image details:" +ls -lh "$WOKWI_DIR/firmware-combined.bin" +echo "" +echo "Image structure:" +echo " 0x00001000 - Bootloader ($(stat -f%z "$BOOTLOADER_BIN" 2>/dev/null || stat -c%s "$BOOTLOADER_BIN") bytes)" +echo " 0x00008000 - Partition table ($(stat -f%z "$PARTITIONS_BIN" 2>/dev/null || stat -c%s "$PARTITIONS_BIN") bytes)" +echo " 0x00010000 - Application ($(stat -f%z "$FIRMWARE_BIN" 2>/dev/null || stat -c%s "$FIRMWARE_BIN") bytes)" + +# Verify the combined image +echo "" +echo "Verifying combined image..." +python3 -c " +import sys +try: + with open('$WOKWI_DIR/firmware-combined.bin', 'rb') as f: + data = f.read() + print(f'✓ Combined image size: {len(data)} bytes ({len(data)/1024/1024:.2f} MB)') + + # Check bootloader magic byte at offset 0x1000 + if len(data) > 0x1000 and data[0x1000] == 0xe9: + print('✓ Bootloader magic byte (0xe9) found at 0x1000') + else: + print('⚠ Warning: Bootloader magic byte not found at expected location') + + # Check partition table magic bytes at offset 0x8000 + if len(data) > 0x8000 and data[0x8000:0x8002] == b'\xaa\x50': + print('✓ Partition table magic bytes found at 0x8000') + else: + print('⚠ Warning: Partition table magic bytes not found at expected location') + + # Check application magic byte at offset 0x10000 + if len(data) > 0x10000 and data[0x10000] == 0xe9: + print('✓ Application magic byte (0xe9) found at 0x10000') + else: + print('⚠ Warning: Application magic byte not found at expected location') + +except Exception as e: + print(f'❌ ERROR verifying image: {e}') + sys.exit(1) +" + +echo "" +echo "✅ Firmware prepared successfully!" +echo "Combined firmware image ready for Wokwi simulation." diff --git a/test/wokwi/run-simulator.sh b/test/wokwi/run-simulator.sh new file mode 100755 index 0000000000..b2d31443dc --- /dev/null +++ b/test/wokwi/run-simulator.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Script to run Wokwi simulator with the built firmware +# This script starts the Wokwi CLI simulator and waits for it to be ready + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WOKWI_TIMEOUT=${WOKWI_TIMEOUT:-300} + +cd "$SCRIPT_DIR" + +# Check if combined firmware exists +if [ ! -f "firmware-combined.bin" ]; then + echo "Error: firmware-combined.bin not found in $SCRIPT_DIR" + echo "Please run prepare-firmware.sh first" + exit 1 +fi + +echo "Starting Wokwi simulator..." +echo "Timeout: ${WOKWI_TIMEOUT} seconds" +echo "Web server will be available at http://localhost:8080" +echo "Serial output will be displayed below" +echo "==================================" +echo "" + +# Run wokwi-cli with timeout (in milliseconds) and scenario flag for better output +# The simulator will forward port 80 to localhost:9080 +# Note: wokwi-cli runs in foreground, so this needs to be backgrounded or run in a separate process +# When run from the directory containing diagram.json and wokwi.toml, wokwi-cli will find them automatically +# Serial output goes to stdout, diagnostic messages go to stderr +wokwi-cli --timeout ${WOKWI_TIMEOUT}000 . diff --git a/test/wokwi/scenarios/boot-check.yaml b/test/wokwi/scenarios/boot-check.yaml new file mode 100644 index 0000000000..caa7eb270f --- /dev/null +++ b/test/wokwi/scenarios/boot-check.yaml @@ -0,0 +1,21 @@ +# Quick Boot Check Scenario for WLED-MM +# This scenario performs a fast validation that WLED boots without immediate crashes +# Total runtime: ~15 seconds +# Perfect for CI pre-flight checks + +name: "WLED Quick Boot Check" +version: 1 +timeout: 15000 # 15 seconds - increased to allow for slower CI environments + +# Simple validation: just delay and ensure the simulator doesn't crash +steps: + - name: "Wait for initial boot" + delay: 6s # 6 seconds + + - name: "Verify simulator is still running" + delay: 6s # 6 seconds + + - name: "Final stability check" + delay: 3s # 3 seconds + +# If we get here without the simulator crashing, boot is successful diff --git a/test/wokwi/scenarios/boot-full.yaml b/test/wokwi/scenarios/boot-full.yaml new file mode 100644 index 0000000000..335d0a3cb5 --- /dev/null +++ b/test/wokwi/scenarios/boot-full.yaml @@ -0,0 +1,24 @@ +# Comprehensive Boot Validation Scenario for WLED-MM +# This scenario performs thorough validation with extended timing for WiFi AP and HTTP server +# Total runtime: ~30 seconds +# Better for local testing and troubleshooting + +name: "WLED Comprehensive Boot Validation" +version: 1 +timeout: 30000 # 30 seconds + +# Extended validation with more time for full system initialization +steps: + - name: "Initial boot phase" + delay: 8s # 8 seconds - ESP32 basic initialization + + - name: "WiFi AP initialization" + delay: 10s # 10 seconds - WiFi access point setup + + - name: "HTTP server startup" + delay: 8s # 8 seconds - Web server initialization + + - name: "Final stability verification" + delay: 4s # 4 seconds - Ensure everything is stable + +# If we get here, WLED has fully initialized with WiFi AP and HTTP server diff --git a/test/wokwi/wokwi.toml b/test/wokwi/wokwi.toml new file mode 100644 index 0000000000..1e1ae123fa --- /dev/null +++ b/test/wokwi/wokwi.toml @@ -0,0 +1,19 @@ +[wokwi] +version = 1 +# Use combined firmware image that includes bootloader, partitions, and application +# This is the recommended approach for Wokwi to ensure proper filesystem support +firmware = "firmware-combined.bin" +elf = "firmware.elf" +gdbServerPort=3333 +logLevel = "warn" # Enable Wokwi debug logging (error, warn, info, debug, trace) + +# Combined firmware layout: +# 0x1000 - bootloader +# 0x8000 - partition table +# 0x10000 - application + +[[net.forward]] +# Forward the web server port +from = "localhost:8080" +to = "target:80" +logLevel = "debug" # Log all network traffic for debugging AsyncWebServer issues diff --git a/tools/wokwi-test.js b/tools/wokwi-test.js new file mode 100644 index 0000000000..29391a41b2 --- /dev/null +++ b/tools/wokwi-test.js @@ -0,0 +1,35 @@ +/** + * Simple test to verify cdata.js build process works + */ + +const fs = require("fs"); +const path = require("path"); + +console.log("Running cdata.js build test..."); + +// Check if required files exist +const requiredFiles = [ + "wled00/data/index.htm", + "wled00/data/settings.htm", + "wled00/html_ui.h" +]; + +let allFilesExist = true; + +for (const file of requiredFiles) { + const filePath = path.join(__dirname, "..", file); + if (!fs.existsSync(filePath)) { + console.error(`❌ Required file not found: ${file}`); + allFilesExist = false; + } else { + console.log(`✓ Found: ${file}`); + } +} + +if (!allFilesExist) { + console.error("\n❌ Some required files are missing. Please run 'npm run build' first."); + process.exit(1); +} + +console.log("\n✓ All tests passed!"); +process.exit(0); diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 2c2b18d3f9..970c0abdff 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -733,7 +733,8 @@ void WLED::setup() fsinit = WLED_FS.begin(); #endif if (!fsinit) { - USER_PRINTLN(F("Mount FS failed!")); // WLEDMM + USER_PRINT(F("Mount FS failed!")); // WLEDMM + USER_PRINTF(" errno=%d (%s)\n", errno, strerror(errno)); errorFlag = ERR_FS_BEGIN; } else { USER_PRINTLN(F("Mount FS succeeded.")); // WLEDMM @@ -1255,6 +1256,7 @@ void WLED::initInterfaces() MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); } server.begin(); + DEBUG_PRINTLN("Web Server Started (sta mode)"); if (udpPort > 0 && udpPort != ntpLocalPort) { udpConnected = notifierUdp.begin(udpPort);