Skip to content

ci: add E2E staging workflow for iOS - #747

Merged
piotr-iohk merged 8 commits into
masterfrom
cursor/e2e-staging-workflow-e7f4
Sep 15, 2026
Merged

piotr-iohk merged 8 commits into
masterfrom
cursor/e2e-staging-workflow-e7f4

Conversation

@piotr-iohk

@piotr-iohk piotr-iohk commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds a new E2E Staging workflow (.github/workflows/e2e-staging.yml) for nightly regression testing and manual dispatch against staging/regtest infrastructure.

Triggers: nightly schedule (cron 0 4 * * *) and workflow_dispatch with optional inputs. No PR trigger — this workflow runs only on schedule or manual dispatch.

Changes

  • New workflow: e2e-staging.yml with:

    • Nightly schedule (0 4 * * *) and manual dispatch triggers only
    • Builds iOS app with E2E_BUILD flag and regtest network config
    • Runs E2E tests from synonymdev/bitkit-e2e-tests repo
    • Matrix shards: @multi_address_2, @pubky, @transfer_max
    • Up to 3 retry attempts with simulator reset on attempt 3
    • Slack summary via shared scripts/slack_summary.py script
  • Inputs:

    • e2e_branch — E2E tests branch to use (default: default-feature-branch)
    • post_to_slack — checkbox to post E2E staging summary to Slack (default: off)
  • Slack notification gating:

    • Scheduled runs: always post summary to #bitkit-staging-nightly
    • Manual dispatch: post only when post_to_slack checkbox is checked
    • Summary posted on every finished gated run, not just failures
    • STAGING_RESULT is success only when all needed jobs succeed (catches cancelled runs)
  • Concurrency:

    • Group is ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} with cancel-in-progress: true, so schedule and dispatch never share a group and cannot cancel each other

Dependencies

  • ✅ bitkit-e2e-tests#223 — shared scripts/slack_summary.py with e2e-staging mode (merged to main)

Test Plan

  • Workflow syntax validated
  • Manual dispatch test run

Notes

  • @transfer_1 temporarily omitted from staging shard pending e2e-tests fix; @transfer_max kept
  • Uses default-feature-branch logic from determine-e2e-branch.yml
  • timeout-minutes: 90 retained for e2e-tests-staging job

Design

N/A — no UI changes.

Open in Web Open in Cursor 

Add .github/workflows/e2e-staging.yml that runs staging E2E matrix
separately from the main e2e-tests.yml merge gate.

Triggers:
- schedule: 04:00 UTC daily (after migration cron)
- workflow_dispatch: with e2e_branch input for custom branch
- pull_request: for self-testing this PR

Staging shards:
- @multi_address_2
- @pubky
- @transfer_1|@transfer_max

Features:
- Reuses build-staging and e2e-branch patterns from e2e-tests.yml
- BACKEND=regtest for staging environment
- 3 retry attempts with simulator reset before attempt 3
- Slack failure notification to #bitkit-staging-nightly

Refs: synonymdev/bitkit-e2e-tests#221

Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>
@piotr-iohk
piotr-iohk marked this pull request as ready for review September 11, 2026 17:35
@greptile-apps

greptile-apps Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

The workflow should not merge until build and branch-resolution failures reliably trigger the intended staging alert.

Findings

  1. P1 Prerequisite failures skip alerts ▶
  2. P2 Security Mutable action receives secret ▶

Summary

  • Runs three staging shards using a separately built regtest application.
  • Supports nightly, manual, and pull-request triggers with configurable E2E branch selection.
  • Retries failed shards up to three times and resets the simulator before the final attempt.
  • Adds Slack reporting for non-PR failures, but prerequisite failures currently bypass that notification.

Reviews (1) · Last reviewed commit: "ci: add E2E staging workflow for iOS"

Comment thread .github/workflows/e2e-staging.yml Outdated
Comment thread .github/workflows/e2e-staging.yml Outdated
cursoragent and others added 3 commits September 11, 2026 19:04
- P1: notify-failure now depends on build-staging, e2e-branch, and
  e2e-tests-staging; fires if any fails on schedule/workflow_dispatch
- P2: pin slackapi/slack-github-action to SHA b0fa283ad8fea605de13dc3f449259339835fc52 (v2.1.0)

Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>
…taging

- Remove pull_request trigger (keep only schedule and workflow_dispatch)
- Remove transfer_staging shard (@transfer_1|@transfer_max) from matrix
- Remove draft PR checks (no longer needed without PR trigger)
- Simplify notify-failure condition (keep build-staging/e2e-branch/e2e-tests-staging coverage)

Transfers deferred to PR B.

Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>
Add @transfer_max grep to staging workflow. @transfer_1 temporarily
omitted pending e2e-tests fix (PR B).

Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>
- Add boolean input notify_slack (default: false)
- Scheduled runs: always notify Slack on failure
- Manual dispatch: notify only when checkbox is checked

Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 file: Adds a nightly and manually dispatched staging E2E workflow that builds a regtest iOS app, runs three shard greps with three attempts each, and posts a Slack alert when a shard fails. 1 non-blocking finding inline.

Coverage

Total: 0% (whole PR, 1 file)

  • Journeys: 0% - The change is a CI workflow; no user journey is implemented, changed, or exercised by it.
  • Unit tests: 0% - No unit or integration suite covers e2e-staging.yml, and the diff adds no test.
  • QA: 0% - Manual Tests not run

Reviewed by Codex (deepseek-v4.1-flash-high) via gh-pr-review-loop skill

Comment thread .github/workflows/e2e-staging.yml Outdated

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings. This is a faithful merge of two workflows that already work in this repo, and I verified the merge mechanically rather than by eye: build-staging against e2e-tests.yml:134-222, and e2e-tests-staging against e2e-tests.yml:373-508. They differ only in dropped detect-changes gating, the shard list, BACKEND: regtest hoisted to job level (line 136, semantically identical), hardcoded strings replaced by env refs, and artifact names (-staging_ vs -regtest_, consistent between the upload at line 111 and the download at line 154).

What I checked that was worth checking:

  • Staging really is staging. Env.swift:19-25 makes E2E_BUILD set isE2E = true; :71 reads E2E_BACKEND from Info.plist (Info.plist:22-23 maps $(E2E_BACKEND)); :95 reads E2E_NETWORK. With E2E_BACKEND: network + E2E_NETWORK: regtest (lines 81-82), electrumServerUrl falls through to electrumServerUrl(for: .regtest) = ssl://electrs.bitkit.stag0.blocktank.to:9999 — staging, not the local docker electrum. E2E_BUILD at line 99 is correct here, and dropping it would be the bug: isE2E would go false, disabling the E2E launch hooks while still landing on the same electrum. That line is byte-identical to the sibling's own build-staging job (e2e-tests.yml:207).
  • Failure propagation is sound. Lines 204/215 put continue-on-error: true on attempts 1-2; attempt 3 (lines 238-247) has none, so its failure fails the job. All four paths traced: pass-first → green; fail-then-pass → green; all-three-fail → red → needs.e2e-tests-staging.result == 'failure' → Slack fires. The .outcome reads at 216/227/239 are the correct accessor under continue-on-error (.conclusion would be wrong).
  • The Slack pin is a real release. Line 269's b0fa283ad8fea605de13dc3f449259339835fc52 resolves to tag v2.1.0.
  • SLACK_WEBHOOK_URL_STAGING exists as a repo secret here, so line 271 will not fail with "Missing input".
  • The nightly will actually fire. The default branch is master, and e2e_migration.yml:10-11 already proves a schedule: nightly runs in this repo. 0 4 * * * (line 5) does not collide with it (0 3 * * *).
  • No injection surface. The only ${{ }} values reaching a run: shell are matrix.shard.grep (workflow-literal) and env.SIMULATOR_NAME. The dispatch input e2e_branch reaches only a reusable-workflow with: and a checkout ref:, never a shell; needs.e2e-branch.outputs.branch goes through an env: indirection (lines 140-142), the safe form.
  • The ${{ env.SIMULATOR_NAME }} self-reference in a step env: block (lines 209-210) looks suspicious but is the established in-production pattern here, identical to e2e_migration.yml:282-283.

Parity against the Android twin synonymdev/bitkit-android#1253, which I reviewed in the same pass:

Behaviour iOS Android Verdict
Schedule 0 4 * * * UTC (L5) same (L5) Same — both alert at the same wall-clock time
Concurrency cancel-in-progress: true on a default-branch-only group (L25-27) same (L22-24) Same; I raised the dispatch-kills-the-nightly case on the Android side and it applies equally here
Failure propagation attempt 3 not continue-on-error (L238-247) same (L208-223) Same
Notify condition result == 'failure' (L257-263) same, reordered (L237-243) Same shape — both miss cancelled/timed_out, already open on this PR
Permissions absent absent Same, and also absent in every existing sibling in both repos
Extra failure diagnostics absent jwalton/gh-docker-logs@v2 (L232-234) Correctly absent here — staging uses the network backend, no docker
Job timeout timeout-minutes: 90 (L122) absent (default 360) Real reporting divergence — a hung Android shard delays the Slack alert by up to 6h vs 90min here. Raised on the Android PR; nothing to change on this one
Slack action pin b0fa283… = v2.1.0 (L269) e598089… = v2.0.0 (L248) Differently shaped; one side should move
Slack payload JSON with workflow/branch/trigger (L273-285) YAML, run link only (L252-258) Differently shaped — the same channel will get two differently-shaped alerts
Shards multi_address_2, pubky, transfer_max those plus hardware_wallet Expected

Not re-raised, already on record here: the prerequisite-failure alert gap, the mutable Slack action tag (now pinned), and the cancelled-result notification gap under cancel-in-progress: true.

cursoragent and others added 2 commits September 14, 2026 07:08
- Replace notify-failure job with slack-report using shared
  scripts/slack_summary.py from bitkit-e2e-tests
- Rename input notify_slack → post_to_slack
- Change concurrency to cancel-in-progress: ${{ github.event_name != 'schedule' }}
  so dispatch cannot silently kill scheduled nightly
- STAGING_RESULT is success only when all needed jobs succeed (fixes
  cancelled runs skipping Slack)
- Post summary on every finished gated run (schedule or post_to_slack),
  not just failures

Dependency: requires scripts/slack_summary.py e2e-staging mode in
synonymdev/bitkit-e2e-tests (parallel PR).

Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>
Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at f26144786. The shared-script refactor is sound and the env-var fix is complete. Three LOW notes, none blocking — all three apply equally to the Android twin.

The env-var contract now matches exactly, which is what the second commit claims and it delivers. All nine names the script reads (SLACK_WEBHOOK_URL, PLATFORM, STAGING_RESULT, BUILD_RESULT, E2E_BRANCH_RESULT, E2E_TESTS_RESULT, RUN_URL, RUN_ATTEMPT, E2E_TESTS_REF) are supplied at lines 273-281, with no orphans in either direction; cross-checked against the script's own fixtures in scripts/test_slack_summary.py. BUILD_STAGING_RESULT -> BUILD_RESULT and E2E_TESTS_STAGING_RESULT -> E2E_TESTS_RESULT were the two that were wrong, and both are fixed. Worth noting the failure mode this avoided: every read uses os.environ.get(..., default), so a mismatch yields "unknown" in the message rather than a failed step — it would have shipped quietly.

Notification paths enumerated at head, since the point of the change was to stop dropping them:

Path Fires? STAGING_RESULT
All shards pass yes success deliberate — input renamed to post_to_slack, this is now a summary not just an alert
A shard fails yes failure
e2e-tests-staging times out at 90 min yes failure GitHub reports a timed-out job as failure
Run cancelled yes — always() survives cancellation failure gap closed; per-job fields still render cancelled, so the message is self-diagnosing
build-staging fails, tests skipped yes failure greptile's P1, genuinely closed
e2e-branch fails yes failure outputs.branch is empty, so the checkout falls back to the default branch and the script is still there
PR / push no — correct, no such triggers
Dispatch with post_to_slack: false no — correct

I verified always() really does survive a concurrency cancellation rather than assuming it: the structurally identical e2e-status job in e2e.yml ran and reported failure in cancelled runs 34485911442 and 34482039396 while its upstreams were cancelled.

Script safety is strictly better than what it replaced. It is Python invoked as python3 scripts/…, so the exec bit and set -euo pipefail are moot (the blob is 100755 anyway). Zero ${{ }} reaches a shell command — every value goes through env: and line 282 is a literal. The webhook is an env: var, never on the command line, so it cannot leak via ps or a set -x trace. And one fewer third-party action to pin on each side.

Concurrency (line 27): the guard does not do what the commit message says, and this one is my fault — I suggested that expression on the Android side last round. cancel-in-progress is evaluated for the incoming run and decides whether that run cancels what is already in the group; it cannot inspect the run in flight. GitHub's documented idiom only protects main because github.ref is also in the group key, so both runs in a group necessarily evaluate it identically. Here github.event_name is not in the group key, so a dispatch on master shares the nightly's group, evaluates 'workflow_dispatch' != 'schedule' -> true, and cancels it.

Simplest correct shape, and it matches what every other workflow in both repos already does:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
  cancel-in-progress: true

Its correctness doesn't depend on the evaluation-context detail at all, which is the main reason to prefer it. There is also a second-order case the expression makes worse: in the reverse order, a schedule run arriving while a dispatch is in flight evaluates false, so it goes pending rather than cancelling — and a pending run is evicted by the next queued run in the group, so two dispatches in the overnight window can drop the nightly before it ever starts.

I am calling this LOW rather than blocking because the consequence I originally cared about is already fixed: a cancelled nightly now posts failure with cancelled in the per-job fields, so the signal isn't lost, just noisy. Reaching it also needs a dispatch inside the 04:00 UTC window. Note e2e_migration.yml in both repos has the same ref-only group with both triggers, so this predates the PR — and the mechanism is demonstrably live: on 2026-09-10, e2e_migration.yml run 34531572432 was cancelled 21 seconds after 34531814945 was created, both workflow_dispatch on refs/heads/master.

Re-verified clean, all at unchanged line numbers: SWIFT_ACTIVE_COMPILATION_CONDITIONS='DEBUG E2E_BUILD' (99); E2E_BACKEND: network / E2E_NETWORK: regtest (81-82), still resolving to ssl://electrs.bitkit.stag0.blocktank.to:9999; continue-on-error on attempts 1-2 only (204, 215) with attempt 3 bare; .outcome not .conclusion (216, 227, 239); timeout-minutes: 90 (122); cron 0 4 * * * not colliding with e2e_migration.yml's 0 3 * * *; artifact names consistent between upload (111) and download (154). No other workflow was switched to the script — the diff is one file, and scanning every workflow at head for slackapi|slack_summary|SLACK_WEBHOOK matches only this one.

Comment thread .github/workflows/e2e-staging.yml
Comment thread .github/workflows/e2e-staging.yml
@piotr-iohk

Copy link
Copy Markdown
Collaborator Author

Suggest we merge with the remaining Lows and enhance later.

This is a new schedule + workflow_dispatch flow — hard to fully exercise Slack/nightly behaviour until it lands on master. The Low notes (Slack script checkout on feature e2e branches; fail-open webhook) are valid follow-ups, but they should not block landing PR A.

Happy to follow up in a small PR after we have a real scheduled/dispatch run to look at.

jvsena42
jvsena42 previously approved these changes Sep 14, 2026
ovi-reviewer[bot]

This comment was marked as resolved.

Include event_name in the concurrency group so a workflow_dispatch on
master cannot cancel a scheduled nightly (or vice versa).

Co-authored-by: piotr-iohk <piotr-iohk@users.noreply.github.com>

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reaudit f261447..97e450c, 1 file. e2e-staging.yml: lines 26-27 now group runs by event name, so a dispatch cannot cancel the nightly. No findings.

Security

Skipped: no security surface in the diff (score 0, threshold 3).

Coverage

Total: 0% (delta diff since f261447, 1 file)

  • Journeys: 0% - The delta changes a CI workflow concurrency key; it implements, changes and exercises no user journey.
  • Unit tests: 0% - No suite in this repository covers e2e-staging.yml, and the delta adds no test.
  • QA: 0% - Manual Tests not run

Reviewed by Claude Code (claude-opus-5-xhigh) via gh-pr-review-loop skill

@ovitrif
ovitrif dismissed ovi-reviewer[bot]’s stale review September 15, 2026 07:05

addressed - reaudit confirmed

@piotr-iohk
piotr-iohk merged commit fa2c364 into master Sep 15, 2026
10 checks passed
@piotr-iohk
piotr-iohk deleted the cursor/e2e-staging-workflow-e7f4 branch September 15, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants