name: E2E Tests on: push: branches: [main] pull_request: paths: - 'src/**' - 'e2e/**' - 'public/**' - 'playwright.config.ts' - 'package.json' - 'pnpm-lock.yaml' - 'next.config.*' - 'tailwind.config.*' - 'tsconfig.json' - '.github/workflows/e2e.yml' jobs: e2e: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: read pull-requests: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 25 - name: Install pnpm run: corepack enable && corepack prepare pnpm@10 --activate - name: Install dependencies run: pnpm install --frozen-lockfile - name: Install Playwright browsers run: pnpm exec playwright install --with-deps chromium webkit - name: Detect affected pages id: affected if: github.event_name == 'pull_request' run: | CHANGED=$(gh pr diff "${{ github.event.pull_request.number }}" --name-only) # Shared files that affect ALL pages (layouts, components, styles, config, public assets) if echo "$CHANGED" | grep -qE '^(src/app/layout\.tsx|src/app/\(main\)/layout\.tsx|src/components/|src/styles/|src/lib/|src/i18n/|public/|tailwind\.config|next\.config|src/app/globals\.css)'; then echo "pages=all" >> "$GITHUB_OUTPUT" echo "frontend_changed=true" >> "$GITHUB_OUTPUT" exit 0 fi # Map route-specific files to page names PAGES="" while IFS= read -r file; do case "$file" in src/app/\(main\)/page.tsx) PAGES="$PAGES,homepage" ;; src/app/\(main\)/about/*) PAGES="$PAGES,about" ;; src/app/\(main\)/privacy/*) PAGES="$PAGES,privacy" ;; src/app/\(main\)/terms/*) PAGES="$PAGES,terms" ;; src/app/\(main\)/login/*) PAGES="$PAGES,login" ;; src/app/\(main\)/embed/*) PAGES="$PAGES,embed" ;; src/app/\(embed\)/*) PAGES="$PAGES,embed" ;; src/app/\(main\)/search/*) PAGES="$PAGES,search" ;; src/app/\(main\)/experts/*) PAGES="$PAGES,experts" ;; esac done <<< "$CHANGED" # Deduplicate and trim leading comma PAGES=$(echo "$PAGES" | tr ',' '\n' | sort -u | paste -sd ',' - | sed 's/^,//') if [ -z "$PAGES" ]; then echo "pages=none" >> "$GITHUB_OUTPUT" echo "frontend_changed=false" >> "$GITHUB_OUTPUT" else echo "pages=$PAGES" >> "$GITHUB_OUTPUT" echo "frontend_changed=true" >> "$GITHUB_OUTPUT" fi env: GH_TOKEN: ${{ github.token }} - name: Build application run: pnpm build - name: Run E2E tests run: pnpm exec playwright test --grep-invert "Visual regression" env: PLAYWRIGHT_BASE_URL: http://localhost:3000 QA_SCREENSHOTS: ${{ (github.event_name == 'push' || steps.affected.outputs.frontend_changed == 'true') && '1' || '0' }} AFFECTED_PAGES: ${{ steps.affected.outputs.pages || 'all' }} - name: Run visual regression (update baselines if missing) if: ${{ github.event_name == 'push' || steps.affected.outputs.pages != 'none' }} run: pnpm exec playwright test e2e/visual-regression.spec.ts --update-snapshots --reporter=line env: PLAYWRIGHT_BASE_URL: http://localhost:3000 AFFECTED_PAGES: ${{ steps.affected.outputs.pages || 'all' }} - name: Upload test report uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report path: e2e/playwright-report/ retention-days: 14 - name: Upload QA screenshots uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: qa-screenshots path: e2e/qa-screenshots/ retention-days: 14 - name: Publish screenshots to VPS if: ${{ !cancelled() && hashFiles('e2e/qa-screenshots/**') != '' }} env: VPS_HOST: ${{ secrets.VPS_HOST }} VPS_USER: ${{ secrets.VPS_USER }} VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }} PR_NUMBER: ${{ github.event.pull_request.number }} RUN_ID: ${{ github.run_id }} GH_TOKEN: ${{ github.token }} run: | # Set up SSH mkdir -p ~/.ssh echo "$VPS_SSH_KEY" > ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key ssh-keyscan -H "$VPS_HOST" >> ~/.ssh/known_hosts 2>/dev/null # Upload to VPS: /var/www/screenshots/pr--run-/ DEST="pr-${PR_NUMBER}-run-${RUN_ID}" ssh -i ~/.ssh/deploy_key "$VPS_USER@$VPS_HOST" "mkdir -p /var/www/screenshots/$DEST" scp -i ~/.ssh/deploy_key -r e2e/qa-screenshots/. "$VPS_USER@$VPS_HOST:/var/www/screenshots/$DEST/" # Build markdown comment body BASE_URL="https://sifa.id/screenshots/$DEST" BODY="## E2E Screenshots\n\nViewports tested: desktop-chrome, mobile-safari (iPhone 14), tablet (iPad Mini)\n\n" for f in $(ssh -i ~/.ssh/deploy_key "$VPS_USER@$VPS_HOST" "find /var/www/screenshots/$DEST -name '*.png' | sort"); do FILENAME=$(basename "$f") LABEL="${FILENAME%.png}" BODY+="### $LABEL\n![$LABEL]($BASE_URL/$FILENAME)\n\n" done # Post comment on the PR if [ -n "$PR_NUMBER" ]; then printf "%b" "$BODY" | gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file - fi - name: Upload test traces uses: actions/upload-artifact@v4 if: failure() with: name: playwright-traces path: e2e/test-results/ retention-days: 7 - name: Upload visual regression snapshots uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: visual-regression-snapshots path: e2e/visual-regression.spec.ts-snapshots/ retention-days: 14 - name: Upload visual regression diffs uses: actions/upload-artifact@v4 if: failure() with: name: visual-regression-diffs path: e2e/test-results/**/*-diff.png retention-days: 14