E2E Daily Test (Payment + Happy Path) #46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: E2E Daily Test (Payment + Happy Path) | |
| # Full production E2E: buy credits via real Stripe, then use them for karaoke generation. | |
| # Replaces the previous e2e-happy-path.yml with an additional payment stage. | |
| on: | |
| schedule: | |
| - cron: '0 6 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| debug_mode: | |
| description: 'Enable debug mode (slower, more screenshots)' | |
| required: false | |
| default: 'false' | |
| type: boolean | |
| browser: | |
| description: 'Browser to test with' | |
| required: false | |
| default: 'chromium' | |
| type: choice | |
| options: | |
| - chromium | |
| - firefox | |
| - webkit | |
| skip_payment: | |
| description: 'Skip Stage 1 (payment) — use admin-granted credits for happy path only' | |
| required: false | |
| default: 'false' | |
| type: boolean | |
| use_test_token: | |
| description: 'Skip signup entirely — use E2E_TEST_TOKEN (for quick iterations)' | |
| required: false | |
| default: 'false' | |
| type: boolean | |
| permissions: | |
| contents: read | |
| jobs: | |
| # ========================================================================= | |
| # Stage 1: Purchase credits via real Stripe Checkout | |
| # ========================================================================= | |
| e2e-credit-purchase: | |
| name: "Stage 1: Credit Purchase" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| if: ${{ github.event.inputs.skip_payment != 'true' && github.event.inputs.use_test_token != 'true' }} | |
| outputs: | |
| session_token: ${{ steps.extract_token.outputs.token }} | |
| result: ${{ steps.test_result.outputs.result }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| working-directory: frontend | |
| run: npm ci --legacy-peer-deps | |
| - name: Install Playwright browsers | |
| working-directory: frontend | |
| run: npx playwright install --with-deps ${{ inputs.browser || 'chromium' }} | |
| - name: Create test-results directory | |
| working-directory: frontend | |
| run: mkdir -p test-results | |
| - name: Ensure testmail.app domain is allowlisted | |
| run: | | |
| curl -sf -X POST \ | |
| "https://api.nomadkaraoke.com/api/admin/rate-limits/blocklists/allowlisted-domains" \ | |
| -H "X-Admin-Token: ${{ secrets.E2E_ADMIN_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"domain": "inbox.testmail.app"}' || echo "Warning: allowlist may already exist" | |
| - name: Run Credit Purchase Test | |
| id: run_test | |
| working-directory: frontend | |
| env: | |
| TESTMAIL_API_KEY: ${{ secrets.TESTMAIL_API_KEY }} | |
| TESTMAIL_NAMESPACE: ${{ secrets.TESTMAIL_NAMESPACE }} | |
| E2E_ADMIN_TOKEN: ${{ secrets.E2E_ADMIN_TOKEN }} | |
| E2E_STRIPE_CARD_NUMBER: ${{ secrets.E2E_STRIPE_CARD_NUMBER }} | |
| E2E_STRIPE_CARD_EXPIRY: ${{ secrets.E2E_STRIPE_CARD_EXPIRY }} | |
| E2E_STRIPE_CARD_CVC: ${{ secrets.E2E_STRIPE_CARD_CVC }} | |
| E2E_STRIPE_CARDHOLDER_NAME: ${{ secrets.E2E_STRIPE_CARDHOLDER_NAME }} | |
| DEBUG_MODE: ${{ inputs.debug_mode || 'false' }} | |
| run: | | |
| echo "=== Stage 1: Credit Purchase via Real Stripe ===" | |
| echo "Time: $(date)" | |
| npx playwright test credit-purchase-real.spec.ts \ | |
| --config=playwright.production.config.ts \ | |
| --project=${{ inputs.browser || 'chromium' }} \ | |
| --reporter=list \ | |
| --timeout=600000 \ | |
| 2>&1 | tee test-output-payment.log | |
| # Capture Playwright's real exit code, not tee's (0) — the | |
| # authoritative pass/fail signal. See Stage 2 for rationale. | |
| echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT" | |
| continue-on-error: true | |
| - name: Check test result | |
| id: test_result | |
| run: | | |
| if [ "${{ steps.run_test.outputs.exit_code }}" = "0" ]; then | |
| echo "result=success" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "result=failure" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Extract session token | |
| id: extract_token | |
| if: steps.test_result.outputs.result == 'success' | |
| working-directory: frontend | |
| run: | | |
| if [ -f test-results/e2e-session-token.txt ]; then | |
| TOKEN=$(cat test-results/e2e-session-token.txt) | |
| echo "::add-mask::$TOKEN" | |
| echo "token=$TOKEN" >> $GITHUB_OUTPUT | |
| echo "Session token extracted for Stage 2" | |
| else | |
| echo "No session token file found" | |
| echo "token=" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Upload artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-payment-results-${{ github.run_number }} | |
| path: | | |
| frontend/test-results/ | |
| frontend/test-output-payment.log | |
| retention-days: 30 | |
| - name: Discord notification (failure) | |
| if: steps.test_result.outputs.result == 'failure' | |
| run: | | |
| curl -s -H "Content-Type: application/json" \ | |
| -d "{\"embeds\": [{\"title\": \"🚨 E2E Daily Test FAILED — Stage 1: Credit Purchase\", \"description\": \"Payment flow is broken. Customers may not be receiving credits.\n\n**Run:** #${{ github.run_number }}\n**Details:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\", \"color\": 15158332}]}" \ | |
| "${{ secrets.DISCORD_ALERT_WEBHOOK_URL }}" | |
| - name: Fail if test failed | |
| if: steps.test_result.outputs.result == 'failure' | |
| run: exit 1 | |
| # ========================================================================= | |
| # Stage 2: Happy Path (uses credit from Stage 1) | |
| # ========================================================================= | |
| e2e-happy-path: | |
| name: "Stage 2: Happy Path" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| needs: e2e-credit-purchase | |
| if: | | |
| always() && ( | |
| needs.e2e-credit-purchase.result == 'success' || | |
| github.event.inputs.skip_payment == 'true' || | |
| github.event.inputs.use_test_token == 'true' | |
| ) | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: 'npm' | |
| cache-dependency-path: frontend/package-lock.json | |
| - name: Install dependencies | |
| working-directory: frontend | |
| run: npm ci --legacy-peer-deps | |
| - name: Install Playwright browsers | |
| working-directory: frontend | |
| run: npx playwright install --with-deps ${{ inputs.browser || 'chromium' }} | |
| - name: Create test-results directory | |
| working-directory: frontend | |
| run: mkdir -p test-results | |
| - name: Ensure testmail.app domain is allowlisted | |
| run: | | |
| curl -sf -X POST \ | |
| "https://api.nomadkaraoke.com/api/admin/rate-limits/blocklists/allowlisted-domains" \ | |
| -H "X-Admin-Token: ${{ secrets.E2E_ADMIN_TOKEN }}" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"domain": "inbox.testmail.app"}' || echo "Warning: allowlist may already exist" | |
| - name: Refresh E2E session token (when use_test_token enabled) | |
| id: refresh_token | |
| if: github.event.inputs.use_test_token == 'true' | |
| run: | | |
| RESPONSE=$(curl -sf -X POST \ | |
| "https://api.nomadkaraoke.com/api/admin/users/e2e-test-runner@nomadkaraoke.com/impersonate" \ | |
| -H "X-Admin-Token: ${{ secrets.E2E_ADMIN_TOKEN }}" \ | |
| -H "Content-Type: application/json") | |
| TOKEN=$(echo "$RESPONSE" | python3 -c 'import json,sys; print(json.load(sys.stdin)["session_token"])') | |
| echo "::add-mask::$TOKEN" | |
| echo "token=$TOKEN" >> $GITHUB_OUTPUT | |
| - name: Run Happy Path Test | |
| id: run_test | |
| working-directory: frontend | |
| env: | |
| TESTMAIL_API_KEY: ${{ secrets.TESTMAIL_API_KEY }} | |
| TESTMAIL_NAMESPACE: ${{ secrets.TESTMAIL_NAMESPACE }} | |
| E2E_TEST_TOKEN: ${{ steps.refresh_token.outputs.token || needs.e2e-credit-purchase.outputs.session_token || '' }} | |
| E2E_ADMIN_TOKEN: ${{ secrets.E2E_ADMIN_TOKEN }} | |
| E2E_BYPASS_KEY: ${{ secrets.E2E_BYPASS_KEY }} | |
| DEBUG_MODE: ${{ inputs.debug_mode || 'false' }} | |
| run: | | |
| echo "=== Stage 2: Happy Path ===" | |
| echo "Time: $(date)" | |
| TOKEN_SOURCE="none" | |
| if [ -n "${{ steps.refresh_token.outputs.token }}" ]; then | |
| TOKEN_SOURCE="use_test_token (impersonation)" | |
| elif [ -n "${{ needs.e2e-credit-purchase.outputs.session_token }}" ]; then | |
| TOKEN_SOURCE="Stage 1 (credit purchase)" | |
| fi | |
| echo "Token source: $TOKEN_SOURCE" | |
| npx playwright test happy-path-real-user.spec.ts \ | |
| --config=playwright.production.config.ts \ | |
| --project=${{ inputs.browser || 'chromium' }} \ | |
| --reporter=list \ | |
| --timeout=3600000 \ | |
| 2>&1 | tee test-output-happy-path.log | |
| # Capture Playwright's real exit code, not tee's (0). This is the | |
| # authoritative pass/fail signal — grepping the log for "failed" is | |
| # fragile because benign retry warnings (e.g. "goto attempt 1/3 | |
| # failed") contain that substring and produce false alarms. | |
| echo "exit_code=${PIPESTATUS[0]}" >> "$GITHUB_OUTPUT" | |
| continue-on-error: true | |
| - name: Check test result | |
| id: test_result | |
| run: | | |
| if [ "${{ steps.run_test.outputs.exit_code }}" = "0" ]; then | |
| echo "result=success" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "result=failure" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Upload test artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-happy-path-results-${{ github.run_number }} | |
| path: | | |
| frontend/test-results/ | |
| frontend/playwright-report/ | |
| frontend/test-output-happy-path.log | |
| retention-days: 30 | |
| - name: Upload video recordings | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-happy-path-videos-${{ github.run_number }} | |
| path: frontend/test-results/**/*.webm | |
| retention-days: 30 | |
| if-no-files-found: ignore | |
| - name: Create summary | |
| if: always() | |
| run: | | |
| STAGE1="${{ needs.e2e-credit-purchase.result || 'skipped' }}" | |
| STAGE2="${{ steps.test_result.outputs.result || 'unknown' }}" | |
| echo "## E2E Daily Test Results" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Stage | Result |" >> $GITHUB_STEP_SUMMARY | |
| echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Stage 1: Credit Purchase | ${STAGE1} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Stage 2: Happy Path | ${STAGE2} |" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY | |
| echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY | |
| echo "| Run | #${{ github.run_number }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Trigger | ${{ github.event_name }} |" >> $GITHUB_STEP_SUMMARY | |
| echo "| Browser | ${{ inputs.browser || 'chromium' }} |" >> $GITHUB_STEP_SUMMARY | |
| - name: Discord notification (failure) | |
| if: steps.test_result.outputs.result == 'failure' | |
| run: | | |
| curl -s -H "Content-Type: application/json" \ | |
| -d "{\"embeds\": [{\"title\": \"🚨 E2E Daily Test FAILED — Stage 2: Happy Path\", \"description\": \"Karaoke generation flow is broken.\n\n**Run:** #${{ github.run_number }}\n**Details:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\", \"color\": 15158332}]}" \ | |
| "${{ secrets.DISCORD_ALERT_WEBHOOK_URL }}" | |
| - name: Fail if test failed | |
| if: steps.test_result.outputs.result == 'failure' | |
| run: exit 1 | |
| # ========================================================================= | |
| # Final: Send notifications | |
| # ========================================================================= | |
| notify: | |
| name: "Notify" | |
| runs-on: ubuntu-latest | |
| needs: [e2e-credit-purchase, e2e-happy-path] | |
| if: always() | |
| steps: | |
| - name: Discord notification (success) | |
| if: needs.e2e-happy-path.result == 'success' | |
| run: | | |
| STAGE1="${{ needs.e2e-credit-purchase.result || 'skipped' }}" | |
| curl -s -H "Content-Type: application/json" \ | |
| -d "{\"embeds\": [{\"title\": \"✅ E2E Daily Test PASSED\", \"description\": \"Payment: ${STAGE1} | Happy Path: success\n\n**Run:** #${{ github.run_number }}\", \"color\": 3066993}]}" \ | |
| "${{ secrets.DISCORD_ALERT_WEBHOOK_URL }}" | |
| - name: Send email notification | |
| if: github.event_name == 'schedule' | |
| uses: dawidd6/action-send-mail@v3 | |
| with: | |
| server_address: smtp.postmarkapp.com | |
| server_port: 587 | |
| username: ${{ secrets.POSTMARK_SERVER_TOKEN }} | |
| password: ${{ secrets.POSTMARK_SERVER_TOKEN }} | |
| subject: "E2E Daily Test ${{ needs.e2e-happy-path.result == 'success' && 'PASSED' || 'FAILED' }} - Run #${{ github.run_number }}" | |
| to: andrew@nomadkaraoke.com | |
| from: "Karaoke Gen CI <noreply@nomadkaraoke.com>" | |
| body: | | |
| E2E Daily Test Results | |
| ====================== | |
| Stage 1 (Credit Purchase): ${{ needs.e2e-credit-purchase.result || 'skipped' }} | |
| Stage 2 (Happy Path): ${{ needs.e2e-happy-path.result || 'skipped' }} | |
| Run: #${{ github.run_number }} | |
| Trigger: ${{ github.event_name }} | |
| View details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} |