Skip to content

E2E Testing

Guide for running and debugging fullsend admin e2e tests locally and in CI.

Related ADRs: 0040 (org pool), 0060 (cross-org mint), 0009 (pull_request_target security model for shims; e2e uses a separate gate pattern documented below).

Historical ADRs 0010 (browser session) and 0039 (2FA) are superseded for CI by cross-org mint auth (#2155); local runs no longer use Playwright or stored sessions.

Prerequisites

Before running e2e locally or in CI:

  1. Pool orgs (halfsend-01halfsend-12) provisioned per Pool org provisioning below
  2. Mint deployed with e2e role enrolled and ALLOWED_ORGS including fullsend-ai
  3. CI only: pool orgs with FULLSEND_FOREIGN_E2E_REPOS authorizing fullsend-ai/fullsend
  4. Local only: gh auth login (or GH_TOKEN / GITHUB_TOKEN) with admin access on pool orgs

Local runs

  1. Authenticate as an admin on the pool orgs (gh auth login --web, or export GH_TOKEN).
  2. Run tests (uses gh auth token, GH_TOKEN, or GITHUB_TOKEN):
bash
make e2e-test

Optional environment variables:

VariablePurpose
GH_TOKEN / GITHUB_TOKENOverride token source for local runs
FULLSEND_MINT_URLOverride mint endpoint (default: hosted public mint, same as fullsend admin --mint-url)
E2E_LOCK_TIMEOUTMax wait for a free pool org (default 10m)
E2E_GCP_PROJECT_IDGCP project for inference setup (github setup --inference-project)

Behaviour tests use the same pool orgs but install via fullsend github setup (per-repo) instead of fullsend admin install. See behaviour-testing.md and behaviour-drivers.md.

Tests acquire an exclusive lock on one org from the pool (halfsend-01halfsend-12) — see ADR 0040.

Shared pool, CLI, and cleanup helpers used by both admin e2e and behaviour tests live in pkg/e2etest/. Admin-specific test logic remains in e2e/admin/.

CI runs

In GitHub Actions, tests mint a cross-org installation token via the mint service:

  1. Workflow requests a GHA OIDC token (id-token: write)
  2. mintclient.MintToken POSTs to {FULLSEND_MINT_URL or hosted default}/v1/token with {role: "e2e", target_org: "<pool org>"} (repos omitted for installation-wide access)
  3. Mint verifies the caller against FULLSEND_FOREIGN_E2E_REPOS on the target org (ADR 0060)

Required repository secrets:

SecretPurpose
E2E_GCP_WIF_PROVIDERGCP WIF provider (inference / auxiliary GCP access)
E2E_GCP_SERVICE_ACCOUNTGCP service account for WIF
E2E_GCP_PROJECT_IDGCP project ID for inference secrets (github setup --inference-project)
TEST_CLOUDFLARE_ACCOUNT_IDCloudflare account ID for CF mint behaviour-test deploys (mapped to env CLOUDFLARE_ACCOUNT_ID in the behaviour job)
TEST_CLOUDFLARE_API_TOKENTest-only Cloudflare API token for Wrangler against Worker mint-test (mapped to env CLOUDFLARE_API_TOKEN; distinct from site-deploy CLOUDFLARE_*)

Mint URL uses the hosted public endpoint by default (same as fullsend admin --mint-url). Override with org/repo variable FULLSEND_MINT_URL if needed; no separate e2e secret.

Cloudflare Worker mint BT credentials

The behaviour job wires TEST_CLOUDFLARE_* into Wrangler’s standard CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN env names so CF mint BT (#5109) can upload versions of Worker mint-test. These secrets must not reuse the production site-deploy CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN used by site-deploy.yml (Worker site).

Prefer wrangler versions upload --name=mint-test --preview-alias=… so runs use preview URLs (<alias>-mint-test.<subdomain>.workers.dev) rather than inventing new Worker names or relying on the production mint-test.…workers.dev route (which may stay disabled). Cloudflare Account API tokens cannot currently attach Workers Scripts permissions under a Specified-Workers-only policy; operators use a dedicated Workers Edit token (for example fullsend-ai/fullsend-mint-test) that is separate from site-deploy credentials and intended only for this test path.

Behaviour tests and per-repo mint enrollment

Behaviour tests install fullsend in per-repo mode (fullsend github setup). Triage workflows on the pool org's test-repo mint same-org triage tokens from vendored reusable workflows; that requires per-repo mint enrollment (PER_REPO_WIF_REPOS). The install driver does not run mint enroll — pool org test-repo repos must be enrolled once by a GCP admin on the hosted mint project.

Inference (E2E_GCP_PROJECT_ID) and mint (it-gcp-konflux-dev-fullsend for the hosted mint) may be different GCP projects. The behaviour install driver runs fullsend inference provision <org>/test-repo using CI credentials on the inference project (same access model as admin e2e), then passes the repo-scoped WIF provider to github setup. E2E_GCP_WIF_PROVIDER authenticates the CI job itself; it is not written to pool org repos.

The CI service account needs inference-provision IAM on E2E_GCP_PROJECT_ID:

IAM rolePurpose
roles/iam.workloadIdentityPoolAdminCreate/update repo-scoped inference WIF providers
roles/resourcemanager.projectIamAdminGrant roles/aiplatform.user to repo WIF principals

One-time enrollment for all pool orgs (idempotent):

bash
export GCP_PROJECT=it-gcp-konflux-dev-fullsend
for i in $(seq -w 1 12); do
  fullsend mint enroll "halfsend-${i}/test-repo" --project="$GCP_PROJECT" --region=us-central1
done

Re-run enrollment when adding a new pool org or after mint infrastructure changes that drop PER_REPO_WIF_REPOS entries. See mint-administration.md for required operator IAM.

Pool org provisioning

Each pool org must be provisioned before e2e can use it:

  1. Org exists with botsend as owner
  2. test-repo and e2e-lock repos (lock created at runtime)
  3. All role apps installed, including fullsend-ai-e2e with Repository → Variables: Read and write (actions_variables) and Organization → Variables: Read and write (organization_actions_variables)
  4. FULLSEND_FOREIGN_E2E_REPOS includes fullsend-ai/fullsend with org-wide visibility (visibility: all)
  5. Mint enrolled: org in ALLOWED_ORGS, ${ORG}/e2e in ROLE_APP_IDS, e2e app PEM enrolled

Use the idempotent setup script:

bash
MINT_PROJECT=... MINT_FUNCTION=... hack/setup-new-e2e-org.sh 07

Verify foreign authorization:

bash
fullsend admin foreign list --org halfsend-01
# expect e2e → fullsend-ai/fullsend

Existing pool orgs (halfsend-01halfsend-12) need a one-time operator pass: install the e2e app (if missing) and run:

bash
fullsend admin foreign allow --org halfsend-NN --role e2e --caller fullsend-ai/fullsend

CI authorization

Pull requests trigger e2e via pull_request_target in .github/workflows/e2e.yml so fork PRs can use repository secrets. Because that exposes credentials to untrusted code, a gate job runs first (see workflow comments for why it is a separate job).

Who runs automatically

E2E tests run without maintainer action when the PR author is an org/repo member or collaborator (author_association of OWNER, MEMBER, or COLLABORATOR on the base repo). The gate uses the frozen github.event.pull_request.author_association from the workflow event — not a live REST lookup — because GITHUB_TOKEN lacks read:org and cannot see org membership for members with private visibility. (Note: agent dispatch paths use the collaborator permission API instead, which does not have this limitation — see ADR 0054.)

Who needs ok-to-test

External contributors and fork PR authors must have a maintainer apply the ok-to-test label after the latest push. The label must be created once in GitHub repo settings (Settings → Labels).

Stale labels

If new commits are pushed after ok-to-test was applied, the label is removed automatically and e2e is skipped until a maintainer re-applies it after reviewing the latest changes. Freshness compares the label timestamp against the frozen PR updated_at from the workflow event (PR_UPDATED_AT); the live API fallback may over-reject when non-push activity bumped updated_at. Applying the label triggers immediate authorization on labeled events.

Blocked runs

When e2e does not run, a sticky PR comment (marker <!-- e2e-gate -->) explains why and what to do. Re-run the workflow or add/re-apply ok-to-test as appropriate.

CI architecture

  1. Gate — authorize the PR author or a fresh ok-to-test label (base checkout only; never checks out PR head)
  2. E2E — checkout PR head SHA, authenticate to GCP via WIF, mint cross-org tokens per pool org, make e2e-test

Pushes to main, merge queue, and workflow_dispatch skip the gate and run e2e directly.

Test GitHub Apps

The fullsend-test-* apps are test-only GitHub Apps owned by fullsend-ai, separate from the production fullsend-ai-* app set. They exist for temporary and test mints, including the Cloudflare Worker mint BT chain. There is no e2e test app. The fix role shares the coder test app and PEM (same as production).

Warning: Do not enroll these apps on the production community mint. They are strictly for test infrastructure.

Role / app / secret inventory

RoleApp SlugApp IDRepository Secret
fullsendfullsend-test-fullsend4312984TEST_FULLSEND_PEM
triagefullsend-test-triage4312988TEST_TRIAGE_PEM
coderfullsend-test-coder4312994TEST_CODER_PEM
reviewfullsend-test-review4313005TEST_REVIEW_PEM
retrofullsend-test-retro4313010TEST_RETRO_PEM
prioritizefullsend-test-prioritize4313012TEST_PRIORITIZE_PEM

PEM private keys are stored as repository secrets on fullsend-ai/fullsend. The fix role reuses the coder app (fullsend-test-coder) and TEST_CODER_PEM.

Installation targets

All six apps are installed with access to all repositories on halfsend-01 through halfsend-12.

App permission scopes

Each app is registered with the minimum explicit permissions its corresponding role requires. Tokens minted for these apps are downscoped by the mint to the canonical role permissions (see internal/mintcore/github.go).

metadata:read is implicitly granted to all GitHub App installations and is not listed below. The canonical role permissions in internal/mintcore/github.go include it for token downscoping completeness, but it is not an explicit registration setting.

RolePermissions
fullsendactions:write, actions_variables:read, administration:write, checks:read, contents:write, issues:read, members:read, organization_projects:read, pull_requests:write, workflows:write
triagecontents:read, issues:write
coderchecks:read, contents:write, issues:write, pull_requests:write
reviewchecks:read, contents:read, issues:write, pull_requests:write
retroactions:read, contents:read, issues:write, pull_requests:write
prioritizecontents:read, issues:write, organization_projects:write

Operator notes

PEM rotation: Generate a new private key on the app's settings page (https://github.com/apps/<slug>/settings), then update the corresponding TEST_*_PEM repository secret on fullsend-ai/fullsend. If the app is enrolled on a test mint, update the PEM secret there as well.

Cloudflare test token rotation: Create a new Account API token with Workers Edit (or the Edit Cloudflare Workers template), store it as TEST_CLOUDFLARE_API_TOKEN, and keep TEST_CLOUDFLARE_ACCOUNT_ID aligned with the account that hosts Worker mint-test. Do not put the new value into site-deploy CLOUDFLARE_API_TOKEN.

Installing on a new pool org: Install each app via its public install URL:

bash
# For each app slug in the inventory table above:
# https://github.com/apps/<slug>/installations/new
# Select the target pool org and grant access to "All repositories".

Using App IDs in ROLE_APP_IDS for temporary mints: When configuring a temporary or test mint (e.g., the CF Worker mint), set ROLE_APP_IDS using the App IDs from the inventory table:

json
{
  "fullsend": "4312984",
  "triage": "4312988",
  "coder": "4312994",
  "review": "4313005",
  "retro": "4313010",
  "prioritize": "4313012"
}

Point the mint's PEM directory (or Secret Manager entries) at the corresponding TEST_*_PEM keys.