Skip to content
Alle Beiträge
Blog25. März 20265 min read
SL

Screenshots.live

Team

Eine Screenshot-Pipeline in Ihrer CI/CD aufbauen: Schritt-fuer-Schritt-Anleitung

Erfahren Sie, wie Sie eine automatisierte Screenshot-Pipeline mit der Screenshots.live API, GitHub Actions und Fastlane aufbauen. Komplett mit YAML-Konfigurationen, Rendering-Skripten und Upload-Automatisierung.

Warum Screenshots in Ihre CI/CD-Pipeline gehoeren

App-Store-Screenshots werden normalerweise als Design-Aufgabe behandelt, etwas, das einmal vor dem Launch passiert und dann alle paar Monate muehsam aktualisiert wird. Dieser Ansatz scheitert, sobald Ihr Team haeufig ausliefert. Wenn Sie alle zwei Wochen releasen, fallen Ihre Screenshots nach dem ersten Sprint zurueck.

Die Loesung ist, Screenshots wie jedes andere Build-Artefakt zu behandeln. Sie sollten automatisch generiert werden, wenn sich Ihre UI aendert, versioniert neben Ihrem Code, und als Teil Ihres Release-Prozesses in die App Stores deployt werden.

Die Vorteile sind erheblich. Erstens spiegeln Ihre Screenshots immer den aktuellen Stand Ihrer App wider. Zweitens aktualisieren sich lokalisierte Screenshots automatisch. Drittens setzt Ihr Design-Team das Template einmal und Entwickler erledigen den Rest ueber Konfiguration. Viertens eliminieren Sie den manuellen Export-und-Upload-Zyklus.

Architektur-Ueberblick

Eine vollstaendige Screenshot-Pipeline hat fuenf Stufen:

  1. Template-Design — Ihr Design-Team erstellt Screenshot-Templates im visuellen Editor von Screenshots.live.
  2. Konfiguration — Eine YAML-Datei in Ihrem Repository definiert welche Templates gerendert werden, welche Sprachen generiert werden und welche Textvariablen injiziert werden.
  3. Rendering — Ihre CI/CD-Pipeline ruft die Screenshots.live API auf, die alle Variationen rendert und als ZIP-Archiv zurueckgibt.
  4. Nachbearbeitung — Die Pipeline extrahiert das ZIP und organisiert Dateien in die erwartete Verzeichnisstruktur.
  5. Upload — Fastlane liefert die Screenshots automatisch an App Store Connect und Google Play Console.

Konfigurationsdatei einrichten

Erstellen Sie eine Screenshot-Konfigurationsdatei im Wurzelverzeichnis Ihres Repositorys:

# .screenshots/config.yml

api_key: ${SCREENSHOTS_API_KEY}
base_url: https://api.screenshots.live/v1

templates:
  - id: tpl_hero_screen
    name: "Hero Screenshot"
    devices:
      - iphone67
      - ipad129
      - android_phone
      - android_tablet

  - id: tpl_feature_list
    name: "Feature List"
    devices:
      - iphone67
      - ipad129
      - android_phone
      - android_tablet

locales:
  - code: en
    variables:
      headline: "Track Your Progress"
      subtitle: "All-in-one fitness companion"
  - code: de
    variables:
      headline: "Verfolge deinen Fortschritt"
      subtitle: "Alles-in-einem Fitness-Begleiter"

Speichern Sie Ihren API-Schluessel als Repository-Secret, niemals in der Konfigurationsdatei selbst.

GitHub Actions Workflow

Hier ist ein vollstaendiger GitHub Actions Workflow. Erstellen Sie diese Datei unter .github/workflows/screenshots.yml:

name: Generate App Store Screenshots

on:
  push:
    branches: [main]
    paths:
      - '.screenshots/**'
  workflow_dispatch:

env:
  SCREENSHOTS_API_KEY: ${{ secrets.SCREENSHOTS_API_KEY }}
  SCREENSHOTS_OUTPUT_DIR: ./fastlane/screenshots

jobs:
  generate-screenshots:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Cache Screenshots
        uses: actions/cache@v4
        with:
          path: .screenshots/cache
          key: screenshots-${{ hashFiles('.screenshots/config.yml') }}
      - run: npm install js-yaml node-fetch@2 adm-zip
      - run: node .screenshots/generate.js
      - name: Verify
        run: |
          TOTAL=$(find $SCREENSHOTS_OUTPUT_DIR -name "*.png" | wc -l)
          echo "Total: $TOTAL"
          [ "$TOTAL" -gt 0 ] || exit 1
      - uses: actions/upload-artifact@v4
        with:
          name: app-store-screenshots
          path: ${{ env.SCREENSHOTS_OUTPUT_DIR }}

  upload-ios:
    needs: generate-screenshots
    runs-on: macos-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: app-store-screenshots
          path: fastlane/screenshots
      - run: gem install fastlane
      - name: Upload to App Store Connect
        env:
          APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.ASC_KEY_ID }}
          APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
        run: fastlane ios upload_screenshots

  upload-android:
    needs: generate-screenshots
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: app-store-screenshots
          path: fastlane/screenshots
      - run: gem install fastlane
      - name: Upload to Google Play
        env:
          SUPPLY_JSON_KEY_DATA: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
        run: fastlane android upload_screenshots

Das Rendering-Skript

Der Workflow ruft ein Node.js-Skript auf, das die Konfiguration liest, die API aufruft und die Ausgabe organisiert. Erstellen Sie es unter .screenshots/generate.js:

const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const fetch = require('node-fetch');
const AdmZip = require('adm-zip');

const CONFIG_PATH = path.join(__dirname, 'config.yml');
const OUTPUT_DIR = process.env.SCREENSHOTS_OUTPUT_DIR || './fastlane/screenshots';

async function main() {
  const config = yaml.load(fs.readFileSync(CONFIG_PATH, 'utf8'));
  const apiKey = process.env.SCREENSHOTS_API_KEY;
  if (!apiKey) throw new Error('SCREENSHOTS_API_KEY is required');

  fs.mkdirSync(OUTPUT_DIR, { recursive: true });

  for (const template of config.templates) {
    console.log(`Rendering: ${template.name}`);
    const variables = {};
    for (const locale of config.locales) {
      variables[locale.code] = locale.variables;
    }

    const response = await fetch(`${config.base_url}/render`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        templateId: template.id,
        locales: config.locales.map(l => l.code),
        devices: template.devices,
        variables,
      }),
    });

    if (!response.ok) throw new Error(`Render failed: ${response.status}`);

    const zip = new AdmZip(Buffer.from(await response.arrayBuffer()));
    for (const entry of zip.getEntries()) {
      if (entry.isDirectory) continue;
      const outputPath = path.join(OUTPUT_DIR, entry.entryName);
      fs.mkdirSync(path.dirname(outputPath), { recursive: true });
      fs.writeFileSync(outputPath, entry.getData());
    }
  }
  console.log('Done.');
}

main().catch(err => { console.error(err); process.exit(1); });

Fastlane-Integration

# fastlane/Fastfile

platform :ios do
  lane :upload_screenshots do
    deliver(
      skip_binary_upload: true,
      skip_metadata: true,
      screenshots_path: "./fastlane/screenshots/ios",
      overwrite_screenshots: true,
    )
  end
end

platform :android do
  lane :upload_screenshots do
    upload_to_play_store(
      skip_upload_apk: true,
      skip_upload_aab: true,
      skip_upload_metadata: true,
      images_path: "./fastlane/screenshots/android",
    )
  end
end

Lokalisierung in der Pipeline verwalten

Lokalisierungsverwaltung ist einer der groessten Vorteile einer automatisierten Pipeline. Anstatt manuell zu verfolgen, welche Screenshots uebersetzt wurden, wird Ihre Konfigurationsdatei zur einzigen Wahrheitsquelle.

Wenn Sie eine neue Sprache hinzufuegen, fuegen Sie einen Eintrag in config.yml hinzu. Der naechste Pipeline-Lauf generiert Screenshots fuer jedes Template in der neuen Sprache. Keine Designarbeit noetig.

Fuer Teams, die ein Uebersetzungsmanagement-System wie Lokalise, Phrase oder Crowdin verwenden, koennen Sie einen Pre-Render-Schritt hinzufuegen, der die neuesten Uebersetzungen zieht.

Caching und Optimierungstipps

Inhaltsbasierte Cache-Schluessel. Der Workflow verwendet hashFiles('.screenshots/config.yml') als Cache-Schluessel. Wenn sich die Konfiguration nicht geaendert hat, werden gecachte Screenshots sofort wiederverwendet.

Selektives Rendering. Wenn nur eine Sprache geaendert wurde, erwaegen Sie, Ihre Renders nach Sprache aufzuteilen und jede unabhaengig zu cachen.

Paralleles Rendering. Die Screenshots.live API kann gleichzeitige Anfragen verarbeiten:

const renderPromises = config.templates.map(template =>
  renderTemplate(template, config, apiKey)
);
await Promise.all(renderPromises);

Renders mit Webhooks ueberwachen

Fuer Teams, die Einblick in den Rendering-Prozess benoetigen, unterstuetzt Screenshots.live Webhooks, die Ihre Systeme benachrichtigen, wenn Renders abgeschlossen sind oder fehlschlagen. Sie koennen auch Render-Benachrichtigungen an Slack oder Microsoft Teams senden.

Alles zusammenfuegen

Eine vollstaendige Screenshot-Pipeline transformiert App-Store-Asset-Management von einem manuellen, fehleranfaelligen Prozess in ein automatisiertes, zuverlaessiges System:

  1. Ein Designer erstellt oder aktualisiert ein Template im visuellen Editor.
  2. Ein Entwickler aktualisiert die Konfiguration und pusht nach main.
  3. GitHub Actions erkennt die Aenderung und startet den Workflow.
  4. Das Rendering-Skript liest die Konfiguration, ruft die API auf und laedt Screenshots herunter.
  5. Fastlane laedt die Screenshots in beide Stores hoch.
  6. Beide Store-Eintraege sind innerhalb von Minuten aktualisiert.

Der gesamte Prozess laeuft ohne manuellen Eingriff. Ihre Screenshots sind immer aktuell, immer konsistent und immer lokalisiert.

Ähnliche Beiträge