diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000..03c3bbd --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,229 @@ +name: E2E Test Binaries + +on: + push: + branches: ['**'] + pull_request: + branches: ['**'] + workflow_dispatch: + +jobs: + test: + name: Test ${{ matrix.platform }} ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + max-parallel: 6 + matrix: + include: + - os: windows-latest + platform: win + arch: x64 + output: flashforge-webui-win-x64.exe + can_execute: true + - os: macos-15-intel + platform: mac + arch: x64 + output: flashforge-webui-macos-x64.bin + can_execute: true + - os: macos-latest + platform: mac + arch: arm64 + output: flashforge-webui-macos-arm64.bin + can_execute: true + - os: ubuntu-latest + platform: linux + arch: x64 + output: flashforge-webui-linux-x64.bin + can_execute: true + - os: ubuntu-24.04-arm + platform: linux + arch: arm64 + output: flashforge-webui-linux-arm64.bin + can_execute: true + - os: ubuntu-latest + platform: linux + arch: armv7 + output: flashforge-webui-linux-armv7.bin + can_execute: false + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Cache pkg fetch + uses: actions/cache@v4 + with: + path: ~/.pkg-cache + key: ${{ runner.os }}-pkg-${{ hashFiles('package.json') }} + restore-keys: | + ${{ runner.os }}-pkg- + + - name: Configure GitHub Packages + shell: bash + run: | + echo "@ghosttypes:registry=https://npm.pkg.github.com" >> .npmrc + echo "@parallel-7:registry=https://npm.pkg.github.com" >> .npmrc + echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + run: npm ci + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Pre-download ARMv7 Node.js Binary + if: matrix.arch == 'armv7' + run: | + mkdir -p ~/.pkg-cache/v3.5 + curl -L -o ~/.pkg-cache/v3.5/fetched-v20.18.0-linuxstatic-armv7 \ + https://github.com/yao-pkg/pkg-binaries/releases/download/node20/node-v20.18.0-linuxstatic-armv7 + chmod +x ~/.pkg-cache/v3.5/fetched-v20.18.0-linuxstatic-armv7 + + - name: Build application + shell: bash + run: | + npm run build + if [[ "${{ matrix.platform }}" == "win" ]]; then + npx @yao-pkg/pkg . --targets node20-win-${{ matrix.arch }} --output dist/${{ matrix.output }} + elif [[ "${{ matrix.platform }}" == "mac" ]]; then + npx @yao-pkg/pkg . --targets node20-macos-${{ matrix.arch }} --output dist/${{ matrix.output }} + elif [[ "${{ matrix.arch }}" == "armv7" ]]; then + npx @yao-pkg/pkg . --targets node20-linuxstatic-armv7 --output dist/${{ matrix.output }} + else + npx @yao-pkg/pkg . --targets node20-linux-${{ matrix.arch }} --output dist/${{ matrix.output }} + fi + + - name: Verify binary size + shell: bash + run: | + if [[ "${{ runner.os }}" == "macOS" ]] || [[ "${{ runner.os }}" == "macos-latest" ]] || [[ "${{ runner.os }}" == "macos-15-intel" ]]; then + size=$(stat -f%z "dist/${{ matrix.output }}") + elif [[ "${{ runner.os }}" == "Windows" ]]; then + size=$(powershell -Command "(Get-Item 'dist/${{ matrix.output }}').length") + else + size=$(stat -c%s "dist/${{ matrix.output }}") + fi + + if [ $size -lt 40000000 ]; then + echo "::error::Binary size ($size bytes) is too small - assets may not be embedded" + exit 1 + fi + echo "✓ Binary size: $size bytes" + + - name: Start binary in background + if: matrix.can_execute == true + shell: bash + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + powershell -Command "Start-Process -FilePath '.\dist\${{ matrix.output }}' -ArgumentList '--no-printers' -WindowStyle Hidden" + else + chmod +x dist/${{ matrix.output }} + ./dist/${{ matrix.output }} --no-printers > startup.log 2>&1 & + echo $! > binary.pid + fi + + - name: Wait for startup + if: matrix.can_execute == true + shell: bash + run: sleep 15 + + - name: Validate startup + if: matrix.can_execute == true + shell: bash + run: | + if [[ "${{ runner.os }}" != "Windows" ]]; then + if grep -iE "\[Error\]|\[Fatal\]|exception" startup.log; then + echo "::error::Errors detected during startup" + cat startup.log + exit 1 + fi + + if ! grep -q "\[Ready\] FlashForgeWebUI is ready" startup.log; then + echo "::error::Startup did not complete - missing ready marker" + tail -n 50 startup.log + exit 1 + fi + echo "✓ Startup successful" + fi + + - name: Test API endpoints + if: matrix.can_execute == true + shell: bash + run: | + response=$(curl -s http://localhost:3000/api/auth/status) + if ! echo "$response" | jq '.' > /dev/null 2>&1; then + echo "::error::Auth status API returned invalid JSON: $response" + exit 1 + fi + + if ! curl -s http://localhost:3000/ | grep -q "FlashForge Web UI"; then + echo "::error::Failed to serve index.html" + exit 1 + fi + + login_response=$(curl -s -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"password":"changeme"}') + success=$(echo "$login_response" | jq -r '.success') + if [ "$success" != "true" ]; then + echo "::error::Login API failed: $login_response" + exit 1 + fi + + echo "✓ All API tests passed" + + - name: Stop binary + if: matrix.can_execute == true + shell: bash + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + taskkill /F /IM "${{ matrix.output }}" 2>/dev/null || true + else + if [ -f binary.pid ]; then + kill -TERM $(cat binary.pid) || true + rm binary.pid + fi + pkill -TERM -f "${{ matrix.output }}" || true + fi + sleep 3 + + - name: Verify cleanup + if: matrix.can_execute == true + shell: bash + run: | + if [[ "${{ runner.os }}" != "Windows" ]]; then + if pgrep -f "${{ matrix.output }}"; then + echo "::error::Binary left zombie processes" + exit 1 + fi + fi + echo "✓ Cleanup successful" + + - name: Upload test binary + uses: actions/upload-artifact@v4 + if: always() + with: + name: ${{ matrix.output }} + path: dist/${{ matrix.output }} + retention-days: 7 + compression-level: 6 + + - name: Generate summary + if: always() + shell: bash + run: | + echo "## ${{ matrix.platform }} ${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [[ "${{ job.status }}" == "success" ]]; then + echo "✅ All tests passed" >> $GITHUB_STEP_SUMMARY + else + echo "❌ Tests failed" >> $GITHUB_STEP_SUMMARY + fi