The Screenshot Problem
Every time CCFish ships a new feature or redesigns a screen, we need updated App Store screenshots. For a single iOS release, that means:
- **6.5-inch iPhone screenshots** (6 screens)
- **5.5-inch iPhone screenshots** (6 screens, re-cropped)
- **12.9-inch iPad screenshots** (6 screens, re-cropped)
- **Localized text** for English, Vietnamese, Japanese, Korean
That's 72 individual screenshot files per release if we do it manually. At one release every two weeks, the manual workflow was consuming an entire design sprint.
Enter automation.
The Pipeline Architecture
We built a CI/CD pipeline that generates all 72 screenshots from a single source of truth:
```
CCFish Game Client (Cocos Creator build)
→ iPad Simulator Screenshot Capture (Xcode CLI)
→ Image Processor (Sharp on Node.js)
→ Localization Layer (L10n JSON)
→ Export to App Store Connect (Fastlane deliver)
```
Step 1: Automated Screenshot Capture
Instead of manually swiping through the game, we script the screenshot capture using Xcode's `simctl` and a headless interaction layer:
```bash
#!/bin/bash
Capture all screenshots for a given locale
DEVICE="iPad Pro (12.9-inch) (6th generation)"
LOCALE=$1
xcrun simctl boot "$DEVICE"
xcrun simctl bootstatus "$DEVICE" -b
Launch CCFish with locale override
xcrun simctl launch booted com.ccfish.game --AppleLocale "$LOCALE"
sleep 3 # Wait for initial load
Trigger screenshot scenes via URL scheme
for scene in "main-menu" "gameplay" "shop" "settings" "leaderboard" "achievements"; do
xcrun simctl openurl booted "ccfish://screenshot/$scene"
sleep 1
xcrun simctl io booted screenshot "screenshots/$LOCALE/$scene.png"
done
echo "Captured $LOCALE screenshots"
```
Step 2: Image Processing Pipeline
Raw simulator screenshots need cropping, resizing for different device targets, and overlay application:
```javascript
const sharp = require('sharp');
async function processScreenshot(source, devices) {
const base = await sharp(source);
const results = [];
for (const device of devices) {
const resized = base
.clone()
.resize(device.width, device.height, {
fit: 'contain',
background: { r: 0, g: 0, b: 0, alpha: 1 }
});
// Overlay device frame + localized text
const framed = await resized
.composite([
{ input: `frames/${device.name}.png`, top: 0, left: 0 },
{ input: `overlays/${device.locale}.png`, top: 20, left: 20 }
])
.toFile(`output/${device.name}_${device.locale}.png`);
results.push(framed);
}
return results;
}
```
Step 3: Localization Layer
All text overlays come from a single JSON file per locale:
```json
{
"en": {
"main-menu": { "title": "Dive In", "subtitle": "Match and Collect!" },
"gameplay": { "title": "Swish to Catch", "subtitle": "Easy to Learn, Fun to Master" }
},
"vi": {
"main-menu": { "title": "Khám Phá", "subtitle": "Chơi và Sưu Tập!" },
"gameplay": { "title": "Vuốt để Bắt", "subtitle": "Dễ Học, Khó Bỏ" }
}
}
```
Step 4: Fastlane Upload
The final step uploads everything to App Store Connect:
```ruby
lane :upload_screenshots do
Dir.glob('output/*.png').each do |screenshot|
deliver(
screenshots: [screenshot],
skip_metadata: true,
skip_app_version_update: true
)
end
end
```
CI/CD Integration
We run this pipeline in GitHub Actions whenever a release branch is created:
```yaml
name: Generate Screenshots
on:
create:
branches: [release/**]
jobs:
screenshots:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- run: brew install imagemagick
- run: ./scripts/capture-screenshots.sh
- run: node scripts/process-images.js
- run: bundle exec fastlane upload_screenshots
```
Results
| Metric | Before | After |
|--------|--------|-------|
| Time per release | 4-6 hours | 12 minutes |
| Screenshot errors | ~5% (human cropping mistakes) | <1% |
| Languages supported | 2 | 4 |
| Cost per release | $250 (designer time) | $0.08 (GitHub Actions) |
Key Takeaways
1. **Simulator screenshots with URL scheme triggers** are surprisingly reliable. We added a `ccfish://screenshot/<scene>` handler in Cocos Creator that navigates to the right in-game state and hides UI overlays.
2. **Sharp is a beast.** Processing 72 screenshots takes under 30 seconds on a GitHub Actions runner.
3. **Separate capture from processing.** Raw captures go to one directory, processed outputs to another. If the overlay design changes, we reprocess without re-capturing.
4. **Locale-driven builds are worth the effort.** The Cocos Creator build accepts an `--AppleLocale` argument that swaps all string assets. This was a one-time engineering investment that every subsequent release benefits from.
If you're maintaining a mobile game and dreading screenshot updates, this pipeline pays for itself in the first release. The code is straightforward, the tools are free, and your designer will thank you.