Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ jobs:
- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer

# TODO: The script was written for local use and will need CI-friendly
# xcodebuild flags (CODE_SIGN_IDENTITY="", CODE_SIGNING_REQUIRED=NO)
# passed through to capture-screenshots.sh before this works on runners.
# SCREENSHOT_XCODEBUILD_ARGS disables code signing for the underlying
# capture-screenshots.sh so the UI-test build works on a runner (simulator
# builds need no signing).
- name: Generate promo screenshots
env:
SCREENSHOT_XCODEBUILD_ARGS: 'CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO COMPILER_INDEX_STORE_ENABLE=NO'
run: ./scripts/generate-promo-screenshots.sh

- name: Upload promo screenshots
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/screenshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: App Store Screenshots

on:
workflow_dispatch:

env:
XCODE_VERSION: '26.2'

jobs:
screenshots:
name: Capture App Store Screenshots
runs-on: macos-15
timeout-minutes: 60
if: github.actor == 'adborbas'
steps:
- uses: actions/checkout@v4

- name: Select Xcode
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer

# The script targets iOS 26 simulators (Liquid Glass styling), which ship
# with the Xcode 26 image. SCREENSHOT_XCODEBUILD_ARGS disables code signing
# so the UI-test build works on a runner (simulator builds need no signing).
- name: Capture App Store screenshots
env:
SCREENSHOT_XCODEBUILD_ARGS: 'CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO COMPILER_INDEX_STORE_ENABLE=NO'
run: ./scripts/capture-screenshots.sh

- name: Upload screenshots
uses: actions/upload-artifact@v4
with:
name: app-store-screenshots
path: Screenshots/
30 changes: 27 additions & 3 deletions scripts/capture-screenshots.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,32 @@ SCHEME="Hemera"
OUTPUT_DIR="$PROJECT_DIR/Screenshots"
DERIVED_DATA="$PROJECT_DIR/.deriveddata-screenshots"

# Find simulator UDID for a device name, preferring the latest runtime.
# Extra args appended to every `xcodebuild test` run. CI sets this to pass the
# code-signing flags a simulator build needs on a runner (no signing identity);
# empty for local runs, which sign automatically. Word-split on spaces.
EXTRA_XCODEBUILD_ARGS=(${SCREENSHOT_XCODEBUILD_ARGS:-})

# Find simulator UDID for a device name, restricted to iOS 26+ runtimes so the
# screenshots capture the iOS 26 "Liquid Glass" styling — older runtimes render
# the pre-26 look. Among matching runtimes the highest version wins.
# Args: DEVICE_NAME MATCH_MODE (exact|substring)
find_simulator() {
local name="$1"
local match_mode="${2:-exact}"
xcrun simctl list devices available -j | python3 -c "
import json, sys
import json, sys, re
data = json.load(sys.stdin)
name, match_mode = sys.argv[1], sys.argv[2]
candidates = []
for runtime, devices in data['devices'].items():
m = re.search(r'iOS-(\d+)-(\d+)', runtime)
if not m or int(m.group(1)) < 26:
continue
version = (int(m.group(1)), int(m.group(2)))
for d in devices:
matched = (name == d['name'].strip()) if match_mode == 'exact' else (name in d['name'])
if matched and d['isAvailable']:
candidates.append((runtime, d['udid']))
candidates.append((version, d['udid']))
candidates.sort(key=lambda x: x[0])
if candidates:
print(candidates[-1][1])
Expand All @@ -32,6 +43,18 @@ IPHONE_69_ID="$(find_simulator "iPhone 16 Pro Max" "exact")"

IPAD_13_ID="$(find_simulator "iPad Pro 13-inch (M4)" "substring")"

# These devices are expected on an iOS 26+ runtime (guaranteed by the CI Xcode 26
# image). Fail loudly if either is missing rather than silently capturing a
# partial set — or, on an older runtime, the pre-26 look.
missing=()
[ -z "$IPHONE_69_ID" ] && missing+=("iPhone 16 Pro Max")
[ -z "$IPAD_13_ID" ] && missing+=("iPad Pro 13-inch (M4)")
if [ ${#missing[@]} -gt 0 ]; then
echo "ERROR: No iOS 26+ simulator found for: ${missing[*]}." >&2
echo "This script targets iOS 26 for the Liquid Glass styling; those sims ship with the CI Xcode 26 image (locally, add an iOS 26 device for each)." >&2
exit 1
fi

capture_screenshots() {
local device_name="$1"
local device_id="$2"
Expand Down Expand Up @@ -65,6 +88,7 @@ capture_screenshots() {
"${testing_args[@]}" \
-derivedDataPath "$DERIVED_DATA" \
-resultBundlePath "$result_bundle" \
${EXTRA_XCODEBUILD_ARGS[@]+"${EXTRA_XCODEBUILD_ARGS[@]}"} \
2>&1 | tail -5

local attachment_dir="$OUTPUT_DIR/$output_subdir"
Expand Down
Loading