diff --git a/.github/workflows/e2e-examples.yml b/.github/workflows/e2e-examples.yml new file mode 100644 index 000000000..7a295b99f --- /dev/null +++ b/.github/workflows/e2e-examples.yml @@ -0,0 +1,194 @@ +name: E2E Examples + +on: + pull_request: + types: [opened, reopened, synchronize] + branches: [main] + +concurrency: + group: e2e-examples-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + # ── Detect which examples need testing ────────────────────────────────────── + detect-changes: + name: Detect affected examples + runs-on: ubuntu-latest + timeout-minutes: 2 + outputs: + matrix: ${{ steps.build-matrix.outputs.matrix }} + has-examples: ${{ steps.build-matrix.outputs.has-examples }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Build test matrix + id: build-matrix + run: | + BASE_SHA=${{ github.event.pull_request.base.sha }} + HEAD_SHA=${{ github.event.pull_request.head.sha }} + + CHANGED=$(git diff --name-only "$BASE_SHA"..."$HEAD_SHA") + + mapfile -t ALL_EXAMPLES < <(jq -r '.[] | select(.status=="active") | .name' e2e/examples.json) + + # Check if shared packages changed — if so, run all examples + SHARED_CHANGED=false + while IFS= read -r file; do + case "$file" in + packages/widget/*|packages/widget-provider*/**|packages/wallet-management/*|e2e/*) + SHARED_CHANGED=true ;; + pnpm-workspace.yaml|pnpm-lock.yaml|package.json) + SHARED_CHANGED=true ;; + esac + done <<< "$CHANGED" + + AFFECTED=() + if [ "$SHARED_CHANGED" = "true" ]; then + AFFECTED=("${ALL_EXAMPLES[@]}") + else + for name in "${ALL_EXAMPLES[@]}"; do + if echo "$CHANGED" | grep -q "^examples/${name}/"; then + AFFECTED+=("$name") + fi + done + fi + + if [ ${#AFFECTED[@]} -eq 0 ]; then + echo "matrix=[]" >> "$GITHUB_OUTPUT" + echo "has-examples=false" >> "$GITHUB_OUTPUT" + echo "No affected examples detected." + else + JSON=$(printf '%s\n' "${AFFECTED[@]}" | jq -R . | jq -sc .) + echo "matrix=$JSON" >> "$GITHUB_OUTPUT" + echo "has-examples=true" >> "$GITHUB_OUTPUT" + echo "Affected examples: $JSON" + fi + + # ── Run tests per example ──────────────────────────────────────────────────── + test: + name: E2E ${{ matrix.example }} + needs: detect-changes + if: needs.detect-changes.outputs.has-examples == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + example: ${{ fromJson(needs.detect-changes.outputs.matrix) }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install dependencies + uses: ./.github/actions/pnpm-install + + - name: Build workspace packages + run: pnpm -r --parallel --filter './packages/**' --filter !'*-playground-*' --filter !'*-embedded' build + + - name: Resolve example metadata + id: meta + run: | + NAME="${{ matrix.example }}" + Q='.[] | select(.name==$n)' + echo "pkg=$(jq -r --arg n "$NAME" "$Q | .pkg" e2e/examples.json)" >> "$GITHUB_OUTPUT" + echo "build=$(jq -r --arg n "$NAME" "$Q | .buildCmd" e2e/examples.json)" >> "$GITHUB_OUTPUT" + echo "serve=$(jq -r --arg n "$NAME" "$Q | .serveCmd" e2e/examples.json)" >> "$GITHUB_OUTPUT" + echo "port=$(jq -r --arg n "$NAME" "$Q | .port" e2e/examples.json)" >> "$GITHUB_OUTPUT" + echo "serve_env=$(jq -r --arg n "$NAME" "$Q | .serveEnv // {} | to_entries | map(.key+\"=\"+.value) | join(\" \")" e2e/examples.json)" >> "$GITHUB_OUTPUT" + + - name: Build example + run: | + if [ "${{ steps.meta.outputs.build }}" = "vite-build" ]; then + pnpm --filter "${{ steps.meta.outputs.pkg }}" exec vite build + else + pnpm --filter "${{ steps.meta.outputs.pkg }}" build + fi + + - name: Start example server + run: | + SERVE_ENV="${{ steps.meta.outputs.serve_env }}" + if [ -n "$SERVE_ENV" ]; then + env $SERVE_ENV pnpm --filter "${{ steps.meta.outputs.pkg }}" "${{ steps.meta.outputs.serve }}" & + else + pnpm --filter "${{ steps.meta.outputs.pkg }}" "${{ steps.meta.outputs.serve }}" & + fi + + URL="http://localhost:${{ steps.meta.outputs.port }}" + for i in $(seq 1 60); do + if curl -sf "$URL" > /dev/null 2>&1; then + echo "Server ready at $URL after ${i}s" + exit 0 + fi + sleep 1 + done + echo "::error::Server failed to start within 60s at $URL" + exit 1 + + - name: Install Playwright browsers + run: pnpm --filter @lifi/widget-e2e exec playwright install chromium --with-deps + + - name: Run example tests + run: | + pnpm --filter @lifi/widget-e2e exec playwright test \ + --config playwright.examples.config.ts \ + --project "${{ matrix.example }}" + + - name: Upload Playwright report + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: playwright-report-${{ matrix.example }} + path: e2e/playwright-report-examples/ + retention-days: 7 + + # ── Summarise failures as a single PR comment ──────────────────────────────── + report: + name: Report results + needs: [detect-changes, test] + if: always() && needs.detect-changes.outputs.has-examples == 'true' && needs.test.result != 'skipped' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Find failed examples + if: needs.test.result == 'failure' + id: failures + env: + GH_TOKEN: ${{ github.token }} + run: | + FAILED=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs" \ + --paginate \ + --jq '[.jobs[] | select(.name | test("^E2E ")) | select(.conclusion == "failure") | .name | ltrimstr("E2E ")] | sort | map("- `"+.+"`") | join("\n")') + echo "list<> "$GITHUB_OUTPUT" + echo "$FAILED" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Post success comment + if: needs.test.result == 'success' + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + with: + header: e2e-results + message: | + ## E2E Examples — all passed + + All examples passed in the [latest run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). + + - name: Post failure comment + if: needs.test.result == 'failure' + uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4 + with: + header: e2e-results + message: | + ## E2E Examples — failures + + The following example(s) failed: + + ${{ steps.failures.outputs.list }} + + See the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for Playwright reports and logs. diff --git a/e2e/.gitignore b/e2e/.gitignore new file mode 100644 index 000000000..b140af747 --- /dev/null +++ b/e2e/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +test-results/ +playwright-report/ +playwright-report-examples/ +.auth/ +.env.test +*.local diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 000000000..ed1749944 --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,180 @@ +# LI.FI Widget — E2E Test Suite + +Playwright TypeScript E2E tests for the LI.FI Widget. Two test suites share this directory: + +| Suite | Config | What it tests | +|---|---|---| +| **Playground** | `playwright.config.ts` | Widget playground (`packages/widget-playground-vite`) | +| **Examples** | `playwright.examples.config.ts` | All example apps in `examples/` | + +--- + +## Setup + +Dependencies are installed automatically with `pnpm install` from the repo root. + +```bash +# Install Playwright browsers (once, or after Playwright version bumps) +pnpm --filter @lifi/widget-e2e exec playwright install chromium +``` + +### Prerequisite: build workspace packages + +Examples consume `@lifi/widget`, `@lifi/wallet-management`, etc. as workspace deps that resolve to `packages/*/dist`. Stale dist artifacts cause silent runtime failures (e.g. `Class extends value undefined …` when the widget mounts), so build packages before running the example suite: + +```bash +# From repo root +pnpm -r --parallel --filter './packages/**' --filter '!*-playground-*' --filter '!*-embedded' build +``` + +CI runs the same command before tests. + +--- + +## Playground Tests + +The playground must be running before tests can execute. + +```bash +# Terminal 1 +pnpm dev # starts widget-playground-vite on http://localhost:3000 + +# Terminal 2 — from repo root +pnpm smoketest # playground smoke tests +``` + +From the `e2e/` directory: + +| Command | Description | +|---|---| +| `pnpm test` | Full test suite | +| `pnpm smoketest` | Smoke tests only | +| `pnpm test:headed` | Visible browser | +| `pnpm test:debug` | Playwright debug inspector | +| `pnpm test:ui` | Playwright interactive UI | +| `pnpm report` | Open last HTML report | + +--- + +## Example Tests + +Each example is built, served, and tested in isolation. The local scripts handle the full lifecycle. + +```bash +# From repo root + +# Single example — build → serve → test → kill +pnpm test:example vite +pnpm test:example tanstack-router +pnpm test:example nft-checkout + +# All 17 active examples sequentially +pnpm test:examples +``` + +Reports land in `e2e/playwright-report-examples/`. + +### Active examples and their profiles + +| Profile | Examples | What makes it different | +|---|---|---| +| `standard` | vite, connectkit, privy, privy-ethers, rainbowkit, reown, svelte, zustand-widget-config, vue, nextjs, nextjs15, remix, react-router-7 | Widget at `/`, Exchange heading | +| `routed` | tanstack-router | Widget at a custom route (`/widget`) | +| `iframe` | vite-iframe, vite-iframe-wagmi | Widget inside `