evidence captures repeatable proof that app flows work, using real iOS app runs instead of manual replay.
It is an open-source Swift package and companion CLI for screenshots, App Store assets, preview video sources, and build evidence from declarative plans.
- Describes screenshot flows as
ScreenshotPlanscenes, anchors, launch hooks, and navigation actions. - Writes captures to predictable output directories for review, release checks, and App Store source material.
- Provides CLI workflows for screenshot capture, build evidence, resizing, marketing renders, and preview video encoding.
- Uploads App Store screenshots from the same captured directory with dry-run planning and dimension checks.
- Keeps app-specific plans, copy, brand data, and generated artifacts in the consuming app repository.
- macOS 14 or newer
- Xcode and command line tools
- Swift Package Manager
- ImageMagick for
resizeandrender-marketing - ffmpeg for
record-preview - Fastlane if a consuming app still uses Fastlane snapshot around the capture workflow
brew install imagemagick ffmpegClone and verify the package:
git clone https://github.com/RiddimSoftware/evidence.git
cd evidence
swift test
swift run evidence -- --helpRender the sample marketing scene:
cd Examples
swift run --package-path .. evidence -- render-marketing \
--scene Marketing/scene.json \
--svg /tmp/evidence-scene.svg \
--output /tmp/evidence-scene.pngAdd the Evidence library to an app's UI test target, then describe the scenes that should be proven and captured:
import Evidence
import XCTest
final class AppEvidenceTests: XCTestCase {
func testCaptureScreenshots() throws {
let plan = ScreenshotPlan(
name: "App Store Screenshots",
launchHook: LaunchHook(
launchArguments: ["--ui-testing"],
launchEnvironment: ["EVIDENCE_MODE": "1"]
),
scenes: [
ScreenshotPlan.Scene(
name: "Home",
anchors: [.staticText("Home")],
navigation: [.tap(label: "Search")]
),
ScreenshotPlan.Scene(
name: "Search",
anchors: [.button("Cancel")]
)
]
)
try plan.run()
}
}Screenshots are written to EVIDENCE_OUTPUT_DIR, APPSTORE_SCREENSHOT_DIR, or EvidenceOutput in the current directory.
Create a .evidence.toml file in the project that will run evidence workflows:
scheme = "ExampleApp"
bundle_id = "com.example.app"
simulator_udid = "YOUR-SIMULATOR-UDID"
evidence_dir = "docs/build-evidence"
screenshot_targets = ["6.9", "6.5", "6.1", "5.5", "ipad-13"]
preview_targets = ["app-preview"]
device_matrix = ["iPhone 16 Pro Max"]If the Xcode workspace or project that owns the screenshot UI tests is not at the directory where evidence capture-screenshots runs (for example, the iOS project lives in ios/ while .evidence.toml lives at the repo root), set one of the optional fields below. The value is forwarded to xcodebuild as -workspace or -project. Set at most one:
# Either:
xcode_workspace = "ios/MyApp.xcworkspace"
# Or:
xcode_project = "ios/MyApp.xcodeproj"evidence capture-evidence can also produce the matching .xcresult bundle from xcodebuild test, plus a markdown summary suitable for inlining in a pull request comment. Enable it in .evidence.toml:
xcresult_enabled = true
xcresult_keep_full_bundle = true # default; set false to ship only the summaryA run with --ticket APP-123 then writes:
<evidence_dir>/APP-123-running.png(the screenshot, as before)<evidence_dir>/APP-123.xcresult(full bundle, openable in Xcode andxcrun xcresulttool)<evidence_dir>/APP-123-tests.md(totals, first three failures withfile:line, total duration)
When xcresult_keep_full_bundle = false (or the CLI flag --xcresult-summary-only is passed), the markdown summary stays in the evidence directory and the full bundle is moved to ~/.evidence/cache/APP-123.xcresult so the bundle remains inspectable locally without bloating the repo.
If xcodebuild test fails before the bundle is produced (for example, a build error), <KEY>-tests.md still gets written with a Build error excerpt so the PR comment surfaces what went wrong. The CLI exits non-zero in that case so CI catches the failure.
The conceptual
[xcresult]table is exposed as flat keys (xcresult_enabled,xcresult_keep_full_bundle) because the project's TOML parser is intentionally lightweight. Behaviour is otherwise identical.
Run the command that matches the workflow:
evidence capture-screenshots
evidence capture-evidence --ticket APP-123
evidence resize --input raw.png --target 6.9 --output app-store.png
evidence record-preview --input capture.mov --output preview.mp4 --trim-start 0 --trim-end 30
evidence render-marketing --scene scene.json --svg scene.svg --output scene.png
evidence upload-screenshots --dry-runThe CLI wraps Xcode simulator tooling, ImageMagick, and ffmpeg with explicit checks so missing local dependencies fail with actionable messages.
Use raw capture when the screenshot should show the app exactly as it runs. Use render-marketing when the App Store asset needs a composed layout with headlines, badges, metrics, timelines, device framing, or source text around app imagery.
Marketing scenes are JSON files with app-owned copy and brand values. See Examples/Marketing/scene.json for a complete example using the supported row kinds: left, right, badge, metric, timeline, stage, row, and compose.
evidence upload-screenshots closes the loop from captured screenshots to App Store Connect screenshot slots. It scans PNGs under evidence_dir, validates their dimensions against the device target directory, plans create/replace/skip actions, and uploads changed screenshots through App Store Connect's resumable upload operations.
Add App Store Connect API credentials to .evidence.toml:
[app_store_connect]
key_id = "ABC123DEFG"
issuer_id = "00000000-0000-0000-0000-000000000000"
p8_path = ".secrets/AuthKey_ABC123DEFG.p8"
app_id = "1234567890"The private .p8 file should stay outside git. In CI, write it from a secret before running the command.
Supported screenshot layouts:
docs/build-evidence/6.9/01-home.png
docs/build-evidence/ipad-13/01-home.png
docs/build-evidence/en-US/6.9/01-home.png
docs/build-evidence/fr-FR/6.9/01-home.png
Use dry-run first:
evidence upload-screenshots --dry-run
evidence upload-screenshots --dry-run --locale en-USThe plan lists every slot, whether the content hash already matches (✓) or would change (✗), and the action (create, replace, or skip). A real upload deletes replaced screenshots, creates new screenshot resources, uploads the PNG bytes through the returned upload operations, and marks each screenshot uploaded.
GitHub Actions example:
jobs:
upload-screenshots:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: Write App Store Connect key
shell: bash
env:
ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
run: |
mkdir -p .secrets
printf '%s' "$ASC_PRIVATE_KEY" > .secrets/AuthKey_ABC123DEFG.p8
- uses: RiddimSoftware/evidence@v0
with:
subcommand: upload-screenshots
extra-args: '--dry-run'evidence ships a reusable GitHub Action so any iOS app repo can run the CLI on a hosted macOS runner without bootstrapping Xcode tooling, ImageMagick, or ffmpeg by hand. Pin to a major version for stability, or to a SHA for full reproducibility.
iOS — capture build evidence on every PR:
jobs:
capture-evidence:
runs-on: macos-14
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: RiddimSoftware/evidence@v0
with:
subcommand: capture-evidence
ticket: ${{ github.event.pull_request.title }}
comment-on-pr: 'true'
github-token: ${{ secrets.GITHUB_TOKEN }}Web — capture Playwright screenshots on every PR:
jobs:
capture-web:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Start local HTTP server
run: python3 -m http.server 8765 &
- uses: RiddimSoftware/evidence@v0
with:
subcommand: capture-web
platform: web
comment-on-pr: 'true'
github-token: ${{ secrets.GITHUB_TOKEN }}The Action accepts a subcommand input matching the CLI verb (capture-screenshots, capture-evidence, capture-web, resize, render-marketing, record-preview, upload-screenshots) along with passthrough inputs for config, ticket, output-dir, and extra-args. Set comment-on-pr: 'true' and pass github-token to have the Action post a PR comment listing every artifact produced by the run; the comment step is automatically skipped when no token is supplied or when the workflow does not run on a pull_request event.
The platform input selects the capture mode: ios (default) for iOS simulator captures on macOS runners, or web for Playwright Chromium screenshots on any runner (including ubuntu-latest). When platform: web, Node.js 20 and the Playwright Chromium browser are installed and cached automatically.
ImageMagick and ffmpeg are installed and cached the first time the Action runs on an iOS runner, so warm runs reuse the formula tarballs. The evidence CLI itself is built once per release ref and cached under ~/runner.temp/evidence-build/.build.
Three ready-to-copy workflows live under Examples/workflows/:
capture-evidence-on-pr.yml— captures a screenshot per pull request, posts it as a PR comment, and uploads it as an artifact.capture-screenshots-on-tag.yml— captures the full App Store screenshot matrix when you push a release tag.capture-web-on-pr.yml— starts a local HTTP server and captures Playwright web screenshots on every PR, posting a comment with the results.
Marketplace listing: https://github.com/marketplace/actions/evidence. The iOS platform requires macos-14 or newer; the web platform runs on any runner with Node.js available (including ubuntu-latest).
docs/troubleshooting.mdcovers common simulator, permissions, dependency, and output-path problems.docs/versioning.mddescribes the v0.x stability policy and release-note expectations.docs/launch/README.mdcontains public launch materials: blog draft, social posts, demo script, HN draft, and checklist.CONTRIBUTING.mdexplains how to report issues and open pull requests safely in a public repository.Examples/README.mddescribes the shipped GitHub Action examples and fixture project.
swift test
swift run evidence -- --helpMIT. See LICENSE.