Skip to content

Use intrinsic height for scroll snapshots #837

Use intrinsic height for scroll snapshots

Use intrinsic height for scroll snapshots #837

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
format:
name: SwiftFormat Lint
runs-on: xcode-27
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
- run: ./swiftformat --lint
# Catalogs drift out of Xcode's serialization the moment one is written by
# anything but Xcode, and the cost lands on whoever next builds in the IDE
# (see "Formatting" in AGENTS.md). This rides along in the format job
# rather than a job of its own because branch protection pins the two
# check names.
- name: String Catalog lint
run: ./xcstrings --lint
# The only thing that actually stops a stale attribution report from
# shipping: nothing about adding or bumping a dependency forces a re-run,
# and the app can't check itself (its test bundle can't read Package.swift).
# Offline and sub-second, so it rides along here for the same reason the
# catalog lint does.
- name: Attribution report check
run: ./attribution --check
architecture:
name: Bumper Bowling
needs: format
runs-on: xcode-27
env:
BUMPER_CACHE_DIR: .build/bumper-cache
# Where evaluates in about one second; optimize the cold runner build
# instead of spending minutes compiling SwiftSyntax in release mode.
BUMPER_RUNNER_BUILD_CONFIGURATION: debug
steps:
- uses: actions/checkout@v4
- name: Restore Bumper build cache
id: bumper-cache
uses: actions/cache/restore@v4
with:
path: .build
key: ${{ runner.os }}-${{ runner.arch }}-bumper-${{ hashFiles('Package.swift', 'Package.resolved', 'BumperBowling.swift', '.bumper/Sources/**/*.swift', '.bumper/Tests/**/*.swift') }}
- name: Validate configuration
run: swift run bumper config .
- name: Test architecture rules
run: swift run bumper test .
- name: Save Bumper build cache
if: steps.bumper-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: .build
key: ${{ steps.bumper-cache.outputs.cache-primary-key }}
- name: Enforce Where architecture
run: swift run bumper lint . --timings
test:
name: Build & Test (iOS)
needs: format
runs-on: xcode-27
# A cold/stalled CoreSimulator has held this job for 3.5–5 hours on the
# xcode-27 image (a ~10 minute run), so cap it rather than letting a stuck
# boot sit on a runner until the 6-hour default.
timeout-minutes: 45
env:
# A known path for the diagnostics step below, rather than digging through
# DerivedData for whatever `.xcresult` looks freshest.
TEST_WORKDIR: ${{ github.workspace }}/test-output
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
# `./test` owns the whole invocation — it regenerates the project, resolves
# and boots the simulator by UDID via `./simulator` (never by name; see
# "Selecting a simulator" in AGENTS.md), runs the scheme, and streams
# progress. That is the same command a developer runs, so CI and local runs
# can't drift apart, and the progress lines are newline-terminated here
# because stdout isn't a terminal.
#
# `--shared` takes the image's existing iPhone 17 instead of creating a
# device of this checkout's own: a job already owns its VM, so the isolation
# is free here and a fresh device would only add a first-boot data migration
# to every run.
#
# `--all` rather than the default affected-bundle scope: a CI run must not
# depend on what the diff looks like.
- name: Build & Test (iOS)
run: ./test --all --shared --os 27.0
# Hosted tests can crash the test-host process without leaving any error in
# the summarized output. On failure, capture crash reports plus the logs and
# result bundle `./test` wrote, which hold the per-test crash backtraces.
- name: Collect test diagnostics
if: ${{ failure() }}
run: |
mkdir -p diagnostics/crashes
cp -R "$HOME/Library/Logs/DiagnosticReports/." diagnostics/crashes/ 2>/dev/null || true
cp -R "$TEST_WORKDIR" diagnostics/test-output 2>/dev/null || true
echo '--- collected diagnostics ---'
find diagnostics -maxdepth 3 | head -200
- name: Upload test diagnostics
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-diagnostics-ios
path: diagnostics
if-no-files-found: warn
retention-days: 7
snapshot:
name: Snapshot Tests (iOS)
needs: format
# Same image as the `test` job: references are only reproducible against one
# renderer, so the snapshot job must not lag the toolchain the rest of CI
# (and every local run) uses.
runs-on: xcode-27
# Same stuck-simulator cap as the `test` job above, sized for this job's
# longer run.
timeout-minutes: 60
env:
TEST_WORKDIR: ${{ github.workspace }}/test-output
steps:
# Reference images live in Git LFS (see .gitattributes), so fetch them —
# without this the images arrive as pointer files and every comparison fails.
- uses: actions/checkout@v4
with:
lfs: true
- uses: jdx/mise-action@v3
# `--snapshots` is the standalone `StuffSnapshotTests` scheme, which is
# where the SNAPSHOT_EXPECTED_* / TZ pins live and which gathers every
# module's image bundle — so this stays one invocation as more modules opt
# in. `--review` describes any capture that differs from its reference
# (pixel count, max channel delta, changed region), which is the difference
# between a diagnosable failure and "does not match reference".
#
# Deliberately NOT parallel. xcodebuild's `-parallel-testing-enabled` /
# `-parallelize-tests-among-destinations` distribute XCTest *classes*, and
# every test here is Swift Testing, which presents none — so it lands
# everything on one worker and Swift Testing's own in-process parallelism
# then interleaves captures inside a single StuffTestHost, corrupting each
# other's images (verified: 24+ spurious mismatches, and 1.2–3x SLOWER than
# serial). See `SnapshotCaptureLock` and the sharding note in
# Shared/SnapshotKitTesting/AGENTS.md.
- name: Snapshot Tests (iOS)
run: ./test --snapshots --shared --os 27.0 --review
- name: Collect snapshot diagnostics
if: ${{ failure() }}
run: |
mkdir -p diagnostics/crashes
cp -R "$HOME/Library/Logs/DiagnosticReports/." diagnostics/crashes/ 2>/dev/null || true
cp -R "$TEST_WORKDIR" diagnostics/test-output 2>/dev/null || true
echo '--- collected diagnostics ---'
find diagnostics -maxdepth 3 | head -200
- name: Upload snapshot diagnostics
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: snapshot-diagnostics-ios
path: diagnostics
if-no-files-found: warn
retention-days: 7
test-macos:
name: Build & Test (macOS)
needs: format
# Same image as the other jobs so every target builds against one toolchain.
runs-on: xcode-27
# The macOS-only Ledger scheme is self-contained and fast (no simulator, no
# LFS), so this cap is well clear of a normal run.
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
# The workspace mixes iOS targets and the native-macOS Ledger targets, and
# no single xcodebuild destination can build both — so the macOS scheme
# runs here, in parallel with the iOS `test` job, rather than adding to it.
- name: Build & Test (macOS)
run: mise exec -- tuist test Ledger-macOS-Tests --no-selective-testing -- -destination 'platform=macOS'
# Tests can crash the host process on CI without leaving any error in
# Tuist's summarized log. On failure, capture crash reports + the .xcresult
# bundle (which holds the per-test crash backtraces) and upload them.
- name: Collect test diagnostics
if: ${{ failure() }}
run: |
mkdir -p diagnostics/crashes diagnostics/xcresult
cp -R "$HOME/Library/Logs/DiagnosticReports/." diagnostics/crashes/ 2>/dev/null || true
find "$HOME/Library/Developer/Xcode/DerivedData" -maxdepth 6 -name '*.xcresult' \
-exec cp -R {} diagnostics/xcresult/ \; 2>/dev/null || true
cp -R "$HOME/.local/state/tuist/sessions" diagnostics/tuist-sessions 2>/dev/null || true
echo '--- collected diagnostics ---'
find diagnostics -maxdepth 3 | head -200
- name: Upload test diagnostics
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: test-diagnostics-macos
path: diagnostics
if-no-files-found: warn
retention-days: 7