forked from coinbase/x402
-
Notifications
You must be signed in to change notification settings - Fork 0
348 lines (294 loc) · 12.5 KB
/
e2e_tests.yml
File metadata and controls
348 lines (294 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# E2E Test Workflow for x402 Protocol
#
# This workflow runs the full end-to-end test suite which validates the x402
# payment protocol across multiple languages (TypeScript, Go, Python),
# frameworks, transports (HTTP, MCP), and blockchain networks.
#
# Trigger: Manual only (workflow_dispatch). Select the branch to test from
# the "Use workflow from" dropdown in the GitHub Actions UI.
#
# ============================================================================
# REQUIRED GITHUB SECRETS
# ============================================================================
#
# The following secrets must be configured in the repository settings
# (Settings > Secrets and variables > Actions) for this workflow to succeed.
#
# --- Wallet Keys (Required) ---
#
# CLIENT_EVM_PRIVATE_KEY:
# EVM private key for the client wallet that makes payments.
# Must be funded with testnet USDC on Base Sepolia.
# Format: 0x-prefixed hex string (e.g., 0x4df9...)
#
# CLIENT_SVM_PRIVATE_KEY:
# Solana private key for the client wallet that makes payments.
# Must be funded with devnet USDC on Solana Devnet.
# Format: base58 encoded string
#
# FACILITATOR_EVM_PRIVATE_KEY:
# EVM private key for the facilitator wallet that verifies/settles payments.
# Format: 0x-prefixed hex string
#
# FACILITATOR_SVM_PRIVATE_KEY:
# Solana private key for the facilitator wallet that verifies/settles payments.
# Format: base58 encoded string
#
# SERVER_EVM_ADDRESS:
# EVM address where server payments are sent (payee address).
# Format: 0x-prefixed hex address (e.g., 0x122F...)
#
# SERVER_SVM_ADDRESS:
# Solana address where server payments are sent (payee address).
# Format: base58 encoded public key
#
# --- RPC URLs (Optional) ---
#
# These are optional. If not set, the test suite falls back to public defaults.
# Setting custom RPC URLs is recommended for reliability in CI.
#
# BASE_SEPOLIA_RPC_URL:
# Custom RPC URL for Base Sepolia testnet.
# Default: https://sepolia.base.org
#
# SOLANA_DEVNET_RPC_URL:
# Custom RPC URL for Solana Devnet.
# Default: https://api.devnet.solana.com
#
# ============================================================================
name: E2E Tests
on:
workflow_dispatch:
inputs:
branch:
description: "Branch to test (leave empty to use the branch selected above)"
required: false
default: ""
transport:
description: "Transports to test (comma-separated: http,mcp)"
required: false
default: "http"
facilitators:
description: "Facilitators to test (comma-separated: go,typescript,python)"
required: false
default: "go,typescript,python"
servers:
description: "Servers to test (comma-separated: express,hono,gin,flask,fastapi,next)"
required: false
clients:
description: "Clients to test (comma-separated: axios,fetch,go-http,httpx,requests)"
required: false
families:
description: "Protocol families (comma-separated: evm,svm)"
required: false
minimize:
description: "Use coverage-based test minimization (--min)"
type: boolean
default: true
verbose:
description: "Enable verbose logging"
type: boolean
default: true
permissions:
contents: read
pull-requests: write
jobs:
e2e-tests:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
# --- Validate Required Secrets ---
# Fail fast before installing anything if secrets are missing.
- name: Validate required secrets
run: |
MISSING=()
if [ -z "${{ secrets.CLIENT_EVM_PRIVATE_KEY }}" ]; then MISSING+=("CLIENT_EVM_PRIVATE_KEY"); fi
if [ -z "${{ secrets.CLIENT_SVM_PRIVATE_KEY }}" ]; then MISSING+=("CLIENT_SVM_PRIVATE_KEY"); fi
if [ -z "${{ secrets.FACILITATOR_EVM_PRIVATE_KEY }}" ]; then MISSING+=("FACILITATOR_EVM_PRIVATE_KEY"); fi
if [ -z "${{ secrets.FACILITATOR_SVM_PRIVATE_KEY }}" ]; then MISSING+=("FACILITATOR_SVM_PRIVATE_KEY"); fi
if [ -z "${{ secrets.SERVER_EVM_ADDRESS }}" ]; then MISSING+=("SERVER_EVM_ADDRESS"); fi
if [ -z "${{ secrets.SERVER_SVM_ADDRESS }}" ]; then MISSING+=("SERVER_SVM_ADDRESS"); fi
if [ ${#MISSING[@]} -gt 0 ]; then
echo "::error::Missing required secrets: ${MISSING[*]}"
echo ""
echo "The following secrets must be configured in Settings > Secrets and variables > Actions:"
for secret in "${MISSING[@]}"; do
echo " - $secret"
done
exit 1
fi
echo "All required secrets are present."
- uses: actions/checkout@v4
with:
ref: ${{ inputs.branch || github.ref }}
# --- Language Runtimes ---
- name: Setup pnpm
uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: |
e2e/pnpm-lock.yaml
typescript/pnpm-lock.yaml
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
cache-dependency-path: |
go/go.sum
e2e/**/go.sum
- name: Setup Python (uv)
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
- name: Install Python
run: uv python install 3.13
# --- Install Dependencies ---
- name: Install and build TypeScript SDK
working-directory: ./typescript
run: |
pnpm install --frozen-lockfile
pnpm build
- name: Install E2E dependencies
working-directory: ./e2e
run: pnpm install --frozen-lockfile
# --- Build & Setup E2E Components ---
# setup.sh runs install.sh and build.sh for all servers, clients, and
# facilitators (TypeScript, Go, and Python components).
- name: Setup E2E components
working-directory: ./e2e
run: ./setup.sh -v
# --- Write Environment File ---
- name: Create .env file
working-directory: ./e2e
run: |
{
echo "SERVER_EVM_ADDRESS=${{ secrets.SERVER_EVM_ADDRESS }}"
echo "SERVER_SVM_ADDRESS=${{ secrets.SERVER_SVM_ADDRESS }}"
echo "CLIENT_EVM_PRIVATE_KEY=${{ secrets.CLIENT_EVM_PRIVATE_KEY }}"
echo "CLIENT_SVM_PRIVATE_KEY=${{ secrets.CLIENT_SVM_PRIVATE_KEY }}"
echo "FACILITATOR_EVM_PRIVATE_KEY=${{ secrets.FACILITATOR_EVM_PRIVATE_KEY }}"
echo "FACILITATOR_SVM_PRIVATE_KEY=${{ secrets.FACILITATOR_SVM_PRIVATE_KEY }}"
} > .env
# --- Run E2E Tests ---
# Uses programmatic mode (CLI filter flags) to bypass the interactive
# prompt that `pnpm test` opens by default. Any --flag triggers
# programmatic mode in the test runner (see e2e/src/cli/args.ts).
- name: Run E2E tests
id: e2e
working-directory: ./e2e
run: |
ARGS=""
# Apply workflow_dispatch inputs (or defaults)
ARGS="$ARGS --transport=${{ inputs.transport || 'http' }}"
ARGS="$ARGS --facilitators=${{ inputs.facilitators || 'go,python,typescript' }}"
if [ -n "${{ inputs.servers }}" ]; then
ARGS="$ARGS --servers=${{ inputs.servers }}"
fi
if [ -n "${{ inputs.clients }}" ]; then
ARGS="$ARGS --clients=${{ inputs.clients }}"
fi
if [ -n "${{ inputs.families }}" ]; then
ARGS="$ARGS --families=${{ inputs.families }}"
fi
# Default to --min and -v when inputs are not provided (e.g. push trigger)
MINIMIZE="${{ inputs.minimize }}"
if [ "$MINIMIZE" = "true" ] || [ -z "$MINIMIZE" ]; then
ARGS="$ARGS --min"
fi
VERBOSE="${{ inputs.verbose }}"
if [ "$VERBOSE" = "true" ]; then
ARGS="$ARGS -v"
fi
ARGS="$ARGS --parallel"
ARGS="$ARGS --log-file=e2e-results.log --output-json=e2e-results.json"
echo "Running: pnpm test $ARGS"
# Run tests but capture exit code so we can still comment on the PR
set +e
pnpm test $ARGS
TEST_EXIT_CODE=$?
set -e
echo "test_exit_code=$TEST_EXIT_CODE" >> "$GITHUB_OUTPUT"
exit 0
# --- Upload Results ---
- name: Upload test log
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-test-log
path: |
e2e/e2e-results.log
e2e/e2e-results.json
retention-days: 14
# --- Comment on PR ---
# Find any open PR for the tested branch, delete previous E2E comments
# from this workflow, and post a new comment with results.
- name: Comment results on PR
if: always()
env:
GH_TOKEN: ${{ github.token }}
TEST_EXIT_CODE: ${{ steps.e2e.outputs.test_exit_code }}
run: |
BRANCH="${{ inputs.branch || github.ref_name }}"
# Find open PR for this branch
PR_NUMBER=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null || true)
if [ -z "$PR_NUMBER" ]; then
echo "No open PR found for branch '$BRANCH', skipping comment."
exit 0
fi
echo "Found PR #$PR_NUMBER for branch '$BRANCH'"
# Delete previous E2E comments from this workflow
COMMENT_IDS=$(gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
--paginate --jq '.[] | select(.body | contains("<!-- x402-e2e-results -->")) | .id' 2>/dev/null || true)
for CID in $COMMENT_IDS; do
echo "Deleting previous E2E comment $CID"
gh api --method DELETE "repos/${{ github.repository }}/issues/comments/$CID" 2>/dev/null || true
done
# Build comment body from JSON results
COMMENT_BODY="<!-- x402-e2e-results -->"$'\n'
if [ -f e2e/e2e-results.json ]; then
TOTAL=$(jq '.summary.total' e2e/e2e-results.json)
PASSED=$(jq '.summary.passed' e2e/e2e-results.json)
FAILED=$(jq '.summary.failed' e2e/e2e-results.json)
NETWORK=$(jq -r '.summary.networkMode' e2e/e2e-results.json)
if [ "$FAILED" = "0" ]; then
COMMENT_BODY+="## :white_check_mark: E2E Tests Passed"$'\n\n'
else
COMMENT_BODY+="## :x: E2E Tests Failed"$'\n\n'
fi
COMMENT_BODY+="**Network:** ${NETWORK} | **Total:** ${TOTAL} | **Passed:** ${PASSED} | **Failed:** ${FAILED}"$'\n\n'
# Failed test details (only shown when there are failures)
FAILED_DETAILS=$(jq -r '.results[] | select(.passed == false) | "| \(.testNumber) | \(.client) | \(.server) | \(.endpoint) | \(.facilitator) | \(.error // "Unknown") |"' e2e/e2e-results.json)
if [ -n "$FAILED_DETAILS" ]; then
COMMENT_BODY+="### Failed Tests"$'\n'
COMMENT_BODY+="| Test | Client | Server | Endpoint | Facilitator | Error |"$'\n'
COMMENT_BODY+="|---|---|---|---|---|---|"$'\n'
COMMENT_BODY+="${FAILED_DETAILS}"$'\n\n'
fi
else
# No JSON output — tests likely crashed before producing results
if [ "$TEST_EXIT_CODE" != "0" ]; then
COMMENT_BODY+="## :x: E2E Tests Failed"$'\n\n'
COMMENT_BODY+="Test runner exited with code ${TEST_EXIT_CODE} before producing results."$'\n'
COMMENT_BODY+="Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details."$'\n'
else
COMMENT_BODY+="## :warning: E2E Tests — No Results"$'\n\n'
COMMENT_BODY+="No structured output was produced. Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details."$'\n'
fi
fi
COMMENT_BODY+=$'\n'"<sub>Run: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})</sub>"$'\n'
# Post new comment
gh pr comment "$PR_NUMBER" --body "$COMMENT_BODY"
echo "Posted E2E results comment on PR #$PR_NUMBER"
# --- Fail the workflow if tests failed ---
- name: Check test result
if: always()
run: |
if [ "${{ steps.e2e.outputs.test_exit_code }}" != "0" ]; then
echo "E2E tests failed with exit code ${{ steps.e2e.outputs.test_exit_code }}"
exit 1
fi